6

I'm currently using Java's RNG Random r = new Random(), and having it generate a new integer between 0 and 5 in a while loop.

while (someBoolean == false) {
        int i = r.nextInt(6);
           ....
 }

What I would like to do, is to remove a number from the range (for instance, 4) so that the RNG still generates a new number between 0 and 5, excluding one of the values.

My current best bet is the following:

 while (someBoolean == false) {
        int i = r.nextInt(6);
           if (i == removedInt) { continue; }
          ....
 }

However I'm worried this could cause long runs in my code where the RNG is constantly returning a number that I don't want.

[For clarity; the number that is being returned is a column in a Connect4 grid, or 2D int array. The method is randomly placing moves in columns until a column fills up, at which point I no longer want to be able to play in that column. ]

Any help appreciated :)

JonnyH
  • 107
  • 3
  • 6

4 Answers4

3

Although you could use a List enumerating the numbers you want to generate and exclude/remove the one you want to exclude, this is only efficient for small ranges. If you want to generate a random number in a large range, this solution becomes quite inefficient and unfeasable.

Solution using only 1 Random.nextInt() call

If you want to generate random numbers in the range of 0..5 both inclusive, you can do that with r.nextInt(6).

If you want to exlude a number, e.g. 4, that means the range is smaller by 1, so use r.nextInt(5) and if the result is the excluded number, then return the max allowed which is 5 (because it will never be generated because you used max - 1).

It looks like this:

// Returns a random number in the range 0..5 (0 and 5 included), 4 excluded
public int nextRand() {
    int i = r.nextInt(5);
    return i == 4 ? 5 : i;
}

General solution

Here is a general solution which takes the min, max and the excludable numbers as parameters:

/**
 * Returns a random number in the range min..max both included, but never the excluded.
 */
public int nextRand(int min, int max, int excluded) {
    if (max <= min || excluded < min || excluded > max)
        throw new IllegalArgumentException(
                "Must be: min <= excluded <= max AND min < max");

    int i = min + r.nextInt(max - min);  // r is a java.util.Random instance
    return i == excluded ? max : i;
}

So for example if you call nextRand(0, 5, 3), it will only return a random number being one of 0, 1, 2, 4, 5.

Community
  • 1
  • 1
icza
  • 389,944
  • 63
  • 907
  • 827
  • And what happens when the number to exclude falls within the middle of the range? i.e. 2 or 3 – chancea Feb 12 '15 at 16:13
  • @chancea Why would it matter what number you want to exclude? You want 1-less possible outputs. So you generate numbers up to max-1. And if the result is the number you want to exclude, you return the one that is not generated due to max-1 is used. – icza Feb 12 '15 at 16:14
  • @chancea I think you misunderstood or did not understand the intent of passing max-1 to `nextInt()`. It's not so that the max number can never be generated; it is so that the number of possible outputs is less by 1. The excludable number is checked in a subsequent step. – icza Feb 12 '15 at 16:35
  • This works for a single value, however it does not give a normalized distribution, since there is a `2/(max-min)` (double) chance that `max` will be returned compared to the other values. However, the OP wants multiple numbers in the exclusion. – chancea Feb 12 '15 at 16:38
  • @chancea The op wants to exclude 1 number, I don't see multiple numbers mentioned anywhere. And `max` will only be returned with the same probability than any other numbers in the `min..max` range (with the exception of `excluded` which will never be returned). Please note that if you want a random number between `min..max`, you would have to call `nextInt(max - min + 1)`, **note the +1** meanwhile I call `nextInt(max - min)` there is no +1. – icza Feb 12 '15 at 16:42
  • OP: "_the number that is being returned is a column in a Connect4 grid, or 2D int array. The method is randomly placing moves in columns until a column fills up, at which point I no longer want to be able to play in that column_" – chancea Feb 12 '15 at 16:44
  • It looks like you are correct about the distribution, I misread it – chancea Feb 12 '15 at 16:45
  • @chancea Please see [this answer](http://stackoverflow.com/a/363692/1705598) to clarify that all numbers in this range will have the same chance. – icza Feb 12 '15 at 16:46
  • @chancea Also about only 1 number to be excluded: Your quote uses _singular_ not _plural_: _"the **number** that **is** being returned **is a column** in a Connect4 grid"_ – icza Feb 12 '15 at 16:47
  • If you think about the OP's statement. Once one column is filled in the grid they no longer want the random generator to be able to select that column. However _more than one column_ can be filled in the grid, it's Connect4 – chancea Feb 12 '15 at 16:50
  • 1
    This is a fine solution for a single value. The distribution is fine and I think this answer can help others in _similar_ boats as the OP. I was incorrect before, for which I apologize. – chancea Feb 12 '15 at 16:52
3
ArrayList<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);
myInts.add(3);
myInts.add(4);
int i = myInts.get(r.nextInt(myInts.size())); // 1,2,3,4
myInts.remove(3);
int j = myInts.get(r.nextInt(myInts.size())); // 1,2,4

