简体   繁体   English

生成具有特定总和的随机整数

[英]generate random integers with a specific sum

I have 5 fields, I want them all to have a generated number between 0 and 100. But, the sum of the 5 fields should be 100.我有 5 个字段,我希望它们都有一个介于 0 和 100 之间的生成数字。但是,5 个字段的总和应该是 100。

When I want to give a random number for one field I would do the following:当我想为一个字段提供一个随机数时,我会执行以下操作:

Random rnd = new Random();
int x= rnd.Next(1, 10);

But how should I do that for multiple fields that needs to have a sum of 100 together?但是对于需要总和为 100 的多个字段,我应该如何做到这一点?

You can use the following approach:您可以使用以下方法:

  1. generate 4 random integers in [0, 100]在 [0, 100] 中生成 4 个随机整数
  2. sort them, let's denote the sorted values as 0 ≤ x 1 ≤ x 2 ≤ x 3 ≤ x 4 ≤ 100对它们进行排序,让我们将排序后的值表示为 0 ≤ x 1 ≤ x 2 ≤ x 3 ≤ x 4 ≤ 100
  3. use the following 5 values as the random numbers with sum 100:使用以下 5 个值作为总和为 100 的随机数:
    • N 1 = x 1 N 1 = x 1
    • N 2 = x 2 - x 1 N 2 = x 2 - x 1
    • N 3 = x 3 - x 2 N 3 = x 3 - x 2
    • N 4 = x 4 - x 3 N 4 = x 4 - x 3
    • N 5 = 100 - x 4 N 5 = 100 - x 4

It basically corresponds to randomly choosing 4 sectioning points on the [0, 100] interval, and using the lengths of the 5 resulting intervals as the random numbers:它基本上对应于在 [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];
}

In order to make your distribution uniform, you could try the following aproach:为了使您的分布均匀,您可以尝试以下方法:

  1. Generate some random numbers.生成一些随机数。
  2. Normalize them.使它们正常化。
  3. Correct the last field to get exactly the expected sum, if needed.如果需要,请更正最后一个字段以获得准确的预期总和。

The code:代码:

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;

Live example: https://dotnetfiddle.net/5yXwOP现场示例: https : //dotnetfiddle.net/5yXwOP

To achieve a truly random distribution, with every element having the chance to be 100 with a total sum of 100, you can use the following solution:要实现真正的随机分布,每个元素都有机会成为 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);
}

It increases a random number by one until the sum is reached.它将随机数加一,直到达到总和。 This should fulfill all your criterias.这应该满足您的所有标准。

After thinking about it, I prefer the following solution, because it's less likely to generate an equal distribution:经过思考,我更喜欢以下解决方案,因为它不太可能产生相等的分布:

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;
}

For Example.例如。

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 + " ");                
}

Live Example: https://dotnetfiddle.net/ltIK40现场示例: https : //dotnetfiddle.net/ltIK40

or

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);

My approach is this:我的方法是这样的:

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();

This is as uniform as I think is possible.这是我认为可能的统一。

Create your first random number.创建您的第一个随机数。 After that you take the difference between the value of num1 and 100 as the max def of rnd .之后,您将num1和 100 的值之间的差值作为max def of rndmax def of rnd But to guarantee that their sum is 100, you have to check at the last num if the sum of all nums is 100. If not the value of your last num is the difference that their sum and 100.但要保证他们的总和是100,你必须检查在最后num如果所有的总和nums为100如果最后不是值num的区别是,他们的总和100。

And to simply your code and get a clean strcuture, put that code in a loop and instead of single numbers work with an int[5] array.为了简化您的代码并获得一个干净的结构,将该代码放入一个循环中,而不是单个数字使用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;
}

I think this is a very simple solution:我认为这是一个非常简单的解决方案:

public void GenerateRandNr(int total)
    {
        var rnd = new Random();
        var nr1 = rnd.Next(0, total);
        var nr2 = rnd.Next(0, total - nr1);
        var nr3 = rnd.Next(0, total - nr1 - nr2);
        var nr4 = rnd.Next(0, total - nr1 - nr2 - nr3);
        var nr5 = total - nr1 - nr2 - nr3 - nr4;
    }

