请考虑以下代码。基本思想是返回一个逗号分隔的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;
}
答案 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)的要求,所以请把这个代码用在那个笔记上。