0

给定 n 个骰子,每个骰子都有 x 面,我试图列举所有可能的抽奖。例如,如果我们有 10 个三面骰子,并且它们都落在值 1 面:
10 00 00
我有一个启发式方法(EnumerateDrawsH),它只适用于预先定义的面数和一个递归方法(EnumerateDrawsR ) 适用于任意数量的边。目前,我的 R 版本的性能远低于 H。在 R 中,我需要一种有效的方法来跟踪正在写入的绘图中的骰子总数,这应该是 sum 参数,但它是不正确的。我发现的唯一解决方法是在递归的每个级别上重做总计并将其存储在 drawSum 局部变量中,该变量经过测试以结束递归。有谁知道如何在 sum 参数中获得正确的值?

using System;
using System.Diagnostics;
using System.IO;
using System.Text;

static class EnumerateDicesDraws
{
  public static void Run()
  {
    int nbDices = 10;
    Stopwatch watch;

    watch = Stopwatch.StartNew();
    EnumerateDicesDraws.EnumerateDrawsH(nbDices);
    watch.Stop();
    Console.WriteLine(watch.Elapsed);

    watch = Stopwatch.StartNew();
    EnumerateDicesDraws.EnumerateDrawsR(nbDices, 3);
    watch.Stop();
    Console.WriteLine(watch.Elapsed);

    Console.ReadKey(true);
  }

  public static void EnumerateDrawsR(int nbDices, int nbSides)
  {
    string filePath = Path.Combine(Path.GetTempPath(), string.Concat("Draws[R]", nbDices, ".txt"));
    int[] dices = new int[nbSides];
    using (StreamWriter writer = new StreamWriter(filePath, false))
      EnumerateDrawsR(0, 0, nbDices, dices, writer);
  }
  private static void EnumerateDrawsR(int index, int sum, int nbDices, int[] dices, StreamWriter writer)
  {
    int drawSum = 0;
    for (int i = 0; i < dices.Length; i++)
    {
      drawSum += dices[i];
      if (drawSum == nbDices)
      {
        for (int j = 0; j < dices.Length - 1; j++)
          writer.Write(string.Format("{0:D2} ", dices[j]));
        writer.Write(string.Format("{0:D2} ", dices[dices.Length - 1]));
        writer.Write(string.Format("{0:D3}", sum));
        writer.WriteLine();
        for (; index < dices.Length; index++)
          dices[index] = 0;
        return;
      }
    }
    /*
    if (sum == nbDices)
    {
        for (int j = 0; j < dices.Length; j++)
            writer.Write(string.Format("{0:D2} ", dices[j], j < dices.Length - 1 ? " " : null));
        writer.WriteLine();
        for (; index < dices.Length; index++)
            dices[index] = 0;
        return;
    }
      */
    if (index < dices.Length)
    {
      EnumerateDrawsR(index + 1, sum + dices[index], nbDices, dices, writer);
      EnumerateDrawsR(index, sum + ++dices[index], nbDices, dices, writer);
    }
  }
  public static void EnumerateDrawsH(int nbDices)
  {
    StringBuilder draws = new StringBuilder();
    string format = "{0:D2} {1:D2} {2:D2}\r\n";
    int cumul = 0;
    int[] dices = new int[3];
    for (dices[0] = 0; dices[0] <= nbDices; dices[0]++)
    {
      cumul = dices[0];
      if (cumul == nbDices)
      {
        for (int i = 1; i < dices.Length; i++)
          dices[i] = 0;
        draws.AppendFormat(format, dices[0], dices[1], dices[2]);
        break;
      }
      for (dices[1] = 0; dices[1] <= nbDices; dices[1]++)
      {
        cumul = dices[0] + dices[1];
        if (cumul == nbDices)
        {
          for (int i = 2; i < dices.Length; i++)
            dices[i] = 0;
          draws.AppendFormat(format, dices[0], dices[1], dices[2]);
          break;
        }
        for (dices[2] = 0; dices[2] <= nbDices; dices[2]++)
        {
          cumul = dices[0] + dices[1] + dices[2];
          if (cumul == nbDices)
          {
            draws.AppendFormat(format, dices[0], dices[1], dices[2]);
            break;
          }
        }
      }
    }
    File.WriteAllText(Path.Combine(Path.GetTempPath(), string.Concat("Draws[H]", nbDices, ".txt")), draws.ToString());
  }
}

