Home > Enterprise >  How exactly does one make random numbers add up to a declared sum?
How exactly does one make random numbers add up to a declared sum?

Time:11-16

I'm confused as to how exactly I would make 9 random numbers add to whatever number the user may input. Let's say the user inputs "200" as the number, how do I make it so that I could get 9 random numbers add up exactly to 200?

Obviously, the code below doesn't work the way I want it to because it's literally just 9 random numbers that don't add up to a specific number. I just have no idea how to get this built properly.

public static void RandomStats()
{
    Random RandomClass = new Random();

    int[] intRandomStats = {
        RandomClass.Next(0, 101), 
        RandomClass.Next(0, 101), 
        RandomClass.Next(0, 101), 
        RandomClass.Next(0, 101), 
        RandomClass.Next(0, 101), 
        RandomClass.Next(0, 101), 
        RandomClass.Next(0, 101), 
        RandomClass.Next(0, 101), 
        RandomClass.Next(0, 101)
    };

    // ...
}

CodePudding user response:

I think your question is more of math question than a code question.

It sounds like what you are looking for is a multinomial distribution. A very naive way of generating a distribution like that would be to think of it like throwing dice. Imagine that you have 200 dice with 9 sides each. Roll all of them. Count all the ones that ended up with the 1 side up, that would be your first number. Then count the ones that ended up with the 2 side up, that would be your second number. Continue until all dice are counted. There are 200 dice, so the sum of the counts will be 200. Each count would have the same probability distribution.

The above pseudo-algorithm would not be so efficient, basically looping over each die. Maybe efficiency is not so important in your case, (and 200 is a small number, so it does not matter) so feel free to write this algorithm.

If efficiency matters, try to find an existing implementation in a library. Maybe the MathNet library would work for you? See the Sample method if you are interested. At the very least, now that you know the term "multinomial distribution" it should be a bit easier to google for inspiration.

CodePudding user response:

Imagine you have a bag of 200 coins. You need to divvy those coins into 9 random piles. A pile can have all the coins in the bag, some of the coins in the bag, or no coins.

Each time you allocate coins for a pile, the number of coins in the bag gets smaller (unless you grabbed 0 coins in which case it stays the same). This new count is referenced for the next pile allocation.

var rand = new Random();
var amount = 200;
var targetOutputValueCount = 9;
var outputValues = new List<int>();
for (int i = 1; i < targetOutputValueCount; i  ) // 1 less than all groups
{
    var groupAmount = rand.Next(0, amount);
    amount -= groupAmount;
    outputValues.Add(groupAmount);
}

// for the last group, it's whatever is left over
outputValues.Add(amount);

foreach (var outputValue in outputValues)
{
    Console.WriteLine(outputValue);
}

An example output would be

148
28
0
2
12
2
1
6
1

The advantage of this approach is that you are always guaranteed to have positive output numbers.

CodePudding user response:

Just generate eight numbers and compute the ninth as the missing difference:

int theSum = 200;
var randomNumbers = new int[9];
for(int i = 0; i < 8; i)
{
    randomNumbers[i] = random.Next(0, theSum);
}

randomNumbers[8] = theSum - randomNumbers.Sum();

CodePudding user response:

You could also repeatedly generate 9 random numbers until they sum up to the desired sum. The optimal range for the random numbers is twice the target sum (200) divided by the number of random numbers (9) because then their average will be close to 200/9.

var random = new Random();
var randomNumbers = new int[9];
int input = 200;
int optimalRange = 2 * input / randomNumbers.Length;
int iterations = 0;
do {
    for (int i = 0; i < randomNumbers.Length; i  ) {
        randomNumbers[i] = random.Next(optimalRange);
    }
    iterations  ;
} while (randomNumbers.Sum() != input);

Console.WriteLine($"iterations = {iterations}");
Console.WriteLine($"numbers = {String.Join(", ", randomNumbers)}");

Example output:

iterations = 113
numbers = 2, 24, 39, 28, 6, 28, 34, 17, 22

CodePudding user response:

The methods proposed so far are workable, but tend to produce results that are skewed. For example, forcing the last number to give the correct sum can give you a value that is a long way off the other values (and possibly negative, which might be a problem in some cases). Calculating random values in the range from zero up to the remaining sum will give you a series of numbers that rapidly approach zero.

Instead, to generate n random numbers from 0 to total, I would suggest picking n-1 random values in the range from 0 to total (inclusive). Consider each of these values as the location of a bookmark in a deck of total cards. If the deck is then separated into n piles at these bookmarks, then the number of cards in each pile will give you a uniformly distributed set of values that sum to total.

Here's some code to illustrate the idea (in C, sorry):

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

int cmp(const void *a, const void *b) {
    return *((int*)a) - *((int*)b);
}

int main(int argc, char *argv[]) {
    int nterms, total, x, i, checksum;
    int *array;
    
    srand(time(0));
    
    if (argc != 3) return puts("Require 2 arguments: <nterms> and <total>");
    nterms = atoi(argv[1]);     /* N.B. Input value checks omitted.      */
    total = atoi(argv[2]);      /* Avoid large or negative values!       */
    
    /* We want to generate nterms intervals across the range from 0 to   */
    /* total (inclusive), so we need an array of nterms 1 values to mark */
    /* the start and end of each interval.                               */
    array = malloc((nterms 1) * sizeof(int));
    
    /* The first and last items in this list must be zero and total (to  */
    /* ensure that the list of numbers add up to the correct amount)     */
    array[0] = 0;
    array[nterms] = total;
    
    /* Fill the rest of the array with random values from 0 to total.    */
    for (i=1; i<nterms; i  ) {
        array[i] = rand() % (total 1);
    }
    /* Sort these values in ascending order.                             */
    qsort(array, nterms 1, sizeof(int), cmp);
    
    /* Our list of random numbers can now be calculated from the         */
    /* difference between each pair of values in this list.              */
    printf("Numbers:");
    for (i=checksum=0; i<nterms; i  ) {
        x = array[i 1] - array[i];
        checksum  = x;
        printf(" %d", x);
    }
    printf("\nTotal:   %d\n", checksum);
    return 0;
}

CodePudding user response:

My approach ain't too different from others, but here it is:

  • Started by declaring an integer with the total value;
  • Subtracted the user input;
  • Used a for loop to iterate 8 times;
  • Each time get the division from remaining total and remaining iterations;

Option 1:

  • Used previous division as the maximum random value if remaining total is less than the max value;

Option 2:

  • Used previous division as the maximum random value;

  • The 9th number is the remaining total.

//run: RandomStats(0,101,200) for your particular example.
public static void RandomStats(int min, int max, int total)
{
    Random randomClass = new Random();

    int[] randomStats = new int[9];

    int value = 0;
    int totalValue = total;
    bool parsed = false;
    while (!parsed)
    {
        Console.WriteLine("Please enter a number:");
        if (int.TryParse(Console.ReadLine(), out value))
        {
            parsed = true;
            totalValue -= value;

            for (int i = 0; i < randomStats.Length-1; i  )
            {
                //option 1
                int remainMax = (int) Math.Floor((float) totalValue / (randomStats.Length - 1 - i));
                int randomValue = randomClass.Next(min, totalValue < max ? remainMax : max);  
                //option 2
                //max = (int) Math.Floor((float) totalValue / (randomStats.Length - 1 - i));
                //int randomValue = randomClass.Next(min, max); 
                totalValue -= randomValue;
                randomStats[i] = randomValue;
            }
            randomStats[8] = totalValue;
        }
        else
        {
            Console.WriteLine("Not a valid input");
        }
    }

    Console.WriteLine($"min value: {min}\tmax value: {max}\ttotal value: {total}");
    int testValue = value;
    Console.WriteLine("Input value - "   value);
    for (int i = 0; i < randomStats.Length; i  )
    {
        testValue  = randomStats[i];
        int randomIndex = i   1;
        Console.WriteLine(randomIndex   " Random value - "   randomStats[i]);
    }
    Console.WriteLine("test value - "   testValue);
    Console.ReadKey();
}

option 1 output:

min value: 0    max value: 101  total value: 200
Input value - 10
1 Random value - 13
2 Random value - 2
3 Random value - 95
4 Random value - 10
5 Random value - 0
6 Random value - 15
7 Random value - 10
8 Random value - 10
9 Random value - 35
test value - 200

option 2 output:

min value: 0    max value: 67*   total value: 200
Input value - 10
1 Random value - 1
2 Random value - 16
3 Random value - 5
4 Random value - 29
5 Random value - 17
6 Random value - 7
7 Random value - 48
8 Random value - 19
9 Random value - 48
test value - 200

*this was still 101 but became irrelevant since I divided remaining totals by the iterations

  • Related