n個の設定ビットを持つ数値を正確に生成するためのPRNG


12

現在、バイナリデータを生成するコードを書いています。具体的には、指定されたビット数で64ビットの数値を生成する必要があります。より正確には、プロシージャはを取り、正確にビットがに設定され、残りが0に設定された擬似ランダム64ビット数を返す必要があります。0<n<64n1

私の現在のアプローチには、次のようなものが含まれます。

  1. 擬似乱数64ビット数を生成します。k
  2. のビットをカウントし、結果を保存します。kb
  3. 場合、出力します。それ以外の場合は1に進みます。b=nk

これは機能しますが、洗練されていないようです。これよりもエレガントにセットビットの数を生成できるPRNGアルゴリズムの種類はありますか?n

回答:


12

必要なのは、0〜 64の乱数です。問題は、これをビットパターンに変換することです。(64n)1

これは列挙型コーディングと呼ばれ、最も古い展開された圧縮アルゴリズムの1つです。おそらく最も簡単なアルゴリズムは、Thomas Coverによるものです。これは、設定ビットが最上位ビット順でx kx 1であるビット長の単語がある場合、これを含むすべての単語の辞書式順序におけるこの単語の位置という単純な観察に基づいています。プロパティは:nxkx1

1ik(xii)

したがって、たとえば、7ビットの単語の場合:

i0001011= 3

i(0000111)=(23)+(12)+(01)=0
i0001101= 3
i(0001011)=(33)+(12)+(01)=1
i(0001101)=(33)+(22)+(01)=2

...等々。

序数からビットパターンを取得するには、各ビットを順番にデコードするだけです。Cのような言語でのこのようなもの:

uint64_t decode(uint64_t ones, uint64_t ordinal)
{
    uint64_t bits = 0;
    for (uint64_t bit = 63; ones > 0; --bit)
    {
        uint64_t nCk = choose(bit, ones);
        if (ordinal >= nCk)
        {
            ordinal -= nCk;
            bits |= 1 << bit;
            --ones;
        }
    }
    return bits;
}

最大64の二項係数のみが必要なので、事前に計算できることに注意してください。


  • カバー、T。、列挙型ソースエンコーディング。情報理論に関するIEEEトランザクション、Vol IT-19、No 1、1973年1月。

美しくエレガント!列挙型コーディングは非常に便利なもののように見えます-それに良いリソースはありますか(できれば教科書形式)?
コズロス

これは実際に実際に優れたパフォーマンスを提供しますか?(もちろん、RNGの速度に依存します。)そうでない場合、より複雑なコードを使用しても意味がありません。
ジル 'SO-悪であるのをやめる'

1
@Gilesこれはcs.seであるため、これをコンピューターサイエンスの質問と解釈しました。私はたまたまRRR配列の実装に横たわっていたので、ソースコードのみを提供しました。(たとえば、その意味の説明については alexbowe.com / rrrを。)
仮名

1
@Gillesあなたの質問をフォローアップするために、私は素朴な方法とForthのPseudonymが提供する方法の両方を実装しました。単純なxorshift PRNGを使用する場合でも、単純な方法では20秒のオーダーがかかりました。数字ごと、仮名の方法はほとんど瞬時でした。これには、事前に計算された二項式の表を使用しました。
コズロス

1
@KozRoss nビットの数値を生成し、kビットが設定された数値を探す場合、kがn / 2から遠く離れている場合、それらは非常にまれです。それはそれを説明するでしょう。
gnasher729

3

他の手段で得られた仮名の答えに非常に似ています。

使用可能な組み合わせの総数は、星と棒の方法でアプローチできるため、。数値をサンプリングしようとする64ビットの数値の総数は、明らかにそれよりもはるかに多くなります。c=(64n)

必要なのは、擬似乱数kから導くことができる関数ですkからcまでのから対応する64ビットの組み合わせに。1c

パスカルの三角形は、すべてのノードの値がそのノードから三角形のルートへのパスの数を正確に表し、すべてのパスが左のターンがすべてである場合、探している文字列の1つを表すようにできるため、あなたを助けることができますラベル付き すべての右折には 0が付けられます。10

したがって、を決定するために残されたビット数とし、yを使用するために残されたビット数とします。xy

私達はことを知っています 、それを使用して、各ステップで数値の次のビットを適切に決定できます。(xy)=(x1y)+(x1y1)

whilex>0

ifx>y

ifk>(x1y):ss+"1",kk(x1y),yy1

else:ss+"0"

else:ss+"1",yy1

xx1


2

別の非常にエレガントな方法は、このstackoverflowの答えで説明されているように二分法を使用することですです。考え方は、2つの単語を保持することです。1つは最大でkビットを設定し、もう1つは少なくともkビットを設定し、ランダム性を使用してこれらの1つを正確にkビットに移動します。これを説明するためのソースコードを次に示します。

word randomKBits(int k) {
    word min = 0;
    word max = word(~word(0)); // all 1s
    int n = 0;
    while (n != k) {
        word x = randomWord();
        x = min | (x & max);
        n = popcount(x);
        if (n > k)
            max = x;
        else
            min = x;
    }
    return min;
}

私が作っ様々な方法の性能比較をし、kが非常に小さいことが知られていない限り、この1は、一般的に最速です。


0

次のことができます。

k164

k01

3)手順1と2を繰り返すn

A[]640

for(i=1 to n)
{
    k=ran(1,65-i) % random number between 1 and 65-i
    for(x=1;x<65;x++)
    {
        if(A[x]==0)k--;
        if(k==0)break;
    }
    A[x]=1;
}

散文はあなたのコードと一致しないようですか?コードは1sを配列に割り当てません。また、(制約条件を満たしていない偶数)の均一な分布を生成するように思われない場合、複数kの衝突
Bergi

A[バツ]=1fA[バツ]==0k;

ああ、なるほど。散文アルゴリズムでは、スキップについて言及していませんでした。
ベルギ

@ArghyaChakrabortyあなたはそこに1ベースのインデックスを使用していますか?
コズロス

=1k=1AA[1]==0trあなたはek;k=0A[1]=1forバツ=0;バツ<64;バツ++
ユーザーが見つかりません
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.