编辑

她是 nlucaroni 建议的解决方案,递归退出条件是固定的。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

static class Programm
{
  static void Main()
  {
    var watch = Stopwatch.StartNew();
    var outputFile = DiceRoll.Draws(nbDicesTotal: 10, nbValues: 6);
    watch.Stop();
    Console.WriteLine(watch.Elapsed);

    Console.WriteLine();

    Console.WriteLine("Output to :");
    Console.WriteLine(outputFile);

    Console.ReadKey(true);
  }
}

static class DiceRoll
{
  /// <summary>
  /// Writes all possible draws (or distributions) for a given number of dices having a given number of values
  /// </summary>
  /// <param name="nbDicesTotal">Total number of dices rolled</param>
  /// <param name="nbValues">Number of different values of each dice</param>
  /// <returns>Output file path in system temporary folder</returns>
  public static string Draws(int nbDicesTotal, int nbValues)
  {
    var filePath = Path.Combine(Path.GetTempPath(), string.Format("DiceDraws[{0}].txt", nbDicesTotal));
    var distribution = new int[nbValues];
    using (var writer = new StreamWriter(filePath, false))
      foreach (var draw in Draws(0, 0, nbDicesTotal, distribution))
        //Call to Select method adds huge cost of performance. Remove call to Select to compare.
        writer.WriteLine(string.Join(" ", draw.Select(x => x.ToString("D2"))));
    return filePath;
  }

  /// <summary>
  /// Returns all possible draws (or distributions) for a given number of dices having a given number of values
  /// </summary>
  /// <param name="distributionIndex">We are incrementing the number of dices of this value</param>
  /// <param name="nbAddedDices">Number of  dices (up to nbDicesTotal) considered in the recursion. EXIT CONDITION OF THE RECURSION</param>
  /// <param name="nbDicesTotal">Total number of dices rolled</param>
  /// <param name="distribution">Distribution of dice values in a draw. Index is dice value. Value is the number of dices of value 'index'</param>
  /// <returns>All possible draws</returns>
  /// <remarks>Recursive methode. Should not be called directly, only from the other overload</remarks>
  static IEnumerable<IEnumerable<int>> Draws(int distributionIndex,
                                             int nbAddedDices,
                                             int nbDicesTotal,
                                             int[] distribution)
  {
    // Uncomment for exactness check
    // System.Diagnostics.Debug.Assert(distribution.Sum() == nbAddedDices);

    if (nbAddedDices == nbDicesTotal)
    {
      yield return distribution;

      nbAddedDices -= distribution.Length - distributionIndex;
      for (; distributionIndex < distribution.Length; distributionIndex++)
        distribution[distributionIndex] = 0;
    }

    if (distributionIndex < distribution.Length)
    {
      foreach (var draw in Draws(distributionIndex + 1, nbAddedDices, nbDicesTotal, distribution))
        yield return draw;

      distribution[distributionIndex]++;

      foreach (var draw in Draws(distributionIndex, nbAddedDices + 1, nbDicesTotal, distribution))
        yield return draw;
    }
  }
}
4

1 回答 1

1

x您可以通过用n数字计算基数来枚举所有抽奖。

例如,将数组中的所有骰子初始化为最小值,假设骰子的标签是 from0(x-1)。然后,你从一个 's 数组开始0,这是第一个和最低的值,然后你可以增加最右边的直到它等于x-1,你增加第二个骰子,重置第一个骰子,然后继续,等等。

计算机编程艺术第 4 卷:分册 3 包含大量信息,如果您需要按特定顺序列举它们。

于 2013-01-11T16:31:56.410 回答