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

时间:2018-01-11 10:11:52

标签: c# algorithm

我有5个字段,我希望它们都有0到100之间的生成数字。但是,5个字段的总和应该是100。

当我想为一个字段提供随机数时,我会执行以下操作:

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

但是我应该如何为需要总和为100的多个字段做到这一点?

9 个答案:

答案 0 :(得分:9)

您可以使用以下方法:

  1. 在[0,100]
  2. 中生成4个随机整数
  3. 对它们进行排序,让我们将排序值表示为0≤x 1 ≤x 2 ≤x 3 ≤x 4 < / sub>≤100
  4. 使用以下5个值作为总和100的随机数:
    • N 1 = x 1
    • N 2 = x 2 - x 1
    • N 3 = x 3 - x 2
    • N 4 = x 4 - x 3
    • N 5 = 100 - x 4
  5. 它基本上对应于在[0,100]间隔上随机添加4个切片点,并使用5个结果间隔的长度作为随机数:

    enter image description here

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

答案 1 :(得分:3)

要实现真正的随机分布,每个元素都有机会为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);
}

它将随机数增加1,直到达到总和。这应该符合你的所有标准。

在考虑之后,我更喜欢以下解决方案,因为它不太可能产生平等分配:

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

答案 2 :(得分:3)

为了使您的发行版统一,您可以尝试以下方法:

  1. 生成一些随机数。
  2. 将它们标准化。
  3. 如果需要,纠正最后一个字段以获得完全预期的总和。
  4. 代码:

    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

答案 3 :(得分:1)

例如。

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

答案 4 :(得分:1)

我的方法是:

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

这是我认为可能的统一。

答案 5 :(得分:0)

创建您的第一个随机数。之后,您将num1和100之间的差值作为max def of rnd。但为了保证其总和为100,如果所有num的总和为100,则必须在最后nums检查。如果不是,则上一次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;
}

答案 6 :(得分:0)

我认为这是一个非常简单的解决方案:

console.log(dataString)

编辑: 刚试过它,对我来说很好:enter image description here

答案 7 :(得分:0)

解决方案是,需要随机的数字不是因为分布需要是随机的。数字的随机性将是其随机分布的副作用。

因此,您将从给定范围内的五个随机数开始。只要范围对于所有五个范围都相同,确切范围无关紧要,尽管更宽范围允许更多变化。我使用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的位置。

答案 8 :(得分:0)

好。由于我之前尝试过这个看似微不足道的问题而被烧毁,我决定再去一次。为什么不在生成后对所有数字进行标准化?这保证了随机性,并避免了排序中的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;
}

这也应该是最快的解决方案之一。