2

正確に設定されたビットでビットパターンを生成する問題についてn、私は 2 つの実用的な方法を知っていますが、どちらにも満足できない制限があります。

まず、事前に計算されたテーブルに設定されたビット数を持つ可能なすべての単語値を列挙し、そのテーブルにランダムなインデックスを生成して、可能な結果を​​選択できます。これには、出力サイズが大きくなるにつれて、候補出力のリストが最終的に非現実的に大きくなるという問題があります。

または、n重複しないビット位置をランダムに選択し (たとえば、部分的なフィッシャー イェーツ シャッフルを使用して)、それらのビットのみを設定することもできます。ただし、このアプローチでは、可能な結果の数よりもはるかに大きな空間でランダムな状態を計算します。たとえば、3 つのビットから 1 番目と 2 番目のビットを選択するか、2 番目と 1 番目のビットを別々に選択します。

この 2 番目のアプローチでは、厳密に必要な数よりも多くのビットを乱数ソースから消費する必要があります。順序が重要ではないときに特定の順序でビットを選択nしているため、これは、同じ結果を生成するさまざまな方法を恣意的に区別n!し、少なくともfloor(log_2(n!))必要以上のビットを消費していることを意味します。

これは回避できますか?

明らかに、ランダムなインデックスに到達するまで法的な順列を繰り返し計算してカウントする 3 番目のアプローチがありますが、これは単に最初のアプローチのスペースと時間のトレードオフであり、効率的な方法がない限り直接的には役に立ちません。それらのn順列を数える方法。


説明

<コード>わ! / (n!*(wn)!)</code>最初のアプローチでは、0 から (は出力サイズ) までの間の単一の乱数を選択する必要がありwます。これは、可能な解の数であるためです。

2 番目のアプローチでは、ゼロから、ゼロからなどnの間のランダムな値を選択する必要があり、これらの積は、最初のアプローチよりも 倍大きくなります。w-1w-2<コード>わ! / (wn)!</code><code>ん!</code>

これは、乱数ソースが、n!すべて同等の異なる結果を区別するためにビットを生成することを強制されたことを意味します。この余分なランダム性に頼らない効率的な方法があるかどうか知りたいです。おそらく、ビット位置の順序付けられていないリストを生成するアルゴリズムを使用するか、ビットの n 番目の一意の順列を直接計算します。

4

5 に答える 5

2

フロイドのアルゴリズムの変形が必要なようです:

単一のランダムな値の組み合わせを選択するアルゴリズム?

封じ込めテストは単純なビットマスク操作であるため、あなたの場合に特に役立ちます。これには、RNG へのk回の呼び出しのみが必要です。以下のコードでは、からまでrandint(limit)の一様乱数を生成するがあり、 kビットを 32 ビットの int に設定する必要があると仮定しています。0limit-1

mask = 0;
for (j = 32 - k; j < 32; ++j) {
    r = randint(j+1);
    b = 1 << r;
    if (mask & b) mask |= (1 << j);
    else mask |= b;
}

randint()ここで必要なエントロピーのビット数は、実装方法によって異なります。k > 16 の場合、32 - kに設定し、結果を否定します。

セット内の 1 つの組み合わせを表す単一の乱数を生成するという別の提案 (数学者はこれを組み合わせのランクと呼びます) は、辞書式のランクではなくコレックスの順序を使用する場合により簡単になります。たとえば、このコードは次のとおりです。

for (i = k; i >= 1; --i) {
    while ((b = binomial(n, i)) > r) --n;
    buf[i-1] = n;
    r -= b;
}

は、配列buf[]に、colex ランクrでのk組み合わせの0 からn-1までのインデックスを入力します。あなたの場合、に置き換えます。binomial() 関数は二項係数であり、ルックアップ テーブルを使用して行います (これを参照)。それがエントロピーを最も効率的に利用することになりますが、それでも私はフロイドのアルゴリズムがより良い妥協点になると思います.buf[i-1] = nmask |= (1 << n)

于 2013-06-09T18:45:15.340 に答える
1

これは理論上の問題ですか、それとも実際的な問題ですか?

部分的なシャッフルを行うこともできますが、1 の順序を追跡し、0 を忘れてください。将来の消費のために、最終的な順序で未使用のエントロピーの log(k!) ビットがあります。

