We can use the Fisher-Yates shuffle to generate a sequence of length n <= 55
that is guaranteed to be pairwise different. For this, we only need to return the last n
elements from the shuffled array. But this would be a huge waste of resources: if we need only five random values, why shuffle the whole array? just shuffle the last five positions instead!
import java.util.Arrays;
import java.util.Random;
public class RandomArray {
private static final int[] range = new int[55];
private static final Random rng = new Random();
static {
for (int i = 0; i < range.length; ++i) {
range[i] = i + 1;
}
}
public static void main(String args[]) {
for (int i = 0; i < 10; ++i) {
System.out.println(Arrays.toString(getNRandom(5)));
}
}
/* partial Fisher-Yates shuffle for the last n elements */
public static int[] getNRandom(final int n) {
int limit = range.length - n;
for (int i = range.length - 1; i >= limit && i > 0; --i) {
int swapIdx = rng.nextInt(i);
int tmp = range[swapIdx];
range[swapIdx] = range[i];
range[i] = tmp;
}
return Arrays.copyOfRange(range, limit, range.length);
}
}
Demo (rextester.com)
We keep using the same source array range
, even if it is mixed up a little bit already. This does not change the possibility since Fisher-Yates shuffle guarantees an equally distributed array (i.e. the probability of each number to be at the i
th position after a shuffle is 1/n
, where n
is the array size.
This algorithm has the followung properties:
- It terminates provably. Both answers of forty-two and Tim Biegelstein are not guaranteed to terminate1.
- It uses the minimum randomness required, i.e. it always uses only
n
calls to rng.nextInt(...)
when a random sequence of length n
is generated. If a random sequence of length range.length
is required, it needs only range.length - 1
random calls.
1 Although termination is very likely for both algorithms.