I want to generate 3 random numbers in the range 0 to 9
in a row which should sum up to a given fixed number. For example, for the given fixed sum 15, one possible solution would be (3, 8, 4)
. How can I do this ? Thanks.

- 25,517
- 12
- 101
- 143

- 61
- 1
- 6
-
3can the numbers be repeated? – GorvGoyl Nov 28 '16 at 05:55
-
2Generate first random number in range `(0, 9)`, second in `(0, min(15 - first_no, 9))`, and the third would be `15 - first_no - second_no`, which is also random. – Jarvis Nov 28 '16 at 05:58
-
Loop until all three are not different, won't take much time. @n.m. – Jarvis Nov 28 '16 at 13:22
-
@Jarvis I misread the problem, I thought the sum should be 9. For the correct sum==15, always generate (5,5,5). Or(4,5,6). Or perhaps randomly and uniformly select between (5,6,4) and (4,5,6). Wonderful! the numbers are random and different! You could say that they are not "very" random, which would be in some sense true, but so are numbers generated by your method. – n. m. could be an AI Nov 28 '16 at 13:36
-
+1 This problem turned out to be more interesting, and non-obvious, than it initially appeared and as simulations of several answers show. – doug Nov 29 '16 at 21:56
3 Answers
We can:
- First generate random float number
a,b,c
between0
and1
- Get
sum
ofa,b,c
- Divide
a,b,c
bysum
- Multiple
a,b,c
by given desired sum integer, and then rounda,b,c
to the nearest integer - See if
sum(a, b, c) == given integer ? get result : try again
Check this demo:
Using boost random generator:
#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;
}
Test efficiency:
-
If you simulate this, aI and bI==6 has a probability of 16.2% but cI==6 has a probability of 17.5%. Something is wrong with this algorithm and this error seems considerably larger than what can be explained by rand()'s poor qualities. – doug Nov 29 '16 at 21:22
-
Thanks @doug. About random number generator , I use rand() because of its simplicity.But demo is just demo i think, as you mentioned, basic rand() with c++ has poor qualities, yes. So mostly I use random generator in boost, which is much much more efficient. And about this question, solution is more important than CODE. – xtluo Nov 30 '16 at 00:25
-
Rand() isn't the issue. It has problems, but, they are often overstated and here they are not a factor. There is some other bias in the algorithm since the frequency distribution of the three variables ,aI, bI, and cI should be identical. Since cI has about 10% more chance of being a 6 than does aI, or bI the algorithm does not randomly select the three variables. – doug Nov 30 '16 at 01:41
-
@doug, Where is the problem please? I tested after using `boost::random::uniform_real_distribution<>(0, 1)`, and here is the outcome after `100000000` running loops: [1]: https://i.stack.imgur.com/OmFXj.png – xtluo Nov 30 '16 at 02:09
-
You changed the code from generating a random number from 0 to 8/9. and it now generates from 0 to 1. So your asymmetric distribution is fixed. Now, the question is whether the random numbers selected are biased. Each number from 0 to 9 should have an equal probability of being selected. A set of these three numbers is then selected if summing to 15 otherwise discarded. That would be unbiased. – doug Nov 30 '16 at 03:26
-
Here's why this answer is wrong. Given that the first number is 0. There are only 4 possible values for the second number. 9,8,7, and 6. the third number is selected accordingly to sum to 15. Now, if the first number is 1, there are 5 possible values: 9,8,7,6, and 5. Therefore, the ratio of the probability of a==0 to a==1 should be 5/4 or 1.25. It is not but is on Kay's algorithm. – doug Nov 30 '16 at 03:36
-
1@doug, Yes, now I admit that the former demo has problems, and thanks for doing this job cause without it I will never notice. But the solution part of this answer, specifically the first step,as I have mentioned is generate random number from 0 to 1, but what I did was using rand() % Integer, which cause the problem. But the user may pay more attention to the solution part I think. Thanks for this wonderful work. – xtluo Nov 30 '16 at 03:36
-
Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/129390/discussion-between-doug-and-xiaotao-luo). – doug Nov 30 '16 at 03:44
When dealing with random variables it's a really good idea to check the work.
I simulated both answers. Xiaotao's not only has a different distribution, but different distribution frequencies. aI and bI have the same distribution but cI is significantly different. All three should have identical distributions.
Also, Kay's solution has the proper distribution as P(a)==1 s/b 1.25 times P(a)==1.
This is a deterministic solution and it has exactly the same statistics as Kay's
Further, the frequency of occurrence of each number looking at it purely from a probability POV from 0 to 9 is 4/73, 5/73, 6/73, 7/73, 8/73, 9/73, 10/73, 9/73, 8/73 and 7/73
A vector of all possible number sequences that sums to 15 is created. Then one element is chosen randomly. Each number set has an identical probability of being selected
#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
*/
Xiaotao's answer simulation: Note the different distribution of cI v aI and 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's answer does not exhibit this error. Here's that simulation:
#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
*/
Here's a tutorial how to generate random numbers in C++11: http://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution
The easiest solution is to try it until you find a match:
#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;
}
You'll need about nine iterations to have a 50/50 chance that you'll throw a 15:
- P(3d10 = 18) ≈ 1/14 (+3 to account for the range shift)
- (13/14)^n < 0.5 → n ≈ 9.4

- 25,517
- 12
- 101
- 143
-
2
-
This approach is not very efficient. Xiaotao Luo's solution simply normalizes three uniform random deviates by their sum and is much more efficient. His solution has no bias since each uniform random deviate is independent of the others and the discretization bias is equal in his rounding step. – Special Sauce Nov 28 '16 at 07:23
-
1@SpecialSauce, don't micro-optimize before measuring. Do you really think this will be the crucial bottle neck in OP's code? PS: I upvoted their answer nevertheless ... – Kijewski Nov 28 '16 at 07:27
-
Kay, DICE_SIDES is a misnomer as it is one less. Also, the lower limit s/b 0, not 1. Otherwise I still believe it the most clearly statistically unbiased answer as `rand()%n` is to be avoided for multiple reasons. – doug Nov 28 '16 at 07:48
-
@Special Sauce I simulated both answers. Xiaotao's not only has a different distribution, but different distribution frequencies aI and bI have the same distribution but cI is significantly different. When dealing with random variables it's a really good idea to check the work. – doug Nov 29 '16 at 21:31
-
@doug Yes, I've confirmed that Kay's answer is the actually the best. Xiaotao's answer is four times faster but has a significantly incorrect distribution. I would delete my earlier comment if possible, but it's past the time limit. – Special Sauce Nov 30 '16 at 02:38
-
@SpecialSauce, if speed matters then I'd simply fill a vector with all passing triples, and select a random index. Should be much faster for repeated calls. – Kijewski Nov 30 '16 at 11:44
-
Oh, I just saw that @doug already posted the solution with a pre-filled vector. :) – Kijewski Nov 30 '16 at 14:11