0

I am looking to generate some random numbers that would sum up to a specific number.

Example:

Range(10,35)

Total(sum): 100

Numbers to generate: 5

What should be the best way to do this? Tried to write my own logic but it looked quite messy as a psudo-code. any algorithm that i can follow? saw similar questions but didn't get any proper answer.

  • 2
    what have you tried so far and what where those similar questions? – derHugo Jun 25 '19 at 11:47
  • 1
    Similar questions : https://stackoverflow.com/questions/472013/generate-a-series-of-random-numbers-that-add-up-to-n-in-c-sharp https://stackoverflow.com/questions/21782329/generate-n-random-numbers-whose-sum-is-a-constant-k-excel/21782884 https://crypto.stackexchange.com/questions/47577/generate-random-numbers-whose-sum-is-equal-to-a-constant-value-modulo-n https://www.excelforum.com/excel-general/1112914-generate-n-random-numbers-whose-sum-is-constant-k-and-constant-l-by-dividing-j.html – Muhammad Farhan Aqeel Jun 25 '19 at 11:56
  • This sounds like a maths test I did at school. Have you any thoughts as to how you think you would succeed @MuhammadFarhanAqeel? – BugFinder Jun 25 '19 at 12:03

5 Answers5

1

An idea could be to consider the fact that the randomness of the first 4 numbers, will contribute to the randomness of the last one. To follow your example:

  1. 100 / 5 = 20. The average number generated cannot be greater than 20. Every time you generate a number greater than 20, you can subtract the delta to what will be generated next.
  2. a = rand(10, 35). Let's say a = 20. We are good to go.
  3. b = rand(10, 35). Let's say b = 35. We have a 15 delta.
  4. c = rand(10, 35 - 15) = rand(10, 20) = 18.
  5. d = rand(10, 20) = 12.
  6. e = 100 - (a + b + c + d) = 15

You have now 5 random numbers that sum up to 100.

Note: Very nice remark from @derHugo. The delta adjustment works both ways. If the first number is 10, the next will be rand(10 + 10, 35) = rand(20, 35). Etc.

Zedka29
  • 127
  • 4
  • 4
    what happens though if by casuality the first 4 values are all `10` ? then `e = 60` which is not quite in the range of `10, 35` ;) => you probably have to make the same delta adjustment to the lower limit of the range – derHugo Jun 25 '19 at 12:20
  • I was answering you :) Yes, exactly, which is far from obvious in my example. I will make that clear. – Zedka29 Jun 25 '19 at 12:25
  • I don't really understand your note , let's say your first randomly generated number is `27`, which means the second generated number will be `rand(10 + 27) = rand(37, 35)` which is not possible. What am I doing wrong? – DataMind Feb 28 '22 at 13:21
0

All you have to use is System.Random to generate the random numbers between the range and then keep adding them while checking if the total sum is equal to the result or not.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RandomNumberGenerationWithSum : MonoBehaviour
{

    string output = "";
    int sum = 0;
    int result = 100;    //Give the sum

    void Start()
    {
        System.Random r = new System.Random();

        while (sum != result)
        {
            int add;
            if ((result - sum) > 35) //maximum limit
            {
                add = r.Next(10, 35); //define the range
            }
            else
            {
                add = r.Next(result - sum + 1);
            }

            sum += add;
            output += add.ToString() + " + ";
        }

        output = output.Remove(output.Length - 2);

        Debug.Log("Output: " + output);
    }

}
  • It can create less - or more - numbers than required. – Zedka29 Jun 25 '19 at 12:20
  • Then I guess from the above script, he can create an array with size 5 and push the random generated values to the array. Once the array is filled, he can check if the addition of the 5 values is equal to 100 or not, If not, then pop the last value entered and push a new value and check it again. Do this repeatedly till the result is achieved. –  Jun 25 '19 at 12:23
0

Here I have updated a created a new script with an array. So you can add the size in the population, say 5. So only 5 random numbers will be generated to add the values to get the result. Hope this helps!

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RandomInitial : MonoBehaviour
{

    public int[] population;
    int result = 100;    //Give the sum
    int z;
    private bool Check = true;

    void Start()
    {


    }

    void Update()
    {
        if (Check == true)
        {

            for (int i = 0; i < population.Length; i++)
            {
                population[i] = Random.Range(10, 35);
            }

            for (int i = 0; i < population.Length; i++)
            {
                z += population[i];
            }
            Debug.Log("value " + z);
        }

        if (z == result)
        {
            Debug.Log("Success");
            Check = false;
        }
        else
        if (z != result)
        {
            z = 0;
        }
    }

}

  • 2
    This generates new random values each frame until by accident you find a matching set of numbers ... eventually .. 1. not very efficient and 2. not reliable .. it is possible that after 5 Minutes you still don't have a valid result – derHugo Jun 25 '19 at 13:43
  • @derHugo Yeah not reliable if the target is 100 and population is higher than 5, but does get the result that the user was asking. –  Jun 25 '19 at 13:45
  • *never reliable – derHugo Jun 25 '19 at 13:47
0

Okay.. So the most simpler way is this: Just make a loop that subtracts random number from target number.. Do it n-1 times and check if the one thats left is in range.. I'll write code in php, than I'll post it here

EDIT: here it is

Note: It does work, But it's not error free, you would have to add some defensive stuff, So it doesn't go over $targetNum

<?php
    $targetNum = 100;
    $randMin =10;
    $randMax = 35;
    $generateNum = 5;

    $numArray = array();

    $numLeft=$targetNum;

    for($i = 0; $i<$generateNum-1;$i++){
        $numArray[$i] = rand($randMin,$randMax);
        $numLeft -= $numArray[$i];
        if($i==$generateNum-2){
            $numArray[$i+1] = $numLeft;
        }
    }
    for($i = 0; $i<$generateNum;$i++){
        echo $numArray[$i];
        echo ", ";
    }
?>
bozo5573
  • 15
  • 4
  • I don't think it is working as far as I understand what you mean. In the example, if 35 is picked 3 times, you are already above 100 by the time you reach n-1. – Zedka29 Jun 25 '19 at 15:45
  • Yes, but you just need to do a bit of defensive programming and ensure that it isn't over targetNum – bozo5573 Jun 25 '19 at 21:52
0

I wrote this JavaScript code following Zedka29's answer, but I ran into a few bugs when selecting different amounts of numbers to generate. In your example, if 20 was the max of your range, you would end up having a gap left over as it is likely the first number will be less than 20 in that case.

    function genRand(min, max, target, numItems) {
        let total = 0;  
        let maxNum = Math.floor(target/min);
        let minNum = Math.ceil(target/max);
        
        if (!numItems) numItems = Math.floor(Math.random() * (maxNum - minNum) + minNum);
        
        let avg = Math.round(target / numItems);
        let tries = 0;
        while (total < target && tries < maxNum) { 
            let tmp = Math.floor(Math.random() * (max - min) + min); 
            if (total + tmp > target) {
                tmp = target - total;
            }
            else if (total + tmp > target - min) {
                tmp += target - (total + tmp);
                if (tmp > max) {
                    // SOMETHING WRONG
                }
            }
            else {
                if (tmp < avg) { 
                    min += (avg - tmp); 
                } 
                else if (tmp > avg) { 
                    max -= (tmp - avg); 
                } 
            }
            total += tmp; 
            

            console.log(tmp + ', new min: ' + min + ', new max: ' + max); 
            console.log('total: ' + total);
            ++tries;
        } 
        console.log('numItems ' + numItems);
        console.log('nums generated: ' + tries);
    }

    genRand(10,20,100, 5);

However, after running it in this post a few times, I am realizing the last number seems to always be 30. 10, the minimum, more than the maximum... I wonder if that info would be useful.

Edit 1

After struggling to figure out what went wrong with Zedka29's answer, I developed a rather brute-force solution to this problem using recursion and arrays. The code below will generate all possible unique sorted combinations of numbers within your range that add up to the total. To actually generate the random numbers you need, sort your current array of numbers and check that it is in the array(s) generated by the brute-force method described here. Note: this method uses more memory than is necessary and could still be optimized, but will protect against having a remaining number too large or too little regardless of what the user input.

Here is the link to the repl.it: https://repl.it/@geraldgehrke/RandomLengthsTesting-1

(function(){

  const total = 41; 
  const size = 5; 
  let arr = [];
  let cntrs = new Array(size);
  const min = 5;
  const max = 10;
  let maxNum = Math.floor(total/min);
  let minNum = Math.ceil(total/max);
  if (minNum > maxNum) {
    minNum = maxNum;
  }

  for (let i = minNum; i <= maxNum; ++i) {
    cntrs = new Array(i);
    arr = [];
    generateSums(total, i, arr, cntrs, min, max);
  }

  function generateSums(total, size, arr, cntrs, min, max) {
    function count(index) {
      ++cntrs[index];
      let sum = 0;
      for (let i of cntrs) {
        sum += i;
      }
      if (sum == total) {
        arr.push(cntrs.slice(0));
      }
      if (index < size - 1) {
        count(index + 1);
      }
    }
    function loop(prevIndex, loopLevel, maxLoopLevel) {
      if (loopLevel < maxLoopLevel) {
        for (let i = prevIndex; i < size; ++i) {
          loop(i, loopLevel + 1, maxLoopLevel);
        }
      }
      else {
        for (let i = prevIndex; i < size; ++i) {
          cntrs.fill(min, i+1);
          count(i);
        }
      }
    }
    cntrs.fill(min, 0);
    let sum = 0;
    for (let i of cntrs) {
      sum += i;
    }
    if (sum == total) {
      arr.push(cntrs.slice(0));
    }
    count(0);
    for (let i = 1; i < max-min; ++i) {
      loop(0,1,i);
    }
    console.log(arr);
  }

})();