12

ランダムサンプルを使用して計算を近似する方法があります。このメソッドは何百万回も呼び出されるため、乱数を選択するプロセスが効率的であることが非常に重要です。

Java が実際にどれほど速いかはわかりRandom().nextIntませんが、私のプログラムは、私が望んでいるほどには恩恵を受けていないようです。

乱数を選択するときは、次のことを行います (半疑似コードで)。

// Repeat this 300000 times
Set set = new Set();
while(set.length != 5)
    set.add(randomNumber(MIN,MAX));

さて、これは明らかに悪い最悪の場合の実行時間を持っています。理論的には、ランダム関数は永遠に重複した数値を追加できるため、whileループに永遠に留まる可能性があります。ただし、数値は {0..45} から選択されるため、値が重複することはほとんどありません。

上記の方法を使用すると、他の方法よりも 40% だけ速くなります。これは概算ではありませんが、正しい結果が得られます。これは約 100 万回実行されたので、この新しい方法は少なくとも 50% 高速になると予想していました。

より高速な方法について何か提案はありますか? あるいは、一連の乱数を生成するより効率的な方法を知っているかもしれません。

明確にするために、ここに2つの方法があります:

// Run through all combinations (1 million). This takes 5 seconds
 for(int c1 = 0; c1 < deck.length; c1++){
    for(int c2 = c1+1; c2 < deck.length; c2++){
     for(int c3 = c2+1; c3 < deck.length; c3++){
        for(int c4 = c3+1; c4 < deck.length; c4++){
         for(int c5 = c4+1; c5 < deck.length; c5++){
             enumeration(hands, cards, deck, c1, c2, c3, c4, c5);
         }
            } 
      }     
   }
   }

// Approximate (300000 combinations). This takes 3 seconds
Random rand = new Random();
HashSet<Integer> set = new HashSet<Integer>();
int[] numbers = new int[5];
while(enumerations < 300000){
set.clear();
while(set.size() != 5){
    set.add(rand.nextInt(deck.length));
}
Iterator<Integer> i = set.iterator();
int n = 0;
while(i.hasNext()){
    numbers[n] = i.next();
    n++;
}

いくつかのテストとプロファイリングの後、この方法が最も効果的であることがわかりました。

Random rand = new Random();
int[] numbers = new int[5];
ArrayList<Integer> list = new ArrayList<Integer>();
while(enumerations < 300000){
 while(list.size() != 5) {
     int i = rand.nextInt(deck.length);
        if(!list.contains(i)) list.add(i);
 }
 int index = 0;
 for(int i : list){ numbers[index] = i; index++; }
 enumeration(hands, cards, deck,numbers);
}
4

8 に答える 8

11

Mersenne Twisterの既存の Java 実装(またはこれ) を使用してみることができます。

ほとんどの MTは暗号的に安全ではないことに注意してください。

于 2010-03-26T13:27:16.473 に答える
5

セットSからk -組み合わせを選択したいようです。Sにはn 個の異なる値、k = 5 およびn = 52 があります。セット全体からk 個の要素を選択できます ( @Tesserexが示唆するように)、または(あなたが示したように)重複を避けながらk要素。特定の環境と選択したジェネレーターの両方でプロファイルを作成する必要があります。常にではありませんが、私はしばしば の適度な優位性を目にします。shuffle()pick() pick()

private static final Random rnd = new Random();
private static final int N = 52;
private static final int K = 5;
private static final List<Integer> S = new ArrayList<Integer>(N);
static {
    for (int i = 0; i < N; i++) {
        S.add(i + 1);
    }
}
private final List<Integer> combination = new ArrayList<Integer>(K);

...

private void shuffle() {
    Collections.shuffle(S, rnd);
    combination.addAll(S.subList(0, K));
}

private void pick() {
    for (int i = 0; i < K; i++) {
        int v = 0;
        do {
            v = rnd.nextInt(N) + 1;
        } while (combination.contains(v));
        combination.add(v);
    }
}
于 2010-03-26T15:25:01.197 に答える
2

線形合同を乱数発生器として使用できます: http://en.wikipedia.org/wiki/Linear_congruential_generator [まだ統計的な欠点を考慮してください]

各数値に対して (x + c) % m の計算のみが必要です。それでも、私の経験では、オブジェクトの作成 (使用する実装に応じて、new Set と add のすべての呼び出しで行う場合と同様) は、nextInt() の呼び出しよりも高速になる可能性があります。たとえば、次のようなプロファイラーを試してみてください: http://www.eclipse.org/tptp/

于 2010-03-26T13:51:48.060 に答える
2

一般的な手法は、考えられるすべての入力のリストから始めて、そこからランダムに選択し、途中で削除することです。そうすれば、重複を選択して、不明な時間ループするリスクがなくなります。もちろん、この方法は離散データでのみ機能しますが、幸いなことに整数は機能します。また、速度を重視しているため、リスト (またはその他のデータ構造) の選択と削除は、可能であれば O(1) にする必要があることも覚えておいてください。

于 2010-03-26T13:26:58.043 に答える
1

私はあなたの実際の問題について何の情報も持っていません。しかし、ポーカー用のハンド エバリュエーターを構築しようとしているように思えます。このスレッドhttp://pokerai.org/pf3/viewtopic.php?f=3&t=16には、非常に高速な Java ハンド エバリュエーターが含まれています。このコードの一部が役立つことを願っています。

于 2010-03-26T14:09:54.070 に答える
1

重複をスキップしなければならないという事実によって速度が低下している場合は、すべてのカードの値のリストを作成し、カードが選択されるとリストから削除し、乱数を選択することでその問題を解決できます。次回は範囲を狭めます。このようなもの:

// Assuming we're just numbering all the cards 0 to 51. This could be more sophisticated, of course.
ArrayList cards=new ArrayList(52);
for (int x=0;x<52;++x)
  cards=new Integer(x);

Integer[] hand=new Integer[5];
for (int h=0;h<5;++h)
{
  // Pick a card from those remaining
  int n=random.nextInt(cards.size());
  hand[h]=cards.get(n);
  // Remove the picked card from the list
  cards.remove(n);
}

最初のドローでは、n が何であれ、cards.get(n) は n を返します。しかし、それ以降は値が削除されるため、cards.get(3) は 7 などを返す可能性があります。

リストを作成してリストから削除すると、大量のオーバーヘッドが追加されます。私の推測では、一度に 5 枚のカードしかピックしない場合、衝突の確率は十分に小さいため、重複を見つけた後に重複を排除する方が、重複を防ぐよりも速くなります。最後のドローでも重複の確率は 4/52=1/13 にすぎないため、重複をヒットすることはほとんどなく、連続して 2 つのドローが両方とも重複する確率はごくわずかです。それはすべて、配列をセットアップして削除を行うのにかかる時間と比較して、乱数を生成するのにかかる時間に依存します。最も簡単な方法は、いくつかの実験と測定を行うことです。(またはプロフィール!)

于 2010-03-26T17:10:24.987 に答える
0

推測しないで、常に測定してください。

 long time = System.getCurrentMilliseconds();
 Random().nextInt()
 System.out.println(System.getCurrentMilliseconds() - time);

また、既知のバグが発生する頻度に依存するべきではありません。重複を検出し、重複している場合は追加せず、 continueステートメントで反復をスキップします。

最速の方法と乱数については... Javaの では乱数を取得できませんMath.random()。疑似乱数のみを取得できます。これをどれだけ速くしたいかは、一見ランダムに見えるようにする必要があるという犠牲の上に成り立ちます。疑似乱数を生成する最速の方法は、次のようなシード値に基づいたビット シフトと加算を伴いますSystem.getCurrentMilliSeconds()。で生成するのに何ミリ秒かかるかがわかれば十分ですMath.random()

于 2010-03-26T13:27:50.007 に答える
0

既知の乱数ジェネレーターを開発しようとしないでください。代わりに、SecureRandom などの既知のものを使用します。

http://www.owasp.org/index.php/Using_the_Java_Cryptographic_Extensions

于 2010-03-26T21:48:44.913 に答える