A seleção de uma permutação aleatória requer simultaneamente mais e menos aleatoriedade do que a sua pergunta implica. Deixe-me explicar.
A má notícia: precisa de mais aleatoriedade.
A falha fundamental em sua abordagem é que ela está tentando escolher entre ~ 2.226 possibilidades usando 64 bits de entropia (a semente aleatória). Para escolher razoavelmente entre ~ 226 possibilidades, você precisará encontrar uma maneira de gerar 226 bits de entropia em vez de 64.
Existem várias maneiras de gerar bits aleatórios: hardware dedicado , instruções de CPU , interfaces de SO , serviços online . Já existe uma suposição implícita na sua pergunta de que você pode, de alguma forma, gerar 64 bits, então faça o que quer que fosse fazer apenas quatro vezes e doe os bits em excesso para a caridade. :)
A boa notícia: precisa de menos aleatoriedade.
Depois de ter esses 226 bits aleatórios, o restante pode ser feito de forma determinística e, portanto, as propriedades de java.util.Random
podem ser irrelevantes . Aqui está como.
Digamos que geramos todos os 52! permutações (tenha paciência comigo) e classifique-as lexicograficamente.
Para escolher uma das permutações, basta um único número aleatório entre 0
e 52!-1
. Esse número inteiro é nossos 226 bits de entropia. Vamos usá-lo como um índice em nossa lista ordenada de permutações. Se o índice aleatório for distribuído uniformemente, você não apenas terá a garantia de que todas as permutações poderão ser escolhidas, mas também de forma equitativa (o que é uma garantia mais forte do que a pergunta).
Agora, você realmente não precisa gerar todas essas permutações. Você pode produzir uma diretamente, dada a sua posição escolhida aleatoriamente em nossa lista hipotética classificada. Isto pode ser feito em O (n 2 ) usando o Lehmer [1] código (ver também numeração permutações e sistema número factoriadic ). O n aqui é o tamanho do seu baralho, ou seja, 52.
Há uma implementação C nesta resposta do StackOverflow . Existem várias variáveis inteiras que estourariam para n = 52, mas felizmente em Java você pode usar java.math.BigInteger
. O restante dos cálculos pode ser transcrito quase como está:
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] Não deve ser confundido com Lehrer . :)
Random
nunca são números aleatórios reais . É um PRNG, onde P significa "pseudo". Para números aleatórios reais , você precisa de uma fonte de aleatoriedade (como random.org).