6

I want to generate 9 non zero random numbers whose sum is 250. I have tried following code it gives me 9 random numbers but some numbers are zero.

 public void n_random()
{
  Random r = new Random();
ArrayList<Integer> load = new ArrayList<Integer>();
    int temp = 0;
    int sum = 0;
    for (int i = 1; i <= 9; i++) {
        if (!(i == 9)) {
            temp = r.nextInt(250 - sum);
            System.out.println("Temp " + (i) + "    " + temp);
            load.add(temp);
            sum += temp;

        } else {
            int last = (250 - sum);
            load.add(last);
            sum += last;
        }
    }

    System.out.println("Random arraylist " + load);
    System.out.println("Sum is "+ sum);

}

Where is my mistake or where i should improve my code or any other solution?

assylias
  • 321,522
  • 82
  • 660
  • 783
swapnil7
  • 808
  • 1
  • 9
  • 22

8 Answers8

13

I would suggest using:

temp = r.nextInt((250 - sum) / (9 - i)) + 1;

That will make sure that:

  • each number is strictly positive
  • you won't use the full "250 allowance" before reaching the 9th number

However the distribution of the results is probably biased.

Example output:

Random arraylist [18, 28, 22, 19, 3, 53, 37, 49, 21]

Explanation:

  • (250 - sum) is the amount left to reach 250, so you don't want to go over that
  • / (9 - i) if your sum has reached for example 200 (need 50 more) and you have 5 more to go, make sure the next random number is not more than 10, to leave some room for the next 4 draws
  • + 1 to prevent 0

An alternative which probably gives a better distribution is to take random numbers and scale them to get to the desired sum. Example implementation:

public static void n_random(int targetSum, int numberOfDraws) {
    Random r = new Random();
    List<Integer> load = new ArrayList<>();

    //random numbers
    int sum = 0;
    for (int i = 0; i < numberOfDraws; i++) {
        int next = r.nextInt(targetSum) + 1;
        load.add(next);
        sum += next;
    }

    //scale to the desired target sum
    double scale = 1d * targetSum / sum;
    sum = 0;
    for (int i = 0; i < numberOfDraws; i++) {
        load.set(i, (int) (load.get(i) * scale));
        sum += load.get(i);
    }

    //take rounding issues into account
    while(sum++ < targetSum) {
        int i = r.nextInt(numberOfDraws);
        load.set(i, load.get(i) + 1);
    }

    System.out.println("Random arraylist " + load);
    System.out.println("Sum is "+ (sum - 1));
}
assylias
  • 321,522
  • 82
  • 660
  • 783
  • The scale looks wrong, and I don't see where you compensate due to the lost due to conversion to integer. – ikegami Mar 13 '14 at 14:37
  • @ikegami if I reach 1000, scale is 250/1000 or 0.25 and I multiply all the numbers by 0.25. The `while` loop then adds 1 here and there to compensate for the rounding from double to integer, until the sum is as expected... – assylias Mar 13 '14 at 14:39
  • You fixed the bias, but you can end up with zeros since you're not guaranteed to add 1 to every number. To avoid that, you need to add one to every element and adjust the scale accordingly. See the code I posted earlier. – ikegami Mar 13 '14 at 14:51
  • @ikegami indeed - well spotted – assylias Mar 13 '14 at 14:55
  • Nope, can still end up with zeros with your fix. The + 1 must be added after the truncation to `int` is performed. – ikegami Mar 13 '14 at 14:57
  • @ikegami mmm - good point. In which case the sum may get too big so I need to check if I need to add or subtract... – assylias Mar 13 '14 at 14:58
  • 1
    That would work too. The aforementioned solution would be to adjust the scale (`double scale = 1d * (targetSum-numberOfDraws) / sum;`). In otherwords, you are trying to get a numbers that add up to 241, because you'll be adding 1 to each of the 9. – ikegami Mar 13 '14 at 15:07
4

Generate n random numbers whose sum is m and all numbers should be greater than zero

The following is basically what you were trying to achieve. Here, it's written in Perl, since I don't know Java well, but it should be easy to translate.

use strict;
use warnings;
use feature qw( say );

use List::Util qw( shuffle );

my $m = 250;
my $n = 9;
my @nums;
while ($n--) {
   my $x = int(rand($m-$n))+1;  # Gen int in 1..($m-$n) inclusive.
   push @nums, $x;
   $m -= $x;
}

say join ', ', shuffle @nums;   # shuffle reorders if that matters.

The problem with your approach is that you'll end up with a lot of small numbers. Five sample runs with the numbers in ascending order:

  • 1, 1, 1, 1, 2, 3, 6, 50, 185
  • 1, 1, 1, 1, 2, 3, 4, 13, 224
  • 1, 1, 1, 1, 1, 3, 8, 11, 223
  • 1, 1, 1, 1, 2, 4, 19, 103, 118
  • 2, 2, 9, 11, 11, 19, 19, 68, 109

A better approach might be to take N random numbers, then scale them so their sum reaches M. Implementation:

use strict;
use warnings;
use feature qw( say );

use List::Util qw( sum );

my $m = 250;
my $n = 9;

# Generate $n numbers between 0 (incl) and 1 (excl).
my @nums;
for (1..$n) {
   push @nums, rand();
}

# We subtract $n because we'll be adding one to each number later.
my $factor = ($m-$n) / sum(@nums);

for my $i (0..$#nums) {
   $nums[$i] = int($nums[$i] * $factor) + 1;
}

# Handle loss of fractional component.
my $fudge = $m - sum(@nums);
for (1..$fudge) {
   # Adds one to a random number.
   ++$nums[rand(@nums)];
}

