[英]generate random integers with a specific sum
我有 5 个字段,我希望它们都有一个介于 0 和 100 之间的生成数字。但是,5 个字段的总和应该是 100。
当我想为一个字段提供一个随机数时,我会执行以下操作:
Random rnd = new Random();
int x= rnd.Next(1, 10);
但是对于需要总和为 100 的多个字段,我应该如何做到这一点?
您可以使用以下方法:
它基本上对应于在 [0, 100] 区间上随机选择 4 个切片点,并使用 5 个结果区间的长度作为随机数:
const int k = 5;
const int sum = 100;
Random rnd = new Random();
int[] x = new int[k + 1];
// the endpoints of the interval
x[0] = 0;
x[k] = sum;
// generate the k - 1 random sectioning points
for (int i = 1; i < k; i++) {
x[i] = rnd.Next(0, sum + 1);
}
// sort the sectioning points
Array.Sort(x);
// obtain the k numbers with sum s
int[] N = new int[k];
for (int i = 0; i < k; i++) {
N[i] = x[i + 1] - x[i];
}
为了使您的分布均匀,您可以尝试以下方法:
代码:
const int ExpectedSum = 100;
Random rnd = new Random();
int[] fields = new int[5];
// Generate 4 random values and get their sum
int sum = 0;
for (int i = 0; i < fields.Length - 1; i++)
{
fields[i] = rnd.Next(ExpectedSum);
sum += fields[i];
}
// Adjust the sum as if there were 5 random values
int actualSum = sum * fields.Length / (fields.Length - 1);
// Normalize 4 random values and get their sum
sum = 0;
for (int i = 0; i < fields.Length - 1; i++)
{
fields[i] = fields[i] * ExpectedSum / actualSum;
sum += fields[i];
}
// Set the last value
fields[fields.Length - 1] = ExpectedSum - sum;
现场示例: https : //dotnetfiddle.net/5yXwOP
要实现真正的随机分布,每个元素都有机会成为 100,总和为 100,您可以使用以下解决方案:
public static int[] GetRandomDistribution(int sum, int amountOfNumbers)
{
int[] numbers = new int[amountOfNumbers];
var random = new Random();
for (int i = 0; i < sum; i++)
{
numbers[random.Next(0, amountOfNumbers)]++;
}
return numbers;
}
static void Main(string[] args)
{
var result = GetRandomDistribution(100, 5);
}
它将随机数加一,直到达到总和。 这应该满足您的所有标准。
经过思考,我更喜欢以下解决方案,因为它不太可能产生相等的分布:
public static int[] GetRandomDistribution2(int sum, int amountOfNumbers)
{
int[] numbers = new int[amountOfNumbers];
var random = new Random();
for (int i = 0; i < amountOfNumbers; i++)
{
numbers[i] = random.Next(sum);
}
var compeleteSum = numbers.Sum();
// Scale the numbers down to 0 -> sum
for (int i = 0; i < amountOfNumbers; i++)
{
numbers[i] = (int)(((double)numbers[i] / compeleteSum) * sum);
}
// Due to rounding the number will most likely be below sum
var resultSum = numbers.Sum();
// Add +1 until we reach "sum"
for (int i = 0; i < sum - resultSum; i++)
{
numbers[random.Next(0, amountOfNumbers)]++;
}
return numbers;
}
例如。
int sum=100;
int i = 5;
Random rnd = new Random();
while (true)
{
int cur;
--i;
if (i == 0) {
Console.WriteLine(sum + " ");
break;
} else
cur=rnd.Next(1, sum);
sum -= cur;
Console.WriteLine(cur + " ");
}
现场示例: https : //dotnetfiddle.net/ltIK40
或
Random rnd = new Random();
int x= rnd.Next(1, 10);
int y= rnd.Next(x,x+10);
int y2=rnd.Next(y,y+10);
int y3=rnd.Next(y2,y2+10);
int y4=100-(x+y+y2+y3);
我的方法是这样的:
var rnd = new Random();
var numbers = Enumerable.Range(0, 5).Select(x => rnd.Next(0, 101)).ToArray().OrderBy(x => x).ToArray();
numbers = numbers.Zip(numbers.Skip(1), (n0, n1) => n1 - n0).ToArray();
numbers = numbers.Concat(new[] { 100 - numbers.Sum() }).ToArray();
这是我认为可能的统一。
创建您的第一个随机数。 之后,您将num1
和 100 的值之间的差值作为max def of rnd
的max def of rnd
。 但要保证他们的总和是100,你必须检查在最后num
如果所有的总和nums
为100如果最后不是值num
的区别是,他们的总和100。
为了简化您的代码并获得一个干净的结构,将该代码放入一个循环中,而不是单个数字使用int[5]
数组。
private int[] CreateRandomNumbersWithSum()
{
int[] nums = new int[5];
int difference = 100;
Random rnd = new Random();
for (int i = 0; i < nums.Length; i++)
{
nums[i] = rnd.Next(0, difference);
difference -= nums[i];
}
int sum = 0;
foreach (var num in nums)
sum += num;
if (sum != 100)
{
nums[4] = 100 - sum;
}
return nums;
}
解决方案是,不是数字需要随机,而是分布需要随机。 数字的随机性将是其随机分布的副作用。
因此,您将从给定范围内的五个随机数开始。 只要所有五个的范围相同,确切的范围就无关紧要,尽管更宽的范围允许更多的变化。 我会使用 Random.NextDouble() 返回 0 到 1 之间的随机数。
这些单独的数字中的每一个除以这些数字的总和代表一个分布。
例如,假设您的随机数是 .4、.7、.2、.5、.2。 (为简单起见,使用较少的数字。)
这些数字的总和是 2。所以现在分布是这些数字中的每一个除以总数。
.4 / 2 = .20
.7 / 2 = .35
.2 / 2 = .10
.5 / 2 = .25
.2 / 2 = .10
您会注意到,如果有更多的小数位,这些分布将等于 100% 或非常接近它。
输出将是这些分布中的每一个乘以目标数量,在本例中为 100。换句话说,这些数字中的每一个都代表 100 的一部分。
因此,将这些分布中的每一个乘以目标,我们得到 20、35、10、25 和 100,加起来为 100。
麻烦的是,由于四舍五入,您的数字并不总是完美地加起来为 100。要解决这个问题,如果总和小于 100,您可能会在最小数字上加一,或者从总和的最大数字中减去一大于 100。或者您可以选择随机添加或减去其中一个数字。
这是一个创建分布的类。 (我只是在玩,所以我还没有完全优化到死。)
public class RandomlyDistributesNumbersTotallingTarget
{
public IEnumerable<int> GetTheNumbers(int howManyNumbers, int targetTotal)
{
var random = new Random();
var distributions = new List<double>();
for (var addDistributions = 0; addDistributions < howManyNumbers; addDistributions++)
{
distributions.Add(random.NextDouble());
}
var sumOfDistributions = distributions.Sum();
var output = distributions.Select(
distribution =>
(int)Math.Round(distribution / sumOfDistributions * targetTotal, 0)).ToList();
RoundUpOutput(output, targetTotal);
return output;
}
private void RoundUpOutput(List<int> output, int targetTotal)
{
var difference = targetTotal - output.Sum();
if (difference !=0)
{
var indexToAdjust =
difference > 0 ? output.IndexOf(output.Min()) : output.IndexOf(output.Max());
output[indexToAdjust]+= difference;
}
}
}
这是一个不完全科学的单元测试,它对其进行了多次测试并确保结果总和为 100。
[TestMethod]
public void OutputTotalsTarget()
{
var subject = new RandomlyDistributesNumbersTotallingTarget();
for (var x = 0; x < 10000; x++)
{
var output = subject.GetTheNumbers(5, 100);
Assert.AreEqual(100, output.Sum());
}
}
一些示例输出:
5、30、27、7、31
15、7、26、27、25
10、11、23、2、54
这些数字的平均值总是会达到 20,因此虽然 96、1、1、1 是一种假设的可能性,但它们往往会徘徊在接近 20 的位置。
好的。 我之前在这个看似微不足道的问题上的尝试让我感到很沮丧,我决定再试一次。 为什么不将所有的数字都归一化? 这保证了随机性,并避免了排序的 O(n log n) 性能。 它还有一个优点,即使我有基本的数学知识,我也可以计算出数字是均匀分布的。
public static int[] UniformNormalization(this Random r, int valueCount, int valueSum)
{
var ret = new int[valueCount];
long sum = 0;
for (int i = 0; i < valueCount; i++)
{
var next = r.Next(0, valueSum);
ret[i] = next;
sum += next;
}
var actualSum = 0;
for (int i = 0; i < valueCount; i++)
{
actualSum += ret[i] = (int)((ret[i] * valueSum) / sum);
}
//Fix integer rounding errors.
if (valueSum > actualSum)
{
for (int i = 0; i < valueSum - actualSum; i++)
{
ret[r.Next(0, valueCount)]++;
}
}
return ret;
}
这也应该是最快的解决方案之一。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.