Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generate random numbers summing to a predefined value

Tags:

python

random

So here is the deal: I want to (for example) generate 4 pseudo-random numbers, that when added together would equal 40. How could this be dome in python? I could generate a random number 1-40, then generate another number between 1 and the remainder,etc, but then the first number would have a greater chance of "grabbing" more.

like image 845
Jack S. Avatar asked Aug 28 '10 02:08

Jack S.


People also ask

How do you generate a random number with a predefined probability distribution?

The basic idea goes like this: start from a random point x and take a random step xnew = x + delta. evaluate the desired probability distribution in the starting point p(x) and in the new one p(xnew) if the new point is more probable p(xnew)/p(x) >= 1 accept the move.

How do you find random numbers that total to a given amount or value using Excel?

You could use the RAND() function to generate N numbers (8 in your case) in column A. Then, in column B you could use the following formula B1=A1/SUM(A:A)*320 , B2=A2/SUM(A:A)*320 and so on (where 320 is the sum that you are interested into). So you can just enter =RAND() in A1, then drag it down to A8.


1 Answers

Here's the standard solution. It's similar to Laurence Gonsalves' answer, but has two advantages over that answer.

  1. It's uniform: each combination of 4 positive integers adding up to 40 is equally likely to come up with this scheme.

and

  1. it's easy to adapt to other totals (7 numbers adding up to 100, etc.)
import random  def constrained_sum_sample_pos(n, total):     """Return a randomly chosen list of n positive integers summing to total.     Each such list is equally likely to occur."""      dividers = sorted(random.sample(range(1, total), n - 1))     return [a - b for a, b in zip(dividers + [total], [0] + dividers)] 

Sample outputs:

>>> constrained_sum_sample_pos(4, 40) [4, 4, 25, 7] >>> constrained_sum_sample_pos(4, 40) [9, 6, 5, 20] >>> constrained_sum_sample_pos(4, 40) [11, 2, 15, 12] >>> constrained_sum_sample_pos(4, 40) [24, 8, 3, 5] 

Explanation: there's a one-to-one correspondence between (1) 4-tuples (a, b, c, d) of positive integers such that a + b + c + d == 40, and (2) triples of integers (e, f, g) with 0 < e < f < g < 40, and it's easy to produce the latter using random.sample. The correspondence is given by (e, f, g) = (a, a + b, a + b + c) in one direction, and (a, b, c, d) = (e, f - e, g - f, 40 - g) in the reverse direction.

If you want nonnegative integers (i.e., allowing 0) instead of positive ones, then there's an easy transformation: if (a, b, c, d) are nonnegative integers summing to 40 then (a+1, b+1, c+1, d+1) are positive integers summing to 44, and vice versa. Using this idea, we have:

def constrained_sum_sample_nonneg(n, total):     """Return a randomly chosen list of n nonnegative integers summing to total.     Each such list is equally likely to occur."""      return [x - 1 for x in constrained_sum_sample_pos(n, total + n)] 

Graphical illustration of constrained_sum_sample_pos(4, 10), thanks to @FM. (Edited slightly.)

0 1 2 3 4 5 6 7 8 9 10  # The universe. |                    |  # Place fixed dividers at 0, 10. |   |     |       |  |  # Add 4 - 1 randomly chosen dividers in [1, 9]   a    b      c    d    # Compute the 4 differences: 2 3 4 1 
like image 191
Mark Dickinson Avatar answered Sep 27 '22 20:09

Mark Dickinson