簡體   English   中英

生成具有特定總和的隨機整數

[英]generate random integers with a specific sum

我有 5 個字段,我希望它們都有一個介於 0 和 100 之間的生成數字。但是,5 個字段的總和應該是 100。

當我想為一個字段提供一個隨機數時,我會執行以下操作:

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

但是對於需要總和為 100 的多個字段,我應該如何做到這一點?

您可以使用以下方法:

  1. 在 [0, 100] 中生成 4 個隨機整數
  2. 對它們進行排序,讓我們將排序后的值表示為 0 ≤ x 1 ≤ x 2 ≤ x 3 ≤ x 4 ≤ 100
  3. 使用以下 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

它基本上對應於在 [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];
}

為了使您的分布均勻,您可以嘗試以下方法:

  1. 生成一些隨機數。
  2. 使它們正常化。
  3. 如果需要,請更正最后一個字段以獲得准確的預期總和。

代碼:

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

我認為這是一個非常簡單的解決方案:

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

編輯:剛剛測試過,對我來說很好用: 在此處輸入圖片說明

解決方案是,不是數字需要隨機,而是分布需要隨機。 數字的隨機性將是其隨機分布的副作用。

因此,您將從給定范圍內的五個隨機數開始。 只要所有五個的范圍相同,確切的范圍就無關緊要,盡管更寬的范圍允許更多的變化。 我會使用 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM