java.util.Randomは本当にランダムですか?どうすれば52を生成できますか?(階乗)可能なシーケンス?


202

Random (java.util.Random)は52枚のカードのデッキをシャッフルするために使用しています。52があります!(8.0658175e + 67)可能性。しかし、私はその種がjava.util.Randomはでありlong、2 ^ 64(1.8446744e + 19)ではるかに小さい。

ここから、私は疑わしい java.util.Random は本当にランダムである。52をすべて生成できるのでしょうか。可能性?

そうでない場合、52をすべて生成できるより良いランダムシーケンスを確実に生成するにはどうすればよいでしょうか。可能性?


21
「どうすれば52を超える実数の乱数を確実に生成できますか?」からの数字Randomは決して実際の乱数ではありません。これはPRNGであり、Pは「疑似」を表します。以下のために本当の乱数、あなたは、(random.orgなど)乱数の発生源を必要とします。
TJクラウダー2018

7
@JimGarrisonそれはOPの後ではありません。彼は10 ^ 68の可能なシーケンスについて話している。各擬似ランダムシーケンスはシードによって識別されるため、OPは最大で2 ^ 64の異なるシーケンスが存在する可能性があると述べています。
dasblinkenlight 2018

6
興味深い質問だと思います。しかし、私はあなたの問題のコンテキストについて疑問に思わずにはいられません。52をすべて生成できるようにするための要件につながっているのは、まさにそれです。順列?たとえば、実際の橋では、デッキをシャッフルして一度に1枚のカードを配ることができますが、多くの異なる順列が同じハンドになるため、6e11の異なるハンドしかありません。別の方向で考えると、52のための特別なソリューションが必要ですか?または、たとえば、2つのデッキを一緒にシャッフル(104!/(2 ** 52)の可能性、または〜2e150)に一般化するソリューションが必要ですか?
NPE、

9
@NPE-たとえば52、ソリティア(クロンダイク)可能な手の数は正確に..です
Serj Ardovic

3
これは興味深い
記事

回答:


153

ランダムな順列を選択することは、同時に、あなたの質問が意味するものよりも多かれ少なかれランダム性を必要とします。説明させてください。

悪い知らせ:より多くのランダム性が必要です。

あなたのアプローチの根本的な欠陥は、64ビットのエントロピー(ランダムシード)を使用して、2〜226通りの可能性を選択しようとしていることです。〜2 226を公​​平に選択するには可能性には、64ではなく226ビットのエントロピーを生成する方法を見つける必要があります。

ランダムビットを生成する方法はいくつかあります:専用ハードウェアCPU命令OSインターフェイスオンラインサービス。あなたの質問には、どういうわけか64ビットを生成できるという暗黙の仮定があります。そのため、やろうとしていることを4回だけ実行し、余分なビットを慈善団体に寄付してください。:)

良い知らせ:ランダム性を少なくする必要があります。

これらの226個のランダムビットを取得したら、残りを確定的に行うことできるため、のプロパティをjava.util.Random無関係にすることができます。こちらがその方法です。

52をすべて生成するとします。順列(私と一緒にクマ)を辞書順に並べ替えます。

順列のいずれかを選択するには、すべての私達の必要性は、単一の間のランダムな整数である052!-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]レーラーと混同しないでください。:)


7
えっと、最後のリンクはNew Mathになると確信していました。:-)
TJクラウダー2018

5
@TJCrowder:もうすぐでした!それを揺るがしたのは、無限に微分可能なリーマン多様体でした。:-)
NPE、

2
古典を鑑賞する人々を見るのは良いことです。:-)
TJクラウダー2018

3
あなたはJavaでランダムな226ビットをどこで手に入れますか?申し訳ありませんが、あなたのコードはそれに答えません。
Thorsten S.

5
意味がわかりません。JavaRandom()は64ビットのエントロピーも提供しません。OPは、PRNGをシードするために64ビットを生成できる未指定のソースを意味します。同じソースに226ビットを要求できると想定することは理にかなっています。
モニカへの危害を停止する

60

あなたの分析は正解です。疑似乱数ジェネレータに特定のシードをシードすると、シャッフル後に同じシーケンスが生成され、取得できる順列の数が2 64に制限されます。このアサーションは、2回呼び出し、同じシードで初期化されたオブジェクトを渡し、2つのランダムなシャッフルが同一であることを観察することにより、実験的に検証するの簡単です。Collection.shuffleRandom

これに対する解決策は、より大きなシードを可能にする乱数ジェネレータを使用することです。Javaは、事実上無制限のサイズの配列でSecureRandom初期化できるクラスを提供しますbyte[]。次に、SecureRandomtoのインスタンスを渡しCollections.shuffleてタスクを完了することができます。

byte seed[] = new byte[...];
Random rnd = new SecureRandom(seed);
Collections.shuffle(deck, rnd);

8
確かに、大きな種子はすべての52を保証するものではありません!可能性が生み出されるでしょうか(これはこの質問が具体的に言っていることです)?思考実験として、任意の大きなシードを取り、無限に長い一連のゼロを生成する病理学的PRNGを考えます。PRNGが十分な大きさのシードを取得するだけでなく、より多くの要件を満たす必要があることは明らかです。
NPE、

2
@SerjArdovicはい、SecureRandomオブジェクトに渡されるシードマテリアルは、Javaのドキュメントのとおり、予測不可能である必要があります。
dasblinkenlight

10
@NPE正解です。小さすぎるシードは上限の保証ですが、十分な大きさのシードは下限では保証されません。これにより、理論上の上限が削除され、RNGが52をすべて生成できるようになります。組み合わせ。
dasblinkenlight

5
@SerjArdovicそのために必要な最小バイト数は29です(52を表すには226ビットが必要です!可能なビットの組み合わせは28.25バイトなので、切り上げる必要があります)。29バイトのシードマテリアルを使用すると、下限を確立せずに、取得できるシャッフル数の理論上の上限が削除されることに注意してください(非常に大きなシードを取得し、すべて0のシーケンスを生成する、くだらないRNGに関するNPEのコメントを参照してください)。
dasblinkenlight

8
SecureRandom実装は、ほぼ確実に根本的なPRNGを使用します。また、52の階乗置換から選択できるかどうかは、PRNGの期間(および程度は低いが状態の長さ)に依存します。(ドキュメントでは、SecureRandom実装は特定の統計テストに「最小限に準拠」し、「暗号的に強力でなければならない」が、根本的なPRNGの状態の長さまたはその期間に明確な下限を設定しない出力を生成することを示しています。)
Peter O.

26

一般に、擬似乱数ジェネレーター(PRNG)は、状態長が226ビット未満の場合、52項目リストのすべての順列の中から選択できません。

java.util.Randomモジュラスが2 48のアルゴリズムを実装します。したがって、その状態長は48ビットに過ぎず、私が参照した226ビットよりはるかに短いです。状態長が長い別のPRNGを使用する必要があります。具体的には、52階乗以上の周期を持つものです。

乱数ジェネレーターに関する私の記事の「シャッフル」も参照してください。

この考慮事項は、PRNGの性質とは無関係です。これは、暗号化および非暗号化PRNGに等しく適用されます(もちろん、非暗号化PRNGは、情報セキュリティが関係する場合は常に不適切です)。


java.security.SecureRandom無制限の長さの種子に渡すことができ、SecureRandom実装は、基礎となるPRNG(例えば、「SHA1PRNG」または「DRBG」)を使用することができます。また、52の階乗置換から選択できるかどうかは、PRNGの期間(および程度は低いが状態の長さ)に依存します。(「状態の長さ」は「PRNGがそのシードを短縮または圧縮せずにその状態を初期化するために使用できるシードの最大サイズとして定義すること注意してください)。


18

これは理解するのが少し難しいので、事前にお詫び申し上げます...

まず第一に、あなたjava.util.Randomはそれが完全にランダムではないことをすでに知っています。シードから完全に予測可能な方法でシーケンスを生成します。シードは64ビットしかないため、2 ^ 64の異なるシーケンスしか生成できないことは完全に正しいことです。何らかの方法で64個の実際のランダムビットを生成し、それらを使用してシードを選択する場合、そのシードを使用してすべてからランダムに選択することはできません。て52の。等しい確率で可能なシーケンス。

しかし、この事実は、全く重要で、あなたが実際に限り、何も「特別な」または「著しく特別」について2 ^ 64個のシーケンスそれがそこにあるように、以上の2 ^ 64のシーケンスを生成するつもりはない限り、することができます生成します。

1000ビットのシードを使用したより優れたPRNGがあるとしましょう。初期化には2つの方法があると想像してください。1つはシード全体を使用して初期化する方法で、もう1つは初期化する前にシードを64ビットにハッシュする方法です。

どのイニシャライザがどのイニシャライザかわからない場合は、それらを区別するためのテストを作成できますか?あなたが同じように悪いものを初期化することになったのに十分な(不)幸運でない限り 64ビットで 2回、答えはノーです。特定のPRNG実装の弱点についての詳細な知識がなければ、2つの初期化子を区別できませんでした。

または、 Random遠い過去のある時点で完全にランダムに選択された2 ^ 64シーケンスの配列がクラスにあり、シードがこの配列への単なるインデックスであったと。

したがって、Randomシードに64ビットのみを使用するという事実は実際にはありませんは、同じシードを2回使用する有意な可能性がない限り、統計的に必ずしも問題ではありません。

もちろん、暗号化の目的では、システムに同じシードを2回使用させることは計算上可能であるため、64ビットのシードでは不十分です。

編集:

上記のすべてが正しいにもかかわらず、の実際の実装java.util.Randomは素晴らしいものではないことを付け加えます。カードゲームMessageDigestを作成している場合は、APIを使用してのSHA-256ハッシュを生成し、"MyGameName"+System.currentTimeMillis()それらのビットを使用してデッキをシャッフルします。上記の議論により、ユーザーが実際にギャンブルをしていなければcurrentTimeMillis、long が返されることを心配する必要はありません。ユーザー本当にギャンブルをしている場合SecureRandomは、シードなしで使用してください。


6
@ThorstenS、出てこないカードの組み合わせがあるかどうかを判断できるテストをどのように記述できますか?
マット

2
George MarsagliaのDiehardやPierre L'Ecuyer / Richard SimardのTestU01のようないくつかの乱数テストスイートがあり、ランダムな出力の統計的異常を簡単に見つけることができます。カードチェックには、2つの正方形を使用できます。カードの順序を決定します。最初の正方形は、最初の2枚のカードの位置をxyペアとして示しています。最初のカードはxで、2番目のカードの差(!)位置(-26-25)はyです。2番目の正方形は、3番目と4番目のカードを示しており、2番目/ 3番目のカードに対して(-25-25)です。しばらく実行すると、ディストリビューションのギャップとクラスターがすぐに表示されます
Thorsten S.

4
まあ、それはあなたが書くことができると言ったテストではありませんが、それも当てはまりません。なぜそのようなテストで明らかになるであろうギャップとクラスターがディストリビューションにあると思いますか?私が述べたように、これは「PRNG実装の特定の弱点」を意味し、可能なシードの数とは何の関係もありません。このようなテストでは、ジェネレータを再シードする必要はありません。初めはわかりにくいと警告しました。
Matt Timmermans、

3
@ThorstenS。これらのテストスイートは、ソースが64ビットシードの暗号化されたセキュリティで保護されたPRNGであるか、真のRNGであるかを絶対に決定しません。(結局のところ、PRNGのテストはそれらのスイートの目的です。)使用中のアルゴリズムを知っていても、優れたPRNGは、状態空間のブルートフォース検索なしでは状態を決定することを不可能にします。
Sneftel 2018

1
@ThorstenS .:実際のカードのデッキでは、大多数の組み合わせは決して出てきません。あなたはそれらがどれであるかを知らないだけです。半端なPRNGの場合も同じです。指定された出力シーケンスがその画像にあるかどうかをテストできる場合、それはPRNGの欠陥です。52のように途方もなく巨大な状態/期間!必要ありません。128ビットで十分です。
R .. GitHub ICE

10

これについては少し別の方法で説明します。あなたはあなたの仮定に正解です-あなたのPRNGは52をすべてヒットすることはできません!可能性。

問題は、あなたのカードゲームの規模は何ですか?

シンプルなクロンダイクスタイルのゲームを作成している場合は、 そうすれ、52 すべて必要ありません。可能性。代わりに、次のように見てください。プレーヤーには18 兆の異なるゲームがあります。「誕生日の問題」を考慮に入れても、彼らは最初の複製ゲームに出くわす前に何十億ものハンドをプレイしなければなりません。

モンテカルロシミュレーションを作成している場合は、 その後、おそらく大丈夫です。PRNGの「P」が原因でアーティファクトに対処する必要があるかもしれませんが、シードスペースが少ないために問題が発生することはおそらくないでしょう(ここでも、何千ものユニークな可能性を検討しています)。逆に言えば、イテレーション数が多い場合は、そうです、そうですね、シードスペースが少ないと、取引がうまくいかない可能性があります。

あなたがマルチプレイヤーカードゲームを作っているなら、特にラインにお金があるなら? 次に、オンラインポーカーサイトがあなたが尋ねているのと同じ問題をどのように処理したかについて、グーグルする必要があります。低シードスペースの問題ではないながらので目立ち平均プレイヤーに、それがあるから悪用それの価値は時間の投資であれば。(ポーカーサイトはすべて、PRNGが「ハッキング」されるフェーズを通過しました。公開されたカードからシードを推測するだけで、誰かが他のすべてのプレーヤーのホールカードを見ることができます。)これがあなたの状況である場合は、ドン「tはあなたが暗号の問題として真剣にそれを治療する必要があります-単により良いPRNGを見つけます。


9

基本的にdasblinkenlightと同じ短いソリューション:

// Java 7
SecureRandom random = new SecureRandom();
// Java 8
SecureRandom random = SecureRandom.getInstanceStrong();

Collections.shuffle(deck, random);

内部状態を気にする必要はありません。長い説明の理由:

SecureRandomこの方法でインスタンスを作成すると、OS固有の真の乱数ジェネレータにアクセスします。これは、ランダムビットを含む値にアクセスするエントロピープール(たとえば、ナノ秒タイマーの場合、ナノ秒の精度は本質的にランダムです)または内部ハードウェア数値ジェネレーターのいずれかです。

この入力(!)には依然として偽のトレースが含まれている可能性があり、これらのトレースを削除する暗号的に強力なハッシュに送られます。これが、これらのCSPRNGが使用される理由であり、それらの数値自体を作成するためではありません。SecureRandom(使用したビット数トレースカウンタ有しgetBytes()getLong()等)とリフィルSecureRandomエントロピービットを有する必要が

簡単に言うと、単に反対意見を忘れてSecureRandom、真の乱数ジェネレータとして使用します。


4

数値を単なるビット(またはバイト)の配列と見なす場合Random.nextBytes、このスタックオーバーフローの質問で提案されている(安全な)ソリューションを使用して、配列をにマッピングできnew BigInteger(byte[])ます。


3

非常に単純なアルゴリズムは、0から上に向かって増加する整数のシーケンスにSHA-256を適用することです。(必要に応じて「別のシーケンスを取得」するためにソルトを追加できます。)SHA-256の出力が0〜2 256の均一に分散された整数と「等しい」と仮定 - 1、我々はのための十分なエントロピーを有します仕事。

SHA256の出力から順列を取得するには(整数として表す場合)、この疑似コードのように、52、51、50を法としてそれを減らす必要があります。

deck = [0..52]
shuffled = []
r = SHA256(i)

while deck.size > 0:
    pick = r % deck.size
    r = floor(r / deck.size)

    shuffled.append(deck[pick])
    delete deck[pick]
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.