Reputation: 717
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
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.
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
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
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
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:
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
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