2

I have the following list:

Sum=[54,1536,36,14,9,360]

I need to generate 4 other lists, where each list will consist of 6 random numbers starting from 0, and the numbers will add upto the values in sum. For eg;

l1=[a,b,c,d,e,f] where a+b+c+d+e+f=54
l2=[g,h,i,j,k,l] where g+h+i+j+k+l=1536

and so on upto l6. And I need to do this in python. Can it be done?

IndigoChild
  • 842
  • 3
  • 11
  • 29
  • Of course it can be done, but first you have to decide how you plan to choose the random numbers. Can I just do `[random.randrange(2) for _ in range(5)]` and then add `1536 - sum(those five numbers)` for my last random number? If not, why not? What's the rule here? – abarnert Apr 05 '18 at 07:45
  • 1
    Is this a homework question? Please show us what you have tried so far in terms of code. – KuboAndTwoStrings Apr 05 '18 at 07:45
  • @abertnet, no such rule, just that the numbers have to be random – IndigoChild Apr 05 '18 at 07:49
  • Is there any constraint like number should not be repeated, or something like that? – Rao Sahab Apr 05 '18 at 07:53

2 Answers2

6

Generating a list of random numbers that sum to a certain integer is a very difficult task. Keeping track of the remaining quantity and generating items sequentially with the remaining available quantity results in a non-uniform distribution, where the first numbers in the series are generally much larger than the others. On top of that, the last one will always be different from zero because the previous items in the list will never sum up to the desired total (random generators usually use open intervals in the maximum). Shuffling the list after generation might help a bit but won't generally give good results either.

A solution could be to generate random numbers and then normalize the result, eventually rounding it if you need them to be integers.

import numpy as np
totals = np.array([54,1536,36,14])  # don't use Sum because sum is a reserved keyword and it's confusing

a = np.random.random((6, 4))  # create random numbers
a = a/np.sum(a, axis=0) * totals  # force them to sum to totals

# Ignore the following if you don't need integers
a = np.round(a)  # transform them into integers
remainings = totals - np.sum(a, axis=0)  # check if there are corrections to be done
for j, r in enumerate(remainings):  # implement the correction
    step = 1 if r > 0 else -1
    while r != 0:
        i = np.random.randint(6)
        if a[i,j] + step >= 0:
            a[i, j] += step
            r -= step

Each column of a represents one of the lists you want. Hope this helps.

Gianluca Micchi
  • 1,584
  • 15
  • 32
  • A [multinomial distribution](https://stackoverflow.com/a/34998514/752843) is a superior way to do this. – Richard May 31 '21 at 17:15
5

This might not be the most efficient way but it will work

totals = [54, 1536, 36, 14]

nums = []
x = np.random.randint(0, i, size=(6,))
for i in totals:
    while sum(x) != i: x = np.random.randint(0, i, size=(6,))
    nums.append(x)
print(nums)

[array([ 3, 19, 21, 11, 0, 0]), array([111, 155, 224, 511, 457, 78]), array([ 8, 5, 4, 12, 2, 5]), array([3, 1, 3, 2, 1, 4])]


This is a way more efficient way to do this

totals = [54,1536,36,14,9,360, 0]

nums = []
for i in totals:
    if i == 0: 
        nums.append([0 for i in range(6)])
        continue
    total = i
    temp = []
    for i in range(5):
        val = np.random.randint(0, total)
        temp.append(val)
        total -= val
    temp.append(total)
    nums.append(temp)

print(nums)

[[22, 4, 16, 0, 2, 10], [775, 49, 255, 112, 185, 160], [2, 10, 18, 2, 0, 4], [10, 2, 1, 0, 0, 1], [8, 0, 0, 0, 0, 1], [330, 26, 1, 0, 2, 1], [0, 0, 0, 0, 0, 0]]

JahKnows
  • 2,618
  • 3
  • 22
  • 37