A random number generator creates numbers in a finite range, so it has to repeat itself sooner or later.
When the range of numbers you generate is small (like when you want to simulate a deck of cards), you can create a list with all possible values, shuffle it randomly and return the elements in the now randomized order.
class UniqueRandom {
private LinkedList<Integer> results;
public UniqueRandom(int range) {
results = new LinkedList<Integer>(range);
for (var i = 0; i < range; i++) {
results.add(i);
}
Collection.shuffle(results, new SecureRandom());
}
public int nextInt() throws NoSuchElementException {
return results.pop(); // will throw NoSuchElementException when all values are used up
}
}
When this is unfeasible because the range is too large, you could store the numbers which have already been generated in a Set. Before you return a result, check if it is already in that set, and when it is, reroll.
class UniqueRandom {
private Set<Integer> used = new HashSet<Integer>();
private Random rand = new SecureRandom();
public int nextInt(int max) {
Integer ret = null;
do {
ret = rand.nextInt(max);
} while (used.add(ret) == false);
return ret;
}
}
Warning: The latter algorithm will become slower and slower the more numbers you generate and will finally enter an infinite loop when all values are used up, so only use it when you can be sure that the range of results will never be exhausted. Alternatively, you could check the size of used
and throw an exception when it gets too large.