究竟如何使随机数加起来等于声明的总和?



我很困惑我如何让 9 个随机数添加到用户可能输入的任何数字中。假设用户输入"200"作为数字,我该如何使它能够得到 9 个随机数加起来正好是 200?

显然,下面的代码并没有按照我想要的方式工作,因为它实际上只是 9 个随机数,加起来不是一个特定的数字。我只是不知道如何正确构建它。

public static void RandomStats()
{
Random RandomClass = new Random();
int[] intRandomStats = {
RandomClass.Next(0, 101), 
RandomClass.Next(0, 101), 
RandomClass.Next(0, 101), 
RandomClass.Next(0, 101), 
RandomClass.Next(0, 101), 
RandomClass.Next(0, 101), 
RandomClass.Next(0, 101), 
RandomClass.Next(0, 101), 
RandomClass.Next(0, 101)
};
// ...
}

我认为你的问题更多的是数学问题而不是代码问题。

听起来您正在寻找的是多项式分布。生成这样的分布的一种非常幼稚的方法是将其视为掷骰子。想象一下,你有 200 个骰子,每个骰子有 9 个边。全部滚动。数一数所有以 1 面朝上结束的那些,这将是您的第一个数字。然后数一下最终以 2 面朝上的那些,这将是你的第二个数字。继续,直到计算所有骰子。有 200 个骰子,因此计数的总和将是 200。每个计数将具有相同的概率分布。

上面的伪算法不会那么有效,基本上是遍历每个骰子。也许效率在您的情况下并不那么重要,(200 是一个小数字,所以没关系)所以请随意编写此算法。

如果效率很重要,请尝试在库中查找现有实现。也许MathNet库会为你工作?如果您有兴趣,请参阅Sample方法。至少,既然您知道了术语"多项式分布",那么在谷歌上搜索灵感应该会更容易一些。

想象一下,你有一袋200个硬币。您需要将这些硬币分成 9 个随机堆。一堆可以有袋子里所有的硬币,袋子里的一些硬币,或者没有硬币。

每次你为一堆分配硬币时,袋子里的硬币数量就会变小(除非你抓住了0个硬币,在这种情况下它保持不变)。此新计数将引用用于下一个桩分配。

var rand = new Random();
var amount = 200;
var targetOutputValueCount = 9;
var outputValues = new List<int>();
for (int i = 1; i < targetOutputValueCount; i++) // 1 less than all groups
{
var groupAmount = rand.Next(0, amount);
amount -= groupAmount;
outputValues.Add(groupAmount);
}
// for the last group, it's whatever is left over
outputValues.Add(amount);
foreach (var outputValue in outputValues)
{
Console.WriteLine(outputValue);
}

示例输出为

148 28 0 2 12 2 1 6 1

这种方法的优点是始终保证具有正输出数字。

只需生成八个数字并计算第九个数字作为缺失的差值:

int theSum = 200;
var randomNumbers = new int[9];
for(int i = 0; i < 8; i)
{
randomNumbers[i] = random.Next(0, theSum);
}
randomNumbers[8] = theSum - randomNumbers.Sum();

到目前为止提出的方法是可行的,但往往会产生扭曲的结果。例如,强制最后一个数字给出正确的总和可能会给你一个与其他值相差很远的值(并且可能是负数,这在某些情况下可能是个问题)。计算从零到剩余总和范围内的随机值将为您提供一系列快速接近零的数字。

相反,要生成从0totaln随机数,我建议在0total(含)的范围内选择n-1随机值。将这些值中的每一个视为一副total牌中书签的位置。如果在这些书签处将一副牌分成n堆,那么每堆中的牌数量将为您提供一组均匀分布的值,总和为total.

这里有一些代码来说明这个想法(在 C 中,抱歉):

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int cmp(const void *a, const void *b) {
return *((int*)a) - *((int*)b);
}
int main(int argc, char *argv[]) {
int nterms, total, x, i, checksum;
int *array;

srand(time(0));

if (argc != 3) return puts("Require 2 arguments: <nterms> and <total>");
nterms = atoi(argv[1]);     /* N.B. Input value checks omitted.      */
total = atoi(argv[2]);      /* Avoid large or negative values!       */

/* We want to generate nterms intervals across the range from 0 to   */
/* total (inclusive), so we need an array of nterms+1 values to mark */
/* the start and end of each interval.                               */
array = malloc((nterms+1) * sizeof(int));

/* The first and last items in this list must be zero and total (to  */
/* ensure that the list of numbers add up to the correct amount)     */
array[0] = 0;
array[nterms] = total;

/* Fill the rest of the array with random values from 0 to total.    */
for (i=1; i<nterms; i++) {
array[i] = rand() % (total+1);
}
/* Sort these values in ascending order.                             */
qsort(array, nterms+1, sizeof(int), cmp);

/* Our list of random numbers can now be calculated from the         */
/* difference between each pair of values in this list.              */
printf("Numbers:");
for (i=checksum=0; i<nterms; i++) {
x = array[i+1] - array[i];
checksum += x;
printf(" %d", x);
}
printf("nTotal:   %dn", checksum);
return 0;
}

