创建一个返回一系列随机值的方法

时间:2017-07-07 16:28:15

标签: c# random range

请考虑以下代码。基本思想是返回一个逗号分隔的20个数字列表,其中任何一个数字都可以是介于0和最大允许值之间的值。在每次通过循环时,列表​​中下一个值允许的潜在最大值减少了所选的最后一个随机值。

所以它有效......但它很糟糕。问题是它总是会产生这样的输出:

&#34; 22,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0&#34; < / p>

&#34; 17,3,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0&#34; < / p>

&#34; 23,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0&#34; < / p>

如何修改以允许在整个范围内更均匀地分配值?

    public static string RandomRangeOfValuesMaker(int total)
    {
        var remainingTotal = total + 1;

        const int numberOfValues = 20;

        var rand = new Random();

        var builder = new StringBuilder();

        for (var i = 1; i < numberOfValues + 1; i++)
        {
            var currentRandomNumber = rand.Next(remainingTotal);

            remainingTotal = remainingTotal - currentRandomNumber;

            builder.Append(currentRandomNumber + ",");
        }

        var rangeOfValues = builder.ToString().Remove(builder.ToString().Length - 1);

        return rangeOfValues;
    }

3 个答案:

答案 0 :(得分:10)

更新:我误解了对问题的不良约束;我的原始答案解决了问题&#34;生成n个小于m的随机数,按降序排列&#34;。原始答案附于下面。

实际问题是&#34;生成数字m的随机分区为n个部分&#34;。

有很多方法可以做到这一点。这是一个;时间为O(m),空间为O(n)。你能改进吗?

static Random random = new Random();
static IEnumerable<int> Partition(int number, int parts) {
  int[] partition = new int[parts];
  for(int i = 0; i < number; ++i)
    partition[random.Next(parts)] += 1;
  return partition;
}

算法是:我们有parts个箱子和number个苹果;我们将每个苹果随机扔进箱子里,直到我们没有苹果,然后我们将所有苹果分开。

现在你的程序是单行

Partition(maxNumber, 20).OrderByDescending(x=>x).CommaSeparated();

使用下面的帮助函数。

在这个解决方案和Kyle的解决方案中要注意的关键不是生成分区的算法 - 就像我说的,有很多方法可以做到这一点。相反,关键是不要试图在一个功能中做太多。当你试图确保sum属性以及单调性和逗号分离都在同一个地方时,你出错了。排序20个数字很便宜;外包问题。逗号分隔一堆东西很便宜;外包那个问题。编写小函数,每个函数都做好一件事,然后将它们组成更大的函数。

  

如何修改以允许在整个范围内更均匀地分配值?

选择数字首先然后对它们进行排序。

让我们分解吧。第一:随机数的无限序列:

private static Random random = new Random();    
public static IEnumerable<int> RandomNumbers(int max)
{
  while(true) yield return random.Next(max);
}

现在您已经拥有了自己的程序。

public static string RandomRangeOfValuesMaker(int maxNumber)
{
  return string.Join(",", RandomNumbers(maxNumber).Take(20).OrderByDescending(x=>x));
}

每当你做一些涉及序列的事情时,我自己可以使用内置序列运算符来表达我想要的工作流程吗?我可以构建自己的序列运算符吗?

例如,我们可以使另一个序列运算符单行:

public static string CommaSeparated<T>(this IEnumerable<T> items) 
{
  return string.Join(",", items);
}

现在你的程序变得更加优雅了:

public static string RandomRangeOfValuesMaker(int maxNumber)
{
  return RandomNumbers(maxNumber)
    .Take(20)
    .OrderByDescending(x=>x)
    .CommaSeparated();
}

你想要什么? 20个随机数,按降序排列,用逗号分隔。那么你的程序应该怎么读? 应该说我想要20个随机数降序排列,用逗号分隔。编写程序,使其看起来像是要解决的问题的描述。它将更容易理解,更易于维护,更容易看出它是正确的。

答案 1 :(得分:2)

如果你想要一个加起来特定和的随机整数序列,最简单的方法是首先选择[0,sum]范围内的一系列随机整数,对它们进行排序,然后返回差异:

public static IEnumerable<int> GetSequenceWithSum( Random rand, int count, int sum )
{
    // Get a list of count-1 random numbers in the range [0, sum] in ascending order.
    // This partitions the range [0, sum] into count different bins of random size.
    var partition = Enumerable.Range( 0, count - 1 )
                              .Select( _ => rand.Next( 0, sum + 1 ) )
                              .OrderBy( x => x );

    // Yield the size of each partition in the range.
    int previous = 0;
    foreach( int value in partition )
    {
        yield return value - previous;
        previous = value;
    }

    yield return sum - previous;
}

我无法准确说出这给你带来的分配,但它会给你一个看起来&#34;甚至&#34;。

了解其工作原理的最简单方法是以这种方式思考:

假设您想要一堆随机长度的棒,但是当您将棒粘在一起时,您希望它们的长度加起来达到一定的长度。

实现这一目标的一种方法是从你想要的长度开始,然后将它随机切成你想要的数量。这样,每件作品的长度总和就是你开始作品的长度。

此代码&#34; cut&#34;通过首先选择代表我们切割大棒的位置的随机数来完成。然后它按顺序放置切割位置,以便我们知道哪些切口彼此相邻。然后它所要做的就是减去连续的切口以获得棒块的长度。

答案 2 :(得分:0)

我将在前面加上:阅读Eric Lippert的答案 - 他说的几乎肯定比我更正确(pssst ..他在大学学习数学并在微软的C#编译器上工作过! )。

您的要求的基本实现,即函数返回总和为Y的X值可以写为:

public static string StringyRandomNumbersThatAddUpToMaxNumber(int sumOfRandomNumbers, int numberOfValuesToReturn)
{
    var randomNumbers = RandomNumbersThatAddUpToMaxNumber(new List<int>(), sumOfRandomNumbers, numberOfValuesToReturn);
    return string.Join(",", randomNumbers);
}

public static IEnumerable<int> RandomNumbersThatAddUpToMaxNumber(List<int> randomNumbers, int sumOfRandomNumbers, int numberOfValuesToReturn)
{
    var randomNumberGenerator = new Random();

    while (randomNumbers.Sum() < sumOfRandomNumbers)
    {
        var ceiling = (randomNumbers.Any() ? randomNumbers.Sum() : sumOfRandomNumbers) / 2;
        var newNumber = randomNumberGenerator.Next(ceiling);

        if (randomNumbers.Sum() + newNumber <= sumOfRandomNumbers)
        {
            randomNumbers.Add(newNumber);
        }
    }

    if (numberOfValuesToReturn > randomNumbers.Count())
    {
        randomNumbers.Remove(randomNumbers.Max());

        return RandomNumbersThatAddUpToMaxNumber(randomNumbers, sumOfRandomNumbers, numberOfValuesToReturn);
    }

    return randomNumbers;
}

从理论上讲,它可能永远不会返回或抛出StackOverflowException,但在实践中它可能会发生。{/ p>

我不会以任何方式,形状或形式确信结果数字可以被认为是真正随机的(因为它消除了每次递归的最高值,以允许更多“工作空间”来查找更多值满足它们都加起来Y)的要求,所以请把这个代码用在那个笔记上。