如何生成大致遵循正态分布并且加起来达到特定总数的随机数?

问题描述 投票:0回答:1

我正在尝试生成一组随机数字,这些数字加起来达到特定的总数,以及这些数字可以达到的特定最大值。

然而,我遇到的每种方法似乎都有一些缺陷,导致其无法使用。

  • 有时结果加起来并不等于正确的总数。

  • 有时随机生成每次都会产生相同的数字。

  • 某些函数会导致迭代次数过多。

我开始认为这在数学上有点不可能?我想知道是否有人可以帮我编写代码来做到这一点。 数字应遵循以下规则:

  1. 数字之和必须等于变量 t。

  2. 生成数字的最小值为1。

  3. 最大值应为变量m。

  4. 生成的数字必须尽可能接近正态分布。

  5. 正态分布必须以 1 为中心。

  6. 正态分布应该足够平坦,几乎可以得到每个数字最大的示例。

  7. 所有数字必须是整数。

举个例子,如果 t 是 30,m 是 5,那么结果将是:

1,1,1,1,1,1,1,2,2,2,2,3,3,4,5

另一个结果可能是:

1,1,1,1,1,2,2,2,3,3,4,4,5

这是我为此提供的一个函数,但它使用了我希望避免的 while 循环,因为它通常会导致太多迭代。

public static List<int> GenerateNumbers(int t, int m)
{
    double mean = 1.0;
    double variance = 10.0;
    double[] probabilities = new double[m];
    double sumProbabilities = 0.0;
 
    for (int i = 1; i <= m; i++)
    {
        double exponent = -Math.Pow(i - mean, 2) / (2 * variance);
        probabilities[i - 1] = Math.Exp(exponent);
        sumProbabilities += probabilities[i - 1];
    }
 
    for (int i = 0; i < m; i++)
    {
        probabilities[i] /= sumProbabilities;
    }
 
    double meanValue = 0.0;
    for (int i = 0; i < m; i++)
    {
        meanValue += probabilities[i] * (i + 1);
    }
    int N = (int)Math.Round(t / meanValue);
 
    int[] frequencies = new int[m];
    for (int i = 0; i < m; i++)
    {
        frequencies[i] = (int)Math.Round(probabilities[i] * N);
    }
 
    for (int i = 1; i < m; i++)
    {
        if (frequencies[i] > frequencies[i - 1])
        {
            frequencies[i] = frequencies[i - 1];
        }
    }
 
    int currentSum = 0;
    for (int i = 0; i < m; i++)
    {
        currentSum += frequencies[i] * (i + 1);
    }
 
    int maxIterations = 25000;
    int iterationCount = 0;
 
    while (currentSum != t && iterationCount < maxIterations)
    {
        iterationCount++;
        if (currentSum < t)
        {
            for (int i = 0; i < m; i++)
            {
                if (frequencies[i] < (i == 0 ? frequencies[0] : frequencies[i - 1]))
                {
                    frequencies[i]++;
                    currentSum += (i + 1);
                    break;
                }
            }
        }
        else
        {
            for (int i = m - 1; i >= 0; i--)
            {
                if (frequencies[i] > 0)
                {
                    frequencies[i]--;
                    currentSum -= (i + 1);
                    break;
                }
            }
        }
    }
 
    if (iterationCount >= maxIterations)
    {
        Console.WriteLine("Failed to adjust frequencies to match t within max iterations.");
    }
 
    List<int> numbers = new List<int>();
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < frequencies[i]; j++)
        {
            numbers.Add(i + 1);
        }
    }
 
    System.Random rand = new System.Random();
    numbers = numbers.OrderBy(x => rand.Next()).ToList();
 
    return numbers;
}

我该怎么办?

c# math random normal-distribution integer-partition
1个回答
0
投票

由于限制,相当棘手,但可以尝试这个:

  • 确定您需要多少个号码。我们称之为 N。你可能会 通过将 t 除以您期望的平均值来估计 N。
  • 从以 1 为中心的正态分布生成 N 个随机数。
  • 剪裁数字,使它们至少为 1,至多为 m。这意味着如果 小于1的数,设置为1;如果大于m,则设置为 米.
  • 调整数字总和为 t:

将数字四舍五入为整数。 计算 t 与数字之和之间的差。 如果总和小于 t,则对某些数字(最好是小于 m 的数字)加 1,直到总数与 t 匹配。 如果总和大于 t,则从一些数字(大于 1 的数字)中减去 1,直到总和与 t 匹配。

public static List<int> GenerateNumbers(int t, int m)
{
    double mean = 1.0;
    double stdDev = 1.0;
    int N = (int)Math.Round(t / mean);

    List<double> numbers = new List<double>();
    Random rand = new Random();

    for (int i = 0; i < N; i++)
    {
        double num = NextGaussian(rand, mean, stdDev);
        if (num < 1) num = 1;
        if (num > m) num = m;
        numbers.Add(num);
    }

    List<int> intNumbers = numbers.Select(x => (int)Math.Round(x)).ToList();
    int currentSum = intNumbers.Sum();
    int iterationCount = 0;
    int maxIterations = 10000;

    while (currentSum != t && iterationCount < maxIterations)
    {
        iterationCount++;
        if (currentSum < t)
        {
            for (int i = 0; i < intNumbers.Count; i++)
            {
                if (intNumbers[i] < m)
                {
                    intNumbers[i]++;
                    currentSum++;
                    break;
                }
            }
        }
        else if (currentSum > t)
        {
            for (int i = 0; i < intNumbers.Count; i++)
            {
                if (intNumbers[i] > 1)
                {
                    intNumbers[i]--;
                    currentSum--;
                    break;
                }
            }
        }
    }

    if (iterationCount >= maxIterations)
    {
        Console.WriteLine("Failed to adjust numbers to match t within max iterations.");
    }

    intNumbers = intNumbers.OrderBy(x => rand.Next()).ToList();
    return intNumbers;
}

public static double NextGaussian(Random rand, double mean, double stdDev)
{
    double u1 = rand.NextDouble();
    double u2 = rand.NextDouble();
    double randStdNormal = Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Cos(2.0 * Math.PI * u2);
    double randNormal = mean + stdDev * randStdNormal;
    return randNormal;
}

© www.soinside.com 2019 - 2024. All rights reserved.