您还可以重复生成 9 个随机数,直到它们的总和达到所需的总和。随机数的最佳范围是目标总和 (200) 除以随机数 (9) 的两倍,因为它们的平均值将接近 200/9。

var random = new Random();
var randomNumbers = new int[9];
int input = 200;
int optimalRange = 2 * input / randomNumbers.Length;
int iterations = 0;
do {
for (int i = 0; i < randomNumbers.Length; i++) {
randomNumbers[i] = random.Next(optimalRange);
}
iterations++;
} while (randomNumbers.Sum() != input);
Console.WriteLine($"iterations = {iterations}");
Console.WriteLine($"numbers = {String.Join(", ", randomNumbers)}");

示例输出:

iterations = 113
numbers = 2, 24, 39, 28, 6, 28, 34, 17, 22

在我重复一百万次的测试中,我得到了这些#迭代:

  • 平均值 = 98.4
  • 最小值 = 1
  • 最大值 = 1366

在 10170 个案例中,我在第一次迭代时就做对了。

感谢您的提示@DrPhil。下面是使用 linq 的方法。

Random rnd = new Random();
int[] dice = new int[200];
var sidesUp = dice.Select(x => rnd.Next(1, 10));
List<int> randomNumbers = sidesUp.GroupBy(p => p).Select(x => x.Count()).ToList();

我的方法与其他人没有太大区别,但这里是:

  • 首先声明一个带有值的整数;
  • 减去用户输入;
  • 使用 for 循环迭代 8 次;
  • 每次从剩余的总迭代和剩余迭代中获取除法;

选项 1:

  • 如果剩余总数小于最大值,则使用先前的除法作为最大随机值;

选项 2:

  • 使用先前的除法作为最大随机值;

  • 第 9 个数字是剩余的总数

//run: RandomStats(0,101,200) for your particular example.
public static void RandomStats(int min, int max, int total)
{
Random randomClass = new Random();
int[] randomStats = new int[9];
int value = 0;
int totalValue = total;
bool parsed = false;
while (!parsed)
{
Console.WriteLine("Please enter a number:");
if (int.TryParse(Console.ReadLine(), out value))
{
parsed = true;
totalValue -= value;
for (int i = 0; i < randomStats.Length-1; i++)
{
//option 1
int remainMax = (int) Math.Floor((float) totalValue / (randomStats.Length - 1 - i));
int randomValue = randomClass.Next(min, totalValue < max ? remainMax : max);  
//option 2
//max = (int) Math.Floor((float) totalValue / (randomStats.Length - 1 - i));
//int randomValue = randomClass.Next(min, max); 
totalValue -= randomValue;
randomStats[i] = randomValue;
}
randomStats[8] = totalValue;
}
else
{
Console.WriteLine("Not a valid input");
}
}
Console.WriteLine($"min value: {min}tmax value: {max}ttotal value: {total}");
int testValue = value;
Console.WriteLine("Input value - " + value);
for (int i = 0; i < randomStats.Length; i++)
{
testValue += randomStats[i];
int randomIndex = i + 1;
Console.WriteLine(randomIndex + " Random value - " + randomStats[i]);
}
Console.WriteLine("test value - " + testValue);
Console.ReadKey();
}

选项 1 输出:

min value: 0    max value: 101  total value: 200
Input value - 10
1 Random value - 13
2 Random value - 2
3 Random value - 95
4 Random value - 10
5 Random value - 0
6 Random value - 15
7 Random value - 10
8 Random value - 10
9 Random value - 35
test value - 200

选项 2 输出:

min value: 0    max value: 67*   total value: 200
Input value - 10
1 Random value - 1
2 Random value - 16
3 Random value - 5
4 Random value - 29
5 Random value - 17
6 Random value - 7
7 Random value - 48
8 Random value - 19
9 Random value - 48
test value - 200

*这仍然是 101,但由于我将剩余总数除以迭代而变得无关紧要

最新更新