The code selects random entry from allowed list of integers.

PS Please note, that if you have a big range of numbers, then creating an ArrayList of thousands upon thousands of integers might not be the best idea.

Gerino
  • 1,943
  • 1
  • 16
  • 21
  • This seems to answer my question best. Thank you! – JonnyH Feb 12 '15 at 16:26
  • If you'd like to change the naming conventions I'll "accept" the answer (so that others can see too) Add and Remove should be lower case, int should be `Integer` and `.getLength()` should be `.size()` :) – JonnyH Feb 12 '15 at 16:29
  • Changed :) It's amazing how quickly you can forget things when switching between languages :) – Gerino Feb 12 '15 at 16:37
  • This solution is only good for small ranges. Another solution is required for big ranges. Anyway, by your example, you want to apply this to dices so it might solve your problem. – Dalton Feb 12 '15 at 16:41
  • Quite plausible. It's quite readable and in that specific case shouldn't add much overhead or anything. Certainly it's more crude than math-based solution, but much easier to debug and maintain :) I'll add some comment though. – Gerino Feb 12 '15 at 16:44
0

Hope this helps you with your situation. It isn't really excluding any numbers but instead making a decision on what spot to make a move in based off an initial value given to it from a random number.

    //Change these to what you want
    int numberColumns = 7;
    int numberRows = 6;
    /**
    * A way for my snippet to determine what column has a piece in its very top spot.
    */
    boolean[][] hasPiece = new boolean[numberRows][numberColumns];
    /**
    * Get a random number between 0 and numberColumns - 1 and set it equal 
    * to the column you want the computer to place it in.
    */
    int columnToPlace = (int) (0 +Math.random()*numberColumns);
    /**
    * A boolean check to determine what side of the chosen column to check first.
    * This way it doesn't always check to the left or right first.
    */
    if(Math.random() < 0.5){
        //Forwards for loop to start at the initial random number given.
        for(int runner = columnToPlace; runner < numberColumns; runner++){
            if(!hasPiece[0][runner]){/**Check First Row And Next Column To The Right For A Piece.*/
                columnToPlace = runner;/**If there is no piece their set it equal to that number/empty column.*/
                break;/**Break out of the loop*/
            }
        }
    }else{
        //Reversed for loop to start at the initial random number given.
        for(int backwardsRunner = columnToPlace; backwardsRunner > 0; backwardsRunner--){
            if(!hasPiece[0][backwardsRunner]){/**Check First Row And Next Column To The Left For A Piece.*/
                columnToPlace = backwardsRunner;/**If there is no piece their set it equal to that number/empty column.*/
                break;/**Break out of the loop*/
            }
        }
    }
    //The final number/empty column the computer will make it's move in.
    System.out.println(columnToPlace);

Using this method you could probably get a decent AI that doesn't just make random moves. It's a good place to start notice the "runner" loops. If you have a way to check if it's a computers piece to the left or right of the random column you could add logic that would make the wiser decision.

IByrd
  • 177
  • 1
  • 13
0

Your brain is fixed on the idea that the numbers out of random() have to be the same numbers that you actually use for rows or whatever. Separate the mere ordinal from what it represents by using small array:

Vector validColumns = new Vector(6);
for (int i = 0; i < 6; i += 1) { validColumns.add(i); }
. . .
c = validColumns[r.nextInt(validColumns.size())]

In the ... you remove and add columns as needed.

Lee Daniel Crocker
  • 12,927
  • 3
  • 29
  • 55