La sélection d'une permutation aléatoire nécessite simultanément plus et moins de caractère aléatoire que ce que votre question implique. Laisse-moi expliquer.
La mauvaise nouvelle: il faut plus de hasard.
Le défaut fondamental de votre approche est qu'elle essaie de choisir entre ~ 2226 possibilités en utilisant 64 bits d'entropie (la graine aléatoire). Pour choisir équitablement entre ~ 2226 possibilités, vous devrez trouver un moyen de générer 226 bits d'entropie au lieu de 64.
Il existe plusieurs façons de générer des bits aléatoires: matériel dédié , instructions CPU , interfaces OS , services en ligne . Il y a déjà une supposition implicite dans votre question que vous pouvez en quelque sorte générer 64 bits, alors faites simplement ce que vous alliez faire, seulement quatre fois, et donnez les bits excédentaires à une œuvre caritative. :)
La bonne nouvelle: il faut moins de hasard.
Une fois que vous disposez de ces 226 bits aléatoires, le reste peut être effectué de manière déterministe et les propriétés de java.util.Random
peuvent donc être rendues non pertinentes . Voici comment.
Disons que nous générons les 52! permutations (supporter avec moi) et les trier lexicographiquement.
Pour choisir l'une des permutations, tout ce dont nous avons besoin est un seul entier aléatoire entre 0
et 52!-1
. Cet entier est notre 226 bits d'entropie. Nous l'utiliserons comme index dans notre liste triée de permutations. Si l'index aléatoire est uniformément distribué, non seulement vous êtes assuré que toutes les permutations peuvent être choisies, elles seront choisies de manière équiprobable (ce qui est une garantie plus forte que ce que la question pose).
Maintenant, vous n'avez pas réellement besoin de générer toutes ces permutations. Vous pouvez en produire un directement, étant donné sa position choisie au hasard dans notre liste triée hypothétique. Cela peut être fait en temps O (n 2 ) en utilisant le code de Lehmer [1] (voir également les permutations de numérotation et le système de numérotation factoriadique ). Le n ici est la taille de votre deck, soit 52.
Il y a une implémentation C dans cette réponse StackOverflow . Il existe plusieurs variables entières qui débordent pour n = 52, mais heureusement, en Java, vous pouvez utiliser java.math.BigInteger
. Le reste des calculs peut être transcrit presque tel quel:
public static int[] shuffle(int n, BigInteger random_index) {
int[] perm = new int[n];
BigInteger[] fact = new BigInteger[n];
fact[0] = BigInteger.ONE;
for (int k = 1; k < n; ++k) {
fact[k] = fact[k - 1].multiply(BigInteger.valueOf(k));
}
// compute factorial code
for (int k = 0; k < n; ++k) {
BigInteger[] divmod = random_index.divideAndRemainder(fact[n - 1 - k]);
perm[k] = divmod[0].intValue();
random_index = divmod[1];
}
// readjust values to obtain the permutation
// start from the end and check if preceding values are lower
for (int k = n - 1; k > 0; --k) {
for (int j = k - 1; j >= 0; --j) {
if (perm[j] <= perm[k]) {
perm[k]++;
}
}
}
return perm;
}
public static void main (String[] args) {
System.out.printf("%s\n", Arrays.toString(
shuffle(52, new BigInteger(
"7890123456789012345678901234567890123456789012345678901234567890"))));
}
[1] À ne pas confondre avec Lehrer . :)
Random
ne sont jamais de vrais nombres aléatoires. C'est un PRNG, où P signifie «pseudo». Pour les vrais nombres aléatoires, vous avez besoin d'une source d'aléatoire (comme random.org).