繰り返し (n choose k) = (n-1 choose k-1) + (n-1 choose k) を直接使用することもできます。0 ~ (n choose k)-1 の間の乱数を生成します。それをrと呼びます。n 番目から 1 番目までのすべてのビットを反復処理します。残りの i ビットのうち j を設定する必要がある場合、r < (i-1 が j-1 を選択) の場合は i 番目を設定し、それ以外の場合は (i-1 が j-1 を選択) 減算してクリアします。

実際には、部分的なシャッフルによる無駄なエントロピーの数語については心配しません。16 ビット セットでランダムな 32 ビット ワードを生成すると、64 ~ 80 ビットのエントロピーが必要になりますが、これはまったく問題ありません。必要なエントロピーの成長率は、理論的な限界よりも漸近的に悪いので、本当に大きな単語に対しては別のことをします。

非常に大きな単語の場合、確率 k/n で 1 である n 個の独立したビットを生成できます。これはすぐにエントロピー バジェット (そしていくらか) を使い果たしますが、直線的に多くのビットしか使用しません。ただし、設定されたビットの数は k の周りにしっかりと集中しています。さらに予想される線形エントロピー コストについては、修正できます。このアプローチは、部分シャッフル アプローチよりもはるかに優れたメモリ局所性を備えているため、実際にはそれを好むでしょう。

于 2013-06-09T17:41:27.273 に答える
0

バックグラウンド

あなたが与えた式から-w!/ ((wn)! * n!) 問題セットは、異なる位置での重複を処理する順列ではなく、一意の組み合わせの数を計算する二項係数に関係しているようです。

あなたが言った:

「明らかに、ランダムなインデックスに到達するまで法的な順列を繰り返し計算してカウントする 3 番目のアプローチがありますが、それは単に最初のアプローチのスペースと時間のトレードオフであり、直接的には役に立ちません。これらの n 順列を数え上げる効率的な方法。

...

これは、乱数ソースが n! を区別するためにビットを生成することを余儀なくされたことを意味します。すべて同等の異なる結果。この余分なランダム性に頼らない効率的な方法があるかどうか知りたいです。おそらく、ビット位置の順序付けられていないリストを生成するアルゴリズムを使用するか、n 番目の一意のビット順列を直接計算することによります。」