say join('+', @nums), '=', sum(@nums);

Five sample runs:

32+32+23+42+29+32+29+20+11=250
31+18+25+16+11+41+37+56+15=250
21+15+40+46+22+40+32+1+33=250
34+24+18+29+45+30+19+29+22=250
3+45+20+6+3+25+18+65+65=250
ikegami
  • 367,544
  • 15
  • 269
  • 518
3

Your code line:

r.nextInt(250 - sum);

... will generate a pseudo-random from 0 (included) to 250 - sum (excluded).

See API for Random.nextInt.

I won't try to solve all your problem here, but simply adding 1 to the expression above would guarantee that it never returns 0.

All remaining adaptations up to you though :)

For instance if 250 - sum - 1 evaluates to negative, then you'll throw an IllegalArgumentException.

Mena
  • 47,782
  • 11
  • 87
  • 106
  • 2
    That is the issue but the solution is not trivial. If the first number is 248, it will not be possible to satisfy both the sum and the non-zero constraints. – assylias Mar 13 '14 at 14:02
  • @Mena java.lang.IllegalArgumentException – swapnil7 Mar 13 '14 at 14:10
  • @Mena If you use `nextInt(int i)`, the exception is thrown when the argument is not strictly positive. Also it generates a random number from `[0, 250 - sum)` not `[0, 250 - sum]` as you are saying. – Alexis C. Mar 13 '14 at 14:14
  • @ZouZou ehm yes. That's in my answer isn't it? – Mena Mar 13 '14 at 14:15
  • @Mena _"For instance if 250 - sum evaluates to negative"_ Since when 0 is a negative number? _"will generate a pseudo-random from 0 (included) to 250 - sum."_ Shouldn't it be _"will generate a pseudo-random from 0 (included) to 250 - sum - 1"_? (As you said it I understood that it can generate a random number equals to 250 - sum) :) – Alexis C. Mar 13 '14 at 14:17
  • @ZouZou just figured out your second issue. Will update my answer. And thanks for insisting :) – Mena Mar 13 '14 at 14:22
0

I don't see where you've checked to make sure that zero is excluded. Add a check before you insert it into the array.

Too many "magic numbers" in this code to suit me.

duffymo
  • 305,152
  • 44
  • 369
  • 561
0

A call to Random.nextInt(n) will return an integer between 0 and n-1

try temp = r.nextInt(250 - sum) + 1; and see if that solves your issue.

Jon Quarfoth
  • 2,129
  • 16
  • 20
  • Well, that will make sure you don't get a zero when generating random numbers. If you have other problems, they're currently beyond the scope of your question :) – Jon Quarfoth Mar 13 '14 at 14:15
  • you are correct,btw i got answer the which was in the scope of my question. – swapnil7 Mar 13 '14 at 14:22
0

Here's one way to do it, that avoids (most) magic numbers and provides a decent distribution of numbers though all will be smaller than other possible solutions.

public static void n_random(int count, int finalSum)
{
    Random r = new Random();
    int numbers[] = new int[count];
    int sum = 0;
    for (int i = 0; i < count - 1; i++)
    {
        numbers[i] = r.nextInt((finalSum - sum) / 2) + 1;
        sum += numbers[i];
    }
    numbers[count - 1] = finalSum - sum;

    StringBuilder numbersString = new StringBuilder("Random number list: ");
    for (int i = 0; i < count; i++)
        numbersString.append(numbers[i] + " ");
    System.out.println(numbersString);
}

public static void main(String[] args)
{
    n_random(9, 250);
}
8forty
  • 545
  • 4
  • 13
0

this is javascript alternative

function getRandomNos(m,n) {

    var nums=[];
    var i;

    for (i = 0;i<n;i++) {
       nums[i] = Math.random();
    }
    var factor = (m-n) / nums.reduce(add, 0);
    for (i = 0;i<nums.length;i++) {
       nums[i] = parseInt(nums[i] * factor) + 1;
    }

    var fudge = m - nums.reduce(add, 0);

    for (i=0;i<fudge;i++) {
       nums[i] = nums[i] + 1;
    }
    console.log(nums);
    console.log(nums.reduce(add, 0));

}

function add(a, b) {
    return a + b;
}
Lakshman Pilaka
  • 1,803
  • 2
  • 24
  • 48
0
//Perl code translated to Java
//Its working and no 0's !!!!

import java.util.*;
import java.util.stream.*;enter code here

public class MyClass {
    public static void main(String args[]) {
     int numberOfDraws = 17;
     int targetSum = 40;
     


Random r = new Random();


List<Integer> load = new ArrayList<>();



int sum = 0;
for (int i = 0; i < numberOfDraws; i++) {
        int next = r.nextInt(targetSum) + 1;
        load.add(next);
        sum += next;
        System.out.println("Arraylist first loop " + load.get(i));
    }
    
    
double factor = (((double)targetSum)-((double)numberOfDraws)) / ((double)sum);
System.out.println("Factor value: " + factor);

int newSum =0;
for (int i = 0; i < numberOfDraws; i++) {
    load.set(i, (int) ((load.get(i) * factor)) + 1);
    newSum += load.get(i);
    
    System.out.println("Arraylist second loop " + load.get(i));
}



int fudge = targetSum - newSum;
for (int i = 0; i < fudge; i++) {

   
   int y = r.nextInt(numberOfDraws);
        load.set(i, load.get(i) + 1);
   
}

System.out.println("Random arraylist " + load);

    }
    }
KPD
  • 1