简体   繁体   中英

Generate list of random number with the sum divisible by n

My concern was what is a good way to generate a list of 10 - 15 numbers that the sum of them always divisible by n.

The output should be

-8378302799

Here what I got so far, but it not a working script yet.

import random
import numpy as np

def get_num(x, y, n):
    return [random.choice(range(x, y, n)) if x % n != 0 else random.choice(range(x - (x % n) + n, y, n)) for x in x]


def get_list():
    numb = get_num(0, 9, 2)
    return ''.join(np.random.multinomial(numb, [1/10], size=1))

while True:
    amount = int(input("How many Do you want to generate?" + "\n"))
    for i in range(1,amount):
        get_list()

@Samer Ayoub, answer was what I needed, just added the sep='' so it came as I expected.

import random

n, m, k= 3, 9, 14
lis = [random.choice(range(0, m)) for i in range(k)]
tot = sum(lis)
print(*lis, sep = '')

while tot%n != 0:
    tot -= lis.pop()
    last = random.choice(range(0, m))
    lis.append(last)
    tot += last

Perhaps a bit pedantic, but if anyone reading this in the future has a use-case in which questions of probability distribution matters, they should be aware that the algorithm of picking all but the last number at random and then picking the last number so as to satisfy the constraints, introduces a bias in the last number. As proof of concept:

import random, math

def f(k,a,b):
    """generates k random integers in a,b which sum to an even number"""
    start = [random.randint(a,b) for _ in range(k-1)]
    if sum(start) % 2 == 0:
        #pick an even number
        start.append(2*random.randint(math.ceil(a/2),math.floor(b/2)))
    else:
        #pick an off number
        start.append(1 + 2*random.randint(math.ceil((a-1)/2),math.floor((b-1)/2)))
    return start

For example, a typical run of f(3,1,5) produced [1, 5, 2] .

But:

trials = [f(3,1,5) for _ in range(10000)]
print(sum(trial[0]%2 == 0 for trial in trials)/10000) #percentage of first nums which are even
print(sum(trial[2]%2 == 0 for trial in trials)/10000) #percentage of last nums which are even

typical output:

0.3996
0.5198

Which shows a clear bias.

Generate your list fully, check if the sum is divisible by n, if not replace last element untill it works:

import random

n, m, k= 3, 9, 15
lis = [random.choice(range(0, m)) for i in range(k)]
tot = sum(lis)

while tot%n != 0:
    tot -= lis.pop()
    last = random.choice(range(0, m))
    lis.append(last)
    tot += last

An improvement on @SamerAyoub using numpy :

import numpy as np

n, m, k= 3, 9, 15
arr = np.empty(k, dtype = int)
arr[1:] = np.random.choice(np.arange(0, m), k-1)
rem = arr[1:].sum() % n
arr[0]  = np.random.choice(np.arange(n - rem, m, n))

print(''.join([str(i) for i in arr]))
print(arr.sum()%n == 0)

374564205252063
True

There's also the (not guaranteed to be especially fast, especially if n is large) brute force method, which at least guarantees no bias (but doesn't guarantee ever finishing):

def slow_way(n, m, k):
    arr = np.empty(k)
    while arr.sum() % n != 0:
        arr = np.random.choice(np.arange(0, m), k)
    return arr

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM