5

How to generate between 1 and n random numbers (positive integers greater than 0) which sum up to exactly n?

Example results if n=10:

10
2,5,3
1,1,1,1,1,1,1,1,1,1
1,1,5,1,1,1

Each of the permutations should have the same probability of occurring, however, I don't need it to be mathematically precise. So if the probabilities are not the same due to some modulo error, I don't care.

Is there a go-to algorithm for this? I only found algorithms where the number of values is fixed (i.e., give me exactly m random numbers which sum up to n).

D.R.
  • 20,268
  • 21
  • 102
  • 205
  • While the sum is less than `n`, generate another random number. If it doesn't put you over `n`, repeat. If it does, end the loop and change the last number generated to the remainder instead. – ceejayoz Nov 15 '18 at 20:14
  • 1
    @ceejayoz: I thought of this algorithm too, however, it looks like it's *way* off the "same probability for each permutation", isn't it? There are easily more than 10 permutations, but "10" has a probability of 1/10th. (I know, I said it doesn't have to be mathematically precise, but it shouldn't be *way* off) – D.R. Nov 15 '18 at 20:16
  • 1
    Just to clarify - you say "numbers" but your examples are all integer. Are you excluding floating point solutions? Are negative values allowed? How about zeros? – pjs Nov 15 '18 at 20:34
  • Yep, I'm looking for integer solutions. Negative values and zeroes are not allowed. I will edit the question. – D.R. Nov 15 '18 at 21:10
  • https://stackoverflow.com/questions/16883419/generate-random-numbers-of-which-the-sum-is-constant/16884017#16884017 – Lee Daniel Crocker Nov 15 '18 at 23:11
  • @LeeDanielCrocker your answer only solves a problem for known length of permutation. my answers below work for the problem as stated (one may include calling your method, but then i found quicker one working without it) – Milo Bem Nov 15 '18 at 23:38
  • 3
    I'm unsure why people are close-voting the question as "too broad", how can I improve the question? Please leave a comment, thank you! – D.R. Nov 16 '18 at 10:51
  • 1
    No attempt and no tag to indicate what language you're writing this in. To me, this looks more like a [math.se] question than a programming one. – Toby Speight Nov 16 '18 at 11:24
  • 1
    @TobySpeight: In the end I want to have C# code, but I've omitted the C# tag as I'm interested in the algorithm and not a specific implementation. Are algorithm questions not part of StackOverflow? As for the attempt, if I can't find an approach myself, I'm not entitled to post here? – D.R. Nov 16 '18 at 11:26
  • 1
    Just suggesting why people might have down-voted. It certainly helps if you can present an attempt that doesn't achieve what you want - even if only to back up your verbal description with something more concrete. – Toby Speight Nov 16 '18 at 11:28
  • Voting to re-open; I'm at a loss to see how this question is "too broad", especially given that it's attracted an excellent (clear, focused) answer. – Mark Dickinson Nov 16 '18 at 22:29

3 Answers3

8

Imagine the number n as a line built of n equal, indivisible sections. Your numbers are lengths of those sections that sum up to the whole. You can cut the original length between any two sections, or none.

This means there are n-1 potential cut points.

Choose a random n-1-bit number, that is a number between 0 and 2^(n-1); its binary representation tells you where to cut.

0 : 000 : [-|-|-|-] : 1,1,1,1
1 : 001 : [-|-|- -] :  1,1,2
3 : 011 : [-|- - -] :   1,3
5 : 101 : [- -|- -] :   2,2
7 : 111 : [- - - -] :    4

etc.


Implementation in python-3

import random


def perm(n, np):
    p = []
    d = 1
    for i in range(n):
        if np % 2 == 0:
            p.append(d)
            d = 1
        else:
            d += 1
        np //= 2
    return p


def test(ex_n):
    for ex_p in range(2 ** (ex_n - 1)):
        p = perm(ex_n, ex_p)
        print(len(p), p)


def randperm(n):
    np = random.randint(0, 2 ** (n - 1))
    return perm(n, np)

print(randperm(10))

you can verify it by generating all possible solutions for small n

test(4)

output:

4 [1, 1, 1, 1]
3 [2, 1, 1]
3 [1, 2, 1]
2 [3, 1]
3 [1, 1, 2]
2 [2, 2]
2 [1, 3]
1 [4]
Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Milo Bem
  • 1,033
  • 8
  • 20
  • 1
    Thanks for adding the explanation - that's a really good answer now. And we don't need to generate all _n_ bits of the decision tree at once - if we have a source of random bits, we can just take them as we need them. That can avoid the overhead of huge arithmetic numbers which we won't be using for arithmetic. – Toby Speight Nov 16 '18 at 15:10
  • 1
    I love the way you have solved it and I really like how you illustrated the example. – Maytham Fahmi Nov 16 '18 at 15:16
  • honestly, for dumb people like me, I wish you had actually named your variables so I could better reason about the code. I want to make modifications but I have no idea what d is, or np vs p, etc. I'd like to set a cap on the number of values in p. So like, "find 5 numbers that add up to 10". – Josh Sanders Jul 10 '22 at 19:08
1

Generate Random Integers With Fixed Sum

Method One: Multinomial Distribution

Deviation cannot be controlled strictly within the desired range.

# Python
import numpy as np
_sum = 800
n = 16
rnd_array = np.random.multinomial(_sum, np.ones(n)/n, size=1)[0]
print('Array:', rnd_array, ', Sum:', sum(rnd_array))
# returns Array: [64 41 57 49 48 44 46 44 40 55 58 54 54 54 39 53] , Sum: 800

Method Two: Random Integer Generator Within Lower and Upper Bounds

To control the deviation

# Python
import random
def generate_random_integers(_sum, n):  
    mean = _sum / n
    variance = int(5 * mean)

    min_v = mean - variance
    max_v = mean + variance
    array = [min_v] * n

    diff = _sum - min_v * n
    while diff > 0:
        a = random.randint(0, n - 1)
        if array[a] >= max_v:
            continue
        array[a] += 1
        diff -= 1
    return np.array([int(number) for number in array])
_sum = 800
n = 16
rnd_array = generate_random_integers(_sum, n)
print('Array:', rnd_array, ', Sum:', sum(rnd_array))
# Returns Array: [45 46 46 58 53 77 33 53 39 38 44 51 33 60 75 49] , Sum: 800

Archived from http://sunny.today/generate-random-integers-with-fixed-sum/

zelusp
  • 3,500
  • 3
  • 31
  • 65
0

Use a modulo.

This should make your day:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
    srand(time(0));
    int n=10;
    int x=0; /* sum of previous random number */
    while (x<n) {
                int r = rand() % (n-x) + 1;
                printf("%d ", r);
                x += r;
    }
    /* done */
    printf("\n");
}

Example output:

10
1 1 8 
3 4 1 1 1 
6 3 1 
9 1 
6 1 1 1 1 
5 4 1 
user803422
  • 2,636
  • 2
  • 18
  • 36
  • 1
    This seems biased towards the shorter sequences; only one of the example outputs begins with `1`, but we'd expect about half of them to begin with `1` if it selected *fairly* from all possibilities. – Toby Speight Nov 16 '18 at 15:14