Muhammad Farhan Aqeel
Muhammad Farhan Aqeel

Reputation: 717

Random numbers between a range A and B which sum up to a specific Number

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.

Upvotes: 0

Views: 1467

Answers (5)

Gerald Gehrke
Gerald Gehrke

Reputation: 1

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);
  }

})();

Upvotes: 0

bozo5573
bozo5573

Reputation: 15

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 ", ";
    }
?>

Upvotes: 0

user11458208
user11458208

Reputation:

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;
        }
    }

}

Upvotes: 0

Zedwa92
Zedwa92

Reputation: 137

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.

Upvotes: 1

user11458208
user11458208

Reputation:

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);
    }

}

Upvotes: 0

Related Questions