ランダムな順列を選択することは、同時に、あなたの質問が意味するものよりも多かれ少なかれランダム性を必要とします。説明させてください。
悪い知らせ:より多くのランダム性が必要です。
あなたのアプローチの根本的な欠陥は、64ビットのエントロピー(ランダムシード)を使用して、2〜226通りの可能性を選択しようとしていることです。〜2 226を公平に選択するには可能性には、64ではなく226ビットのエントロピーを生成する方法を見つける必要があります。
ランダムビットを生成する方法はいくつかあります:専用ハードウェア、CPU命令、OSインターフェイス、オンラインサービス。あなたの質問には、どういうわけか64ビットを生成できるという暗黙の仮定があります。そのため、やろうとしていることを4回だけ実行し、余分なビットを慈善団体に寄付してください。:)
良い知らせ:ランダム性を少なくする必要があります。
これらの226個のランダムビットを取得したら、残りを確定的に行うことができるため、のプロパティをjava.util.Random
無関係にすることができます。こちらがその方法です。
52をすべて生成するとします。順列(私と一緒にクマ)を辞書順に並べ替えます。
順列のいずれかを選択するには、すべての私達の必要性は、単一の間のランダムな整数である0
と52!-1
。その整数は226ビットのエントロピーです。並べ替えられた並べ替えリストのインデックスとして使用します。ランダムインデックスが均一に分布している場合、すべての順列を選択できることが保証されるだけでなく、それらはほぼ確実に選択されます(これは、質問の質問よりも強力な保証です)。
さて、実際にそれらすべての順列を生成する必要はありません。架空のソートリストでランダムに選択された位置を指定すると、直接生成できます。これはO(n、レーマー[1]コードを使用 2)時間ます(番号付け順列および階乗数体系も参照))。ここのnはデッキのサイズ、つまり52です。
このStackOverflow回答にはC実装があります。n = 52でオーバーフローする整数変数がいくつかありますが、Javaでは幸いにもを使用できますjava.math.BigInteger
。残りの計算はほぼそのまま転記できます。
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]レーラーと混同しないでください。:)
Random
は決して実際の乱数ではありません。これはPRNGであり、Pは「疑似」を表します。以下のために本当の乱数、あなたは、(random.orgなど)乱数の発生源を必要とします。