そのため、k インデックスから n 番目の一意の組み合わせ、つまりランクを効率的に計算する方法があります。k-indexes は、一意の組み合わせを指します。たとえば、4 を選択 3 の n を k を選択するケースが採用されたとします。これは、n で表される合計 4 つの数 (0、1、2、3) を選択できることを意味し、それらは k で表される 3 つのグループで取得されます。一意の組み合わせの総数は n! として計算できます。/ ((k! * (nk)!) 0 のランクは、(2, 1, 0) の k-index に対応します。ランク 1 は、(3, 1, 0) の k-index グループによって表されます。など。

解決

k-index グループと対応するランクの間を反復せずに非常に効率的に変換するために使用できる式があります。同様に、ランクと対応する k-index グループの間を変換する式があります。

私はこの公式とパスカルの三角形からどのようにそれを見ることができるかについて論文を書きました。この論文は、Tablizing The Binomial Coeffieicentと呼ばれています。

論文で説明されている数式を実装するパブリック ドメインにある C# クラスを作成しました。メモリをほとんど使用せず、サイトからダウンロードできます。次のタスクを実行します。

  1. 任意の N choose K について、すべての k-index を適切な形式でファイルに出力します。K インデックスは、よりわかりやすい文字列または文字で置き換えることができます。

  2. k-index を、ソートされた二項係数テーブル内のエントリの適切な辞書式インデックスまたはランクに変換します。この手法は、反復に依存する以前に公開された手法よりもはるかに高速です。パスカルの三角形に固有の数学的プロパティを使用してこれを行い、セット全体を反復する場合と比較して非常に効率的です。

  3. ソートされた二項係数テーブルのインデックスを対応する k-index に変換します。使用される手法は、古い反復ソリューションよりもはるかに高速です。

  4. Mark Dominusメソッドを使用して二項係数を計算します。これは、オーバーフローする可能性がはるかに低く、より大きな数で機能します。このバージョンは long 値を返します。int を返す他のメソッドが少なくとも 1 つあります。long 値を返すメソッドを使用していることを確認してください。

  5. このクラスは .NET C# で記述されており、問題に関連するオブジェクト (存在する場合) をジェネリック リストを使用して管理する方法を提供します。このクラスのコンストラクターは、InitTable と呼ばれる bool 値を受け取ります。これが true の場合、管理対象のオブジェクトを保持する汎用リストが作成されます。この値が false の場合、テーブルは作成されません。上記の 4 つの方法を使用するためにテーブルを作成する必要はありません。テーブルにアクセスするためのアクセサ メソッドが用意されています。

  6. クラスとそのメソッドの使用方法を示す関連するテスト クラスがあります。少なくとも 2 つのケースで広範囲にテストされており、既知のバグはありません。

次のテスト済みのコード例は、クラスの使用方法を示しており、一意の組み合わせごとに反復処理されます。

public void Test10Choose5()
{
   String S;
   int Loop;
   int N = 10;  // Total number of elements in the set.
   int K = 5;  // Total number of elements in each group.
   // Create the bin coeff object required to get all
   // the combos for this N choose K combination.
   BinCoeff<int> BC = new BinCoeff<int>(N, K, false);
   int NumCombos = BinCoeff<int>.GetBinCoeff(N, K);
   // The Kindexes array specifies the indexes for a lexigraphic element.
   int[] KIndexes = new int[K];
   StringBuilder SB = new StringBuilder();
   // Loop thru all the combinations for this N choose K case.
   for (int Combo = 0; Combo < NumCombos; Combo++)
   {
      // Get the k-indexes for this combination.  
      BC.GetKIndexes(Combo, KIndexes);
      // Verify that the Kindexes returned can be used to retrive the
      // rank or lexigraphic order of the KIndexes in the table.
      int Val = BC.GetIndex(true, KIndexes);
      if (Val != Combo)
      {
         S = "Val of " + Val.ToString() + " != Combo Value of " + Combo.ToString();
         Console.WriteLine(S);
      }
      SB.Remove(0, SB.Length);
      for (Loop = 0; Loop < K; Loop++)
      {
         SB.Append(KIndexes[Loop].ToString());
         if (Loop < K - 1)
            SB.Append(" ");
      }
      S = "KIndexes = " + SB.ToString();
      Console.WriteLine(S);
   }
}

したがって、クラスを問題に適用する方法は、単語サイズの各ビットをアイテムの総数と見なすことです。これは、n!/((k! (n - k)!) 式の n になります。k、またはグループ サイズを取得するには、1 に設定されたビット数をカウントするだけです。リストまたは配列を作成する必要があります。この場合は 32 になります。クラスは N を処理しないことに注意してください N を選択、N を 0 を選択、または N を 1 を選択。 32 は 0 ケースを選択し、32 は 32 ケースを選択します. 32 が 1 を選択する場合は、32 を返す必要があります.

32 よりも大きくない値を使用する必要がある場合は、16 を選択します (32 アイテムの最悪のケース - 601,080,390 の一意の組み合わせが生成されます)。現在のクラスの実装方法である 32 ビット整数を使用できます。64 ビット整数を使用する必要がある場合は、64 ビット long を使用するようにクラスを変換する必要があります。long が保持できる最大値は 18,446,744,073,709,551,616 で、これは 2 ^ 64 です。 n が 64 のときに k を選択する場合の最悪のケースは、64 を選択して 32 を選択することです。 . それよりも大きな数値が必要な場合は、何らかの大きな整数クラスの使用を検討することをお勧めします。C# では、.NET フレームワークにBigInteger クラスがあります。別の言語で作業している場合、移植は難しくありません。

非常に優れた PRNG を探している場合、最速、軽量、高品質の出力の 1 つは Tiny Mersenne Twister または略して TinyMT です。コードを C++ と C# に移植しました。元の作成者の C コードへのリンクとともに、ここで見つけることができます。

Fisher-Yates のようなシャッフル アルゴリズムを使用する代わりに、次の例のようなことを検討することもできます。

// Get 7 random cards.
ulong Card;
ulong SevenCardHand = 0;
for (int CardLoop = 0; CardLoop < 7; CardLoop++)
{
  do
  {
    // The card has a value of between 0 and 51.  So, get a random value and
    // left shift it into the proper bit position.  
    Card = (1UL << RandObj.Next(CardsInDeck));
  } while ((SevenCardHand & Card) != 0);
  SevenCardHand |= Card;
}

上記のコードは、52 枚ではなく 7 枚のカードでしか機能しないため、シャッフル アルゴリズムよりも高速です (少なくともランダム カードのサブセットを取得する場合)。また、カードを単一の 64 ビット ワード内の個々のビットにパックします。ポーカー ハンドの評価もより効率的になります。

余談ですが、非常に大きな数で機能することがわかった最高の二項係数計算機 (結果が 15,000 桁を超えるケースを正確に計算したもの) は、こちらで見つけることができます。

于 2013-06-10T01:19:58.093 に答える