EDIT: Just tested it, works fine for me:编辑:刚刚测试过,对我来说很好用: 在此处输入图片说明

The solution is that it's not the numbers that need to be random so much as the distribution needs to be random.解决方案是,不是数字需要随机,而是分布需要随机。 The randomness of the numbers will be a side effect of their random distribution.数字的随机性将是其随机分布的副作用。

So you would start with five random numbers in a given range.因此,您将从给定范围内的五个随机数开始。 The exact range doesn't matter as long as the range is the same for all five, although a broader range allows for more variation.只要所有五个的范围相同,确切的范围就无关紧要,尽管更宽的范围允许更多的变化。 I'd use Random.NextDouble() which returns random numbers between 0 and 1.我会使用 Random.NextDouble() 返回 0 到 1 之间的随机数。

Each of those individual numbers divided by the sum of those numbers represents a distribution.这些单独的数字中的每一个除以这些数字的总和代表一个分布。

For example, say your random numbers are .4, .7, .2, .5, .2.例如,假设您的随机数是 .4、.7、.2、.5、.2。 (Using fewer digits for simplicity.) (为简单起见,使用较少的数字。)

The total of those numbers is 2. So now the distributions are each of those numbers divided by the total.这些数字的总和是 2。所以现在分布是这些数字中的每一个除以总数。

.4 / 2 = .20
.7 / 2 = .35
.2 / 2 = .10
.5 / 2 = .25
.2 / 2 = .10

You'll notice that those distributions will equal 100% or really close to it if there are a lot more decimal places.您会注意到,如果有更多的小数位,这些分布将等于 100% 或非常接近它。

The output is going to be each of those distributions times the target number, in this case, 100. In other words, each of those numbers represents a piece of 100.输出将是这些分布中的每一个乘以目标数量,在本例中为 100。换句话说,这些数字中的每一个都代表 100 的一部分。

So multiplying each of those distributions times the target, we get 20, 35, 10, 25, and 100, which add up to 100.因此,将这些分布中的每一个乘以目标,我们得到 20、35、10、25 和 100,加起来为 100。

The trouble is that because of rounding your numbers won't always perfectly add up to 100. To fix that you might add one to the smallest number if the sum is less than 100, or subtract one from the largest number of the the sum is greater than 100. Or you could choose to add or subtract on one of the numbers at random.麻烦的是,由于四舍五入,您的数字并不总是完美地加起来为 100。要解决这个问题,如果总和小于 100,您可能会在最小数字上加一,或者从总和的最大数字中减去一大于 100。或者您可以选择随机添加或减去其中一个数字。

Here's a class to create the distributions.这是一个创建分布的类。 (I'm just playing around so I haven't exactly optimized this to death.) (我只是在玩,所以我还没有完全优化到死。)

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;
        }
    }
}

And here's a not-perfectly-scientific unit test that tests it many times over and ensures that the results always total 100.这是一个不完全科学的单元测试,它对其进行了多次测试并确保结果总和为 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());
    }
}

Some sample outputs:一些示例输出:

5, 30, 27, 7, 31 5、30、27、7、31
15, 7, 26, 27, 25 15、7、26、27、25
10, 11, 23, 2, 54 10、11、23、2、54

The numbers are always going to average to 20, so while 96, 1, 1, 1 is a hypothetical possibility they're going to tend to hover closer to 20.这些数字的平均值总是会达到 20,因此虽然 96、1、1、1 是一种假设的可能性,但它们往往会徘徊在接近 20 的位置。

Okay.好的。 Having been burned by my previous attempt at this seemingly trivial problem, I decided to have another go.我之前在这个看似微不足道的问题上的尝试让我感到很沮丧,我决定再试一次。 Why not normalise all the numbers after generation?为什么不将所有的数字都归一化? This guarantees randomness, and avoids O(n log n) performance from a sort.这保证了随机性,并避免了排序的 O(n log n) 性能。 It also has the advantage that even with my basic maths, I can work out that the numbers are uniformly distributed.它还有一个优点,即使我有基本的数学知识,我也可以计算出数字是均匀分布的。

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;
}

This should also be one of the fastest solutions.这也应该是最快的解决方案之一。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM