【问题标题】:How to generate random values with fixed sum in C++如何在 C++ 中生成具有固定总和的随机值
【发布时间】:2016-11-28 05:54:07
【问题描述】:

我想连续生成0 to 9 范围内的 3 个随机数,它们的总和应为给定的固定数。例如,对于给定的固定总和 15,一种可能的解决方案是(3, 8, 4)。我怎样才能做到这一点 ?谢谢。

【问题讨论】:

  • 数字可以重复吗?
  • (0, 9)范围内生成第一个随机数,在(0, min(15 - first_no, 9))范围内生成第二个随机数,第三个是15 - first_no - second_no,这也是随机的。
  • 循环直到所有三个都没有不同,不会花费太多时间。 @n.m.
  • @Jarvis 我误读了问题,我认为总和应该是 9。对于正确的总和 ==15,始终生成 (5,5,5)。或(4,5,6)。或者可能在 (5,6,4) 和 (4,5,6) 之间随机均匀地选择。精彩的!数字是随机的和不同的!您可以说它们不是“非常”随机的,这在某种意义上是正确的,但您的方法生成的数字也是如此。
  • +1 事实证明,这个问题比最初出现的更有趣,也更不明显,并且模拟了几个答案。

标签: algorithm random


【解决方案1】:

我们可以:

  1. 首先在01之间生成随机浮点数a,b,c
  2. 获取sum of a,b,c
  3. a,b,c 除以sum
  4. a,b,c乘以给定的所需总和整数,然后将a,b,c四舍五入到最接近的整数
  5. 看看有没有sum(a, b, c) == given integer ? get result : try again

查看此演示:

使用 boost 随机生成器:

#include <iostream>
#include <time.h>
#include <iomanip>
#include <boost/random.hpp>

int main()
{
    static time_t seed = time(0);
    boost::random::mt19937 RandomNumGen(seed++);
    boost::random::uniform_real_distribution<> Range(0, 1);

    int Desired_Integer = 15;
    int Rand_Max = 9;
    int Max_Itr = 100000000;
    int Count = 0;
    int SumABC[3][10] = { 0 };
    float bias = 0.5;

    float a, b, c;
    for (int Loop = 1; Loop <= Max_Itr; ++Loop)
    {
        a = Range(RandomNumGen);
        b = Range(RandomNumGen);
        c = Range(RandomNumGen);

        float Sum = a + b + c;
        a = a / Sum;
        b = b / Sum;
        c = c / Sum;

        //Round to the nearest integer;
        int aI = static_cast<int>(a * Desired_Integer + bias), bI = static_cast<int>(b * Desired_Integer + bias), cI = static_cast<int>(c * Desired_Integer + bias);
        if (aI <= Rand_Max && bI <= Rand_Max && cI <= Rand_Max && aI + bI + cI == Desired_Integer)
        {
            SumABC[0][aI]++;
            SumABC[1][bI]++;
            SumABC[2][cI]++;

            Count++;
        }
    }

    int PaddingWidth = 10;
    std::cout << "\n" << Count << " in " << Max_Itr << " loops get desired outcome. \nDistribution of a,b,c: \n";
    std::cout << "Number" << std::setw(PaddingWidth) << "a" << std::setw(PaddingWidth) << "b" << std::setw(PaddingWidth) << "c" << std::endl;
    for (int i = 0; i < 10; i++)
    {
        std::cout 
            << i << std::setw(PaddingWidth + 8) 
            << std::setprecision(4) << 100.0 * SumABC[0][i] / (float)Count << std::setw(PaddingWidth) 
            << std::setprecision(4) << 100.0 * SumABC[1][i] / (float)Count << std::setw(PaddingWidth)
            << std::setprecision(4) << 100.0 * SumABC[2][i] / (float)Count << std::endl;
    }

    std::cout << "\n\n";

    system("pause");
    return 0;
}

测试效率:

【讨论】:

  • 如果模拟这个,aI 和 bI==6 的概率为 16.2%,而 cI==6 的概率为 17.5%。这个算法出了点问题,这个错误似乎比 rand() 的劣质所解释的要大得多。
  • 谢谢@doug。关于随机数生成器,我使用 rand() 因为它的简单性。但我认为演示只是演示,正如你提到的,带有 c++ 的基本 rand() 质量很差,是的。所以大多数情况下我在 boost 中使用随机生成器,这要高效得多。而关于这个问题,解决方案比代码更重要。
  • Rand() 不是问题。它有问题,但是,它们往往被夸大了,在这里它们不是一个因素。由于三个变量 aI、bI 和 cI 的频率分布应该相同,因此算法中存在一些其他偏差。由于 cI 成为 6 的可能性比 aI 或 bI 高 10%,因此该算法不会随机选择这三个变量。
  • @doug,请问问题出在哪里?我在使用boost::random::uniform_real_distribution&lt;&gt;(0, 1) 之后进行了测试,这是100000000 运行循环后的结果:[1]:i.stack.imgur.com/OmFXj.png
  • @doug,是的,现在我承认以前的演示存在问题,感谢您完成这项工作,因为没有它我永远不会注意到。但是这个答案的解决方案部分,特别是第一步,正如我所提到的,是生成从 0 到 1 的随机数,但我所做的是使用 rand() % Integer,这会导致问题。但用户可能会更关注我认为的解决方案部分。感谢您的出色工作。
【解决方案2】:

在处理随机变量时,检查工作是一个非常好的主意。

我模拟了两个答案。小淘的不仅分布不同,而且分布频率也不同。 aI 和 bI 具有相同的分布,但 cI 显着不同。这三个应该具有相同的分布。

此外,Kay 的解具有适当的分布,即 P(a)==1 s/b 1.25 乘以 P(a)==1。

这是一个确定性解决方案,它与 Kay 的统计数据完全相同

此外,纯粹从 0 到 9 的概率 POV 来看每个数字的出现频率是 4/73、5/73、6/73、7/73、8/73、9/73、10 /73、9/73、8/73 和 7/73

创建所有可能的数字序列的向量,总和为 15。然后随机选择一个元素。每个数字集都有相同的被选中概率

#include <algorithm>
#include <array>
#include <iostream>
#include <numeric>
#include <random>

using namespace std;

// Your constants:
static constexpr unsigned DICE_COUNT = 3;
static constexpr unsigned DICE_SIDES = 10;
static constexpr unsigned DESIRED_NUMBER = 15;

int main() {
    // Initialize your PRNG:

    vector<array<int, 3>> allLegalNumbers;
    for (int i=0; i <= 9; i++)      // go through all possible sets of 3 numbers from 0 to 9
        for (int ii = 0; ii < DICE_SIDES; ii++)
            for (int iii = 0; iii < DICE_SIDES; iii++)
                if (i + ii + iii == DESIRED_NUMBER) // keep the ones that add up to 15
                    allLegalNumbers.push_back(array<int, 3> {i, ii, iii});

    random_device rd;
    mt19937 generator(rd());
    uniform_int_distribution<unsigned> distribution(0, allLegalNumbers.size() - 1);

    int sum[3][DICE_SIDES]{};
    int sum_count = 0;
    for (int Loop = 1; Loop < 100000000; ++Loop)
    {
        auto index = distribution(generator);
        sum[0][allLegalNumbers[index][0]]++;
        sum[1][allLegalNumbers[index][1]]++;
        sum[2][allLegalNumbers[index][2]]++;
        sum_count++;
    }
    for (int i = 0; i < DICE_SIDES; i++)
        printf("Percent of aI==%d:%5.2f   bI==%d:%5.2f   cI==%d:%5.2f\n",
            i, 100.0*sum[0][i] / sum_count,
            i, 100.0*sum[1][i] / sum_count,
            i, 100.0*sum[2][i] / sum_count);
    return 0;
}
/* Results:
Percent of aI==0: 5.48   bI==0: 5.48   cI==0: 5.48
Percent of aI==1: 6.85   bI==1: 6.85   cI==1: 6.85
Percent of aI==2: 8.22   bI==2: 8.22   cI==2: 8.22
Percent of aI==3: 9.59   bI==3: 9.59   cI==3: 9.59
Percent of aI==4:10.96   bI==4:10.96   cI==4:10.96
Percent of aI==5:12.33   bI==5:12.33   cI==5:12.34
Percent of aI==6:13.69   bI==6:13.70   cI==6:13.70
Percent of aI==7:12.34   bI==7:12.33   cI==7:12.33
Percent of aI==8:10.96   bI==8:10.96   cI==8:10.95
Percent of aI==9: 9.59   bI==9: 9.59   cI==9: 9.58
*/

小涛的答案模拟:注意cI v aI和bI的不同分布

#include <iostream>
int main()
{
    int SumI = 15;
    int Rand_Max = 9;
    float a, b, c;
    int sum[3][10]{};
    int sum_count = 0;

    for (int Loop = 1; Loop < 100000000; ++Loop)
    {

        a = static_cast<float>(rand() % Rand_Max) / static_cast<float>(Rand_Max);
        b = static_cast<float>(rand() % Rand_Max) / static_cast<float>(Rand_Max);
        c = static_cast<float>(rand() % Rand_Max) / static_cast<float>(Rand_Max);

        float Sum = a + b + c;
        a = a / Sum;
        b = b / Sum;
        c = c / Sum;

        //Round to the nearest integer;
        int aI = static_cast<int>(a * SumI + 0.5), bI = static_cast<int>(b * SumI + 0.5), cI = static_cast<int>(c * SumI + 0.5);
        if (aI <= Rand_Max && bI <= Rand_Max && cI <= Rand_Max && aI + bI + cI == SumI)
        {
            sum[0][aI]++;
            sum[1][bI]++;
            sum[2][cI]++;
            sum_count++;
        }
    }
    for (int i = 0; i < 10; i++)
        printf("Percent of aI==%d:%5.2f   bI==%d:%5.2f   cI==%d:%5.2f\n",
            i, 100.0*sum[0][i] / sum_count,
            i, 100.0*sum[1][i] / sum_count,
            i, 100.0*sum[2][i] / sum_count);
    return 0;
}
/* Results:
Percent of aI==0: 5.84   bI==0: 5.83   cI==0: 5.84
Percent of aI==1: 5.30   bI==1: 5.31   cI==1: 5.31
Percent of aI==2: 7.43   bI==2: 7.43   cI==2: 6.90
Percent of aI==3: 9.55   bI==3: 9.54   cI==3: 9.28
Percent of aI==4:10.61   bI==4:10.61   cI==4:10.60
Percent of aI==5:15.64   bI==5:15.66   cI==5:15.39
Percent of aI==6:16.18   bI==6:16.18   cI==6:17.51
Percent of aI==7:11.41   bI==7:11.40   cI==7:10.88
Percent of aI==8: 9.82   bI==8: 9.81   cI==8:10.08
Percent of aI==9: 8.22   bI==9: 8.22   cI==9: 8.22
*/

Kay 的回答没有出现这个错误。这是模拟:

#include <algorithm>
#include <array>
#include <iostream>
#include <numeric>
#include <random>

// Don't use "using namespace" in production.
// I only use it to avoid the horizontal scrollbar.
using namespace std;

// Your constants:
static constexpr unsigned DICE_COUNT = 3;
static constexpr unsigned DICE_SIDES = 10;
static constexpr unsigned DESIRED_NUMBER = 15;

int main() {
    // Initialize your PRNG:
    random_device rd;
    mt19937 generator(rd());
    uniform_int_distribution<unsigned> distribution(0, DICE_SIDES - 1);

    int sum[3][10]{};
    int sum_count = 0;
    for (int Loop = 1; Loop < 10000000; ++Loop)
    {

        // Fill the array with three random numbers until you have a match:
        array<unsigned, DICE_COUNT> values = { 0 };
        while (accumulate(begin(values), end(values), 0) != DESIRED_NUMBER) {
            for_each(begin(values), end(values), [&](unsigned &v) {
                v = distribution(generator);
                //v = rand() % DICE_SIDES;  // substitute this to use rand()
            });
        }
        sum[0][values[0]]++;
        sum[1][values[1]]++;
        sum[2][values[2]]++;
        sum_count++;
    }
    for (int i = 0; i < 10; i++)
        printf("Percent of aI==%d:%5.2f   bI==%d:%5.2f   cI==%d:%5.2f\n",
            i, 100.0*sum[0][i] / sum_count,
            i, 100.0*sum[1][i] / sum_count,
            i, 100.0*sum[2][i] / sum_count);
    return 0;
}
/* Results:
Percent of aI==0: 5.48   bI==0: 5.48   cI==0: 5.47
Percent of aI==1: 6.85   bI==1: 6.85   cI==1: 6.85
Percent of aI==2: 8.22   bI==2: 8.19   cI==2: 8.22
Percent of aI==3: 9.60   bI==3: 9.59   cI==3: 9.60
Percent of aI==4:10.97   bI==4:10.96   cI==4:10.99
Percent of aI==5:12.34   bI==5:12.32   cI==5:12.32
Percent of aI==6:13.69   bI==6:13.70   cI==6:13.71
Percent of aI==7:12.31   bI==7:12.34   cI==7:12.30
Percent of aI==8:10.95   bI==8:10.96   cI==8:10.95
Percent of aI==9: 9.60   bI==9: 9.60   cI==9: 9.59
*/

【讨论】:

    【解决方案3】:

    这里有一个如何在 C++11 中生成随机数的教程:http://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution

    最简单的解决方案是尝试直到找到匹配项:

    #include <algorithm>
    #include <array>
    #include <iostream>
    #include <numeric>
    #include <random>
    
    // Don't use "using namespace" in production.
    // I only use it to avoid the horizontal scrollbar.
    using namespace std;
    
    // Your constants:
    static constexpr unsigned DICE_COUNT = 3;
    static constexpr unsigned DICE_SIDES = 10;
    static constexpr unsigned DESIRED_NUMBER = 15;
    
    int main() {
        // Initialize your PRNG:
        random_device rd;
        mt19937 generator(rd());
        uniform_int_distribution<unsigned> distribution(0, DICE_SIDES - 1);
    
        // Fill the array with three random numbers until you have a match:
        array<unsigned, DICE_COUNT> values = { 0 };
        while (accumulate(begin(values), end(values), 0) != DESIRED_NUMBER) {
            for_each(begin(values), end(values), [&](unsigned &v) {
                v = distribution(generator);
            });
        }
    
        // Print the result:
        for_each(begin(values), end(values), [&](unsigned &v) {
            cout << v << ' ';
        });
        cout << endl;
    
        return 0;
    }
    

    您需要大约 9 次迭代才能有 50/50 的机会抛出 15:

    【讨论】:

    • 这种方法显然不会引入任何偏见。
    • 这种方法效率不高。 Xiaotao Luo 的解决方案简单地将三个均匀随机偏差按它们的和归一化,并且效率更高。他的解决方案没有偏差,因为每个均匀随机偏差都独立于其他偏差,并且离散化偏差在他的舍入步骤中是相等的。
    • @SpecialSauce,测量前不要进行微优化。你真的认为这将是 OP 代码中的关键瓶颈吗? PS:尽管如此,我还是赞成他们的回答...
    • Kay,DICE_SIDES 用词不当,因为它少了一个。此外,下限 s/b 为 0,而不是 1。否则,我仍然认为这是最明显的统计上无偏见的答案,因为 rand()%n 是要避免的,原因有很多。
    • @Special Sauce 我模拟了两个答案。小淘的不仅分布不同,而且不同的分布频率aI和bI分布相同但cI差异显着。在处理随机变量时,检查工作是一个非常好的主意。
    猜你喜欢
    • 2014-07-25
    • 1970-01-01
    • 2013-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-06
    • 1970-01-01
    相关资源
    最近更新 更多