1〜5から1〜7のランダムな範囲を拡大します


692

1から5の範囲のランダムな整数を生成する関数を指定して、1から7の範囲のランダムな整数を生成する関数を記述します。

  1. シンプルなソリューションとは何ですか?
  2. メモリ使用量を減らす、またはより遅いCPUで実行するための効果的なソリューションは何ですか?

それは予想外に興味深い問題であることが判明しました。1)一定の時間でそれを行い、2)均一分布が損なわれないようにする方法(ある場合)
eugensk

サイコロを使って5人から1人のプレイヤーを選択するときに、同様の問題が発生しました。サイコロを順番に投げ、最大得点を獲得した方を選びます。均一性は
達成されました

問題があなたに与えられた関数を使用し、ランダムに1-7を返す関数を書かなければならないことを要求しないと答えを投稿した場合、私は反対票を投じられますか?
ドクターブルー

どう7 * rand5() / 5ですか?
kiwixz 2015

@kiwixz、「1と7の間」を生成しますが、3または6を取得できません:{1:19.96、2:20.02、4:20.01、5:19.99、7:20.02}大まかな割合を手動でテストします。7 * .2、7 * .4、7 * .6、7 * .8、7 * 1。
pythonlarry

回答:


572

これはAdam Rosenfieldのソリューションと同等ですが、一部の読者にとってはもう少し明確かもしれません。rand5()は、1〜5の範囲の統計的にランダムな整数を返す関数であると想定しています。

int rand7()
{
    int vals[5][5] = {
        { 1, 2, 3, 4, 5 },
        { 6, 7, 1, 2, 3 },
        { 4, 5, 6, 7, 1 },
        { 2, 3, 4, 5, 6 },
        { 7, 0, 0, 0, 0 }
    };

    int result = 0;
    while (result == 0)
    {
        int i = rand5();
        int j = rand5();
        result = vals[i-1][j-1];
    }
    return result;
}

どのように機能しますか?このように考えてみてください。この2次元配列を紙に印刷し、ダーツボードに貼り付け、ランダムにダーツを投げるところを想像してみてください。ゼロ以外の値をヒットすると、選択できるゼロ以外の値の数が等しいため、1から7までの統計的にランダムな値になります。ゼロを打った場合は、ゼロ以外を打つまでダーツを投げ続けます。それがこのコードが行っていることです。iおよびjインデックスはダーツボード上の場所をランダムに選択し、良好な結果が得られない場合はダーツを投げ続けます。

アダムが言ったように、これは最悪の場合に永遠に実行することができますが、統計的に最悪のケースは決して起こりません。:)


5
私はこの解決策の背後にある論理を理解しましたが、それがどのように均一な確率をもたらすのか理解できませんか?誰かが数学を説明できますか?
user1071840

6
@ user1071840- rand5均一の場合、valsグリッド内のすべてのセルが選択される確率が等しくなります。グリッドには、区間[1、7]の各整数の正確に3つのコピーと、4つのゼロが含まれます。そのため、結果の「生の」ストリームは、[1、7]値と、個々の許容値よりも少し頻繁に発生するいくつかのゼロの偶数混合の傾向があります。しかし、ゼロは取り除かれ、[1、7]値の偶数の混合を残すため、それは問題ではありません。
Daniel Earwicker

3
それで問題を実現するためのショートカット方法:rand5()を1回だけ呼び出している場合、5つの結果しか得られません。ランダム性を追加せずに、それを5つ以上の可能な結果に変える方法は明らかにありません。
Daniel Earwicker

1
長いバージョン:rand5()は値(1、2、3、4、5)のみを持つことができます。したがって、rand5()* 5は値(5、10、15、20、25)のみを持つことができ、完全な範囲(1 ... 25)とは異なります。その場合、4を引くと(-3 ... 21)になりますが、この場合は(1、6、11、16、21)になるため、端点は正しくなりますが、4つの大きな穴があります( 2..5)、(7..10)、(12..15)、(17..21)。最後にmod 7を実行し、1を追加して(2、7、5、3、1)を与えます。したがって、4も6も発生しません。しかし(上記のショートカットを参照)、結果として得られる範囲にはずっと5つの数値しか存在しないことがわかっていたため、2つのギャップが必要でした。
Daniel Earwicker

1
ああ、私たちはrand2()ではなくrand5()しか持っていないからです:-)
gzak 2014年

352

1/7は5を底とする無限小数であるため、一定の時間で実行される(正確に正しい)解決策はありません。1つの簡単な解決策は、拒否サンプリングを使用することです。例:


int i;
do
{
  i = 5 * (rand5() - 1) + rand5();  // i is now uniformly random between 1 and 25
} while(i > 21);
// i is now uniformly random between 1 and 21
return i % 7 + 1;  // result is now uniformly random between 1 and 7

これには、ループの25/21 = 1.19反復の予想ランタイムがありますが、無限にループする可能性は無限に小さいです。


7
> 21> 26 Bに反転された場合、私は下限マップをすることだところ、それは、問題ではないC -1 /必要とされていません
BCS

26
これが正しい理由を説明する私の見解:1から25までの一様な乱数のストリームを出力するプログラムを書きたいとしましょう。そのため、答えのコードのように、5 *(rand5()-1)+ rand5()を返すだけです。ここで、1〜21の一様な乱数のストリームを作成する場合、最初のストリームを使用するだけで、[22、25]の数値が拒否されるようにフィルター処理すると、そのストリームも作成できます。次に、このストリームを取得して、各要素xに対してx%7 + 1を出力するようにフィルター処理すると、1から7までの一様な乱数のストリームが生成されます。とても簡単ですね。:D
Paggas

6
そして、それは結局、制限のない最悪の場合のランタイムを使用した完全な配布が必要か、制限付きのランタイムを使用した不完全な配布が必要かによって決まることに間違いはありません。これは、すべての5の累乗が7で割り切れないという事実、または同等に長さnの5 ^ nのシーケンスがある場合、各シーケンスに1から7までの数を割り当てることができないという事実の結果です。 1..7もおそらく同じです。
Adam Rosenfield、

5
@JulesOlléon:最悪の場合でもN呼び出しを行うだけであることが保証された一定の時間で実行されるソリューションがあったとしましょうrand5()。次に、への呼び出しのシーケンスの5 ^ Nの可能な結果がありrand5、それぞれの出力は1〜7です。したがって、k1≤k≤7ごとに出力される呼び出しのすべての可能なシーケンスを合計すると、出力kがm / 5 ^ Nになる確率はm です(mはそのようなシーケンスの数)。したがって、m / 5 ^ N = 1/7ですが、これに対する可能な整数解(N、m)はありません==>矛盾。
Adam Rosenfield、2011年

4
@paxdiablo:不正解です。真のRNGが5の無限シーケンスを生成する可能性は0です。これは、コインを無限にフリップすることが無限の連続したヘッドを生成しないことが保証されているという事実と同様の推論を使用しています。これはまた、このコードが永久にループする可能性が正確に0であることを意味します(任意の反復回数でループする前向きな可能性はあります)。
BlueRaja-Danny Pflughoeft、2011年

153

最初の回答に加えて別の回答を追加したいと思います。この回答は、への呼び出しrand5()ごとのへの呼び出し数を最小限に抑え、rand7()ランダム性の使用を最大化しようとします。つまり、ランダム性を貴重なリソースであると考える場合、ランダムビットを捨てずに、可能な限り多くを使用したいと考えています。この回答には、Ivanの回答で提示されたロジックといくつかの類似点もあります

確率変数エントロピーは、明確に定義された量です。確率が等しいN状態(一様分布)の確率変数の場合、エントロピーはlog 2 Nです。したがって、rand5()約2.32193ビットのエントロピーとrand7()約2.80735ビットのエントロピーがあります。ランダム性を最大限に活用したい場合は、への各呼び出しからのエントロピーのすべての2.32193ビットを使用しrand5()、それらをへの各呼び出しに必要な2.80735ビットのエントロピーの生成に適用する必要がありますrand7()。したがって、基本的な制限は、への呼び出しrand5()ごとにlog(7)/ log(5)= 1.20906の呼び出しを実行することrand7()です。

補足:特に明記されていない限り、この回答のすべての対数は2を底とします。 rand5()[0、4] rand7()の範囲の数値を返すと想定され、[0、6]の範囲の数値を返すと想定されます。範囲をそれぞれ[1、5]と[1、7]に調整するのは簡単です。

それでは、どうすればよいでしょうか。0から1の間の無限に正確なランダムな実数を生成します(このような無限に正確な数を実際に計算して保存できると仮定してください-これは後で修正します)。基数5でその数字を生成することにより、そのような数を生成できます。乱数0. a1 a2 a3 ...をi選択しrand5()ます。各数字a は、への呼び出しによって選択されます。たとえば、RNGがa i= 1 for allを選択した場合i、それがあまりランダムではないという事実を無視すると、それは実数1/5 + 1/5 2 + 1/5 3 + ... =に対応します1/4(幾何学的系列の合計)。

わかりましたので、0から1の間のランダムな実数を選びました。そのような乱数は均一に分布していると主張します。直感的には、各桁が均一に選択され、数が無限に正確であるため、これは理解しやすいです。ただし、ここでは離散分布ではなく連続分布を扱っているため、これの正式な証明はやや複雑です。したがって、数が区間[ ab]にある確率がの長さに等しいことを証明する必要があります。その間隔、b - a。証明は読者のための演習として残されています=)。

範囲[0、1]から一様にランダムな実数を選択したので、の出力を生成するには、範囲[0、6]の一連の一様乱数に変換する必要がありrand7()ます。これどうやってやるの?先ほどとは逆に、7を底とする無限に正確な10進数に変換すると、7を底とする各桁がの1つの出力に対応しますrand7()

前の例rand5()で、1の無限ストリームを生成する場合、ランダムな実数は1/4になります。1/4を基数7に変換すると、無限小数0.15151515 ...が得られるため、出力1、5、1、5、1、5などとして生成されます。

さて、主な考え方はありますが、2つの問題が残っています。無限に正確な実数を実際に計算または格納することはできないので、その有限部分のみをどのように扱うのでしょうか。次に、実際にどのようにベース7に変換しますか?

0と1の間の数値を基数7に変換する1つの方法は次のとおりです。

  1. 7を掛ける
  2. 結果の整数部分は、次のベース7桁です
  3. 整数部を差し引き、小数部のみを残す
  4. ステップ1に移動

無限の精度の問題に対処するために、部分的な結果を計算し、結果の上限を保存します。つまり、rand5()2回呼び出して、どちらも1を返したとします。これまでに生成した数は0.11(ベース5)です。rand5()生成する無限の一連の呼び出しが何であれ、生成するランダムな実数は0.12より大きくなることはありません。0.11≤0.11xyz ... <0.12であるということは常に真です。

そのため、これまでの現在の数値とそれがとりうる最大値を追跡しながら、両方の数値を基数7に変換ます。それらが最初のk桁で一致すれば、次のk桁を安全に出力できます。基本5桁の無限ストリームはk、基本7表現の次の桁には影響しません!

そしてそれがアルゴリズムです-の次の出力rand7()を生成rand5()するために、乱数の実数を7に変換する際の次の数字の値を確実に知るために必要なだけの数字を生成します。テストハーネスを使用したPython実装:

import random

rand5_calls = 0
def rand5():
    global rand5_calls
    rand5_calls += 1
    return random.randint(0, 4)

def rand7_gen():
    state = 0
    pow5 = 1
    pow7 = 7
    while True:
        if state / pow5 == (state + pow7) / pow5:
            result = state / pow5
            state = (state - result * pow5) * 7
            pow7 *= 7
            yield result
        else:
            state = 5 * state + pow7 * rand5()
            pow5 *= 5

if __name__ == '__main__':
    r7 = rand7_gen()
    N = 10000
    x = list(next(r7) for i in range(N))
    distr = [x.count(i) for i in range(7)]
    expmean = N / 7.0
    expstddev = math.sqrt(N * (1.0/7.0) * (6.0/7.0))

    print '%d TRIALS' % N
    print 'Expected mean: %.1f' % expmean
    print 'Expected standard deviation: %.1f' % expstddev
    print
    print 'DISTRIBUTION:'
    for i in range(7):
        print '%d: %d   (%+.3f stddevs)' % (i, distr[i], (distr[i] - expmean) / expstddev)
    print
    print 'Calls to rand5: %d (average of %f per call to rand7)' % (rand5_calls, float(rand5_calls) / N)

rand7_gen()数値の基数7への変換を含む内部状態があるため、ジェネレーターが返されることに注意してください。テストハーネスはnext(r7)10000回呼び出して10000の乱数を生成し、その分布を測定します。整数演算のみが使用されるため、結果は正確です。

また、ここでの数値は非常に大きく、非常に速くなること注意してください。5と7の累乗は急速に増加します。したがって、bignum演算により、大量の乱数を生成した後、パフォーマンスが著しく低下し始めます。しかし、ここで覚えておいてください。私の目標は、ランダムビットの使用を最大化することであり、パフォーマンスを最大化することではありませんでした(これは副次的な目標です)。

この1回の実行で、へrand5()の10000呼び出しに対して12091呼び出しを行いrand7()、平均でlog(7)/ log(5)呼び出しの最小値を4つの有効数字に達成し、結果の出力は均一でした。

ポートするために、任意の大きな整数が内蔵されていない言語には、このコードは、あなたがの値をキャップする必要がありますpow5し、pow7あなたのネイティブ整数型の最大値に-彼らは大きくなりすぎた場合、その後、リセットすべてと最初からやり直す。これにより、rand5()1コールあたりの平均コール数がrand7()わずかに増加しますが、32ビットまたは64ビットの整数であっても、あまり増加しないはずです。


7
+1は本当に興味深い答えです。特定の値でリセットするのではなく、使用されたビットをシフトオフし、他のビットを上に移動し、基本的に使用されるビットのみを維持することは可能でしょうか?それとも何か不足していますか?
Chris Lutz、

1
私は100%確信はありませんが、そうした場合、ディストリビューションをほんの少しだけ歪めると思います(ただし、そのような歪曲が何兆回もの試行なしで測定可能であるとは思えません)。
Adam Rosenfield、

FTW!bignumを小さくしようとしましたが、5の累乗には7の累乗と共通の要素がないため、実行できません!また、yieldキーワードの有効利用。非常によくやりました。
Eyal

2
非常に素晴らしい!状態を成長させずに余分なエントロピーを保持できますか?トリックは、上限と下限の両方が常に有理数であることに気づくことです。精度を失うことなく、これらを加算、減算、乗算できます。すべてをbase-35で行うと、ほぼそこまでです。残りの部分(7を掛けて小数部分を保持)は演習として残します。
イアン

@adam「pow5とpow7の値をネイティブ整数型の最大値に制限する」を参照する必要があります。これは、少なくとも単純に行われたとしても、これがディストリビューションをゆがめるとあなたが信じていることを私はあなたに信じます
触媒

36

(私はAdam Rosenfeldの答えを盗み、約7%高速にした。)

rand5()が等分布の{0,1,2,3,4}の1つを返し、目標が等分布の{0,1,2,3,4,5,6}を返すと仮定します。

int rand7() {
  i = 5 * rand5() + rand5();
  max = 25;
  //i is uniform among {0 ... max-1}
  while(i < max%7) {
    //i is uniform among {0 ... (max%7 - 1)}
    i *= 5;
    i += rand5(); //i is uniform {0 ... (((max%7)*5) - 1)}
    max %= 7;
    max *= 5; //once again, i is uniform among {0 ... max-1}
  }
  return(i%7);
}

ループが変数で作成できる最大値を追跡していますmax。これまでの結果がmax%7とmax-1の間にある場合、結果はその範囲で均一に分散されます。そうでない場合は、0とmax%7-1の間でランダムな剰余と、rand()への別の呼び出しを使用して、新しい数値と新しい最大値を作成します。その後、再び始めます。

編集:rand5()を呼び出す回数は、この方程式ではxであると期待します。

x =  2     * 21/25
   + 3     *  4/25 * 14/20
   + 4     *  4/25 *  6/20 * 28/30
   + 5     *  4/25 *  6/20 *  2/30 * 7/10
   + 6     *  4/25 *  6/20 *  2/30 * 3/10 * 14/15
   + (6+x) *  4/25 *  6/20 *  2/30 * 3/10 *  1/15
x = about 2.21 calls to rand5()

2
1,000,000回の試行でカタログ化された結果:1 = 47216; 2 = 127444; 3 = 141407; 4 = 221453; 5 = 127479; 6 = 167536; 7 = 167465。あなたが見ることができるように、分布は1得る確率に関してに欠けている
ロバート・K

2
@邪悪なノミ:あなたは間違っていると思います。このソリューションで指定されているように、テストに使用していた入力rand5()が1-5ではなく0-4を生成したことを確認しますか?
アダムローゼンフィールド

5
均一に分散された数値を追加しても、均一に分散された数値にはなりません。実際、正規分布への妥当な近似を得るには、6つの均一分布変数を合計するだけで済みます。
ミッチ小麦

2
@MitchWheat-2つの均一に分散された整数を追加すると、実際には、可能な合計が正確に1つの方法で生成される場合、均一に分散されたランダムな整数になります。それはたまたま式の場合5 * rand5() + rand5()です。
Ted Hopp、2015年

28

アルゴリズム:

7は3ビットのシーケンスで表すことができます

rand(5)を使用して、各ビットをランダムに0または1で埋めます。
例:rand(5)を呼び出し、

結果が1または2の
場合、結果が4または5の場合はビットを0 で埋め
、結果が3の場合はビットを1で埋め、無視して再度実行します(拒否)

このようにして、ランダムに3ビットを0/1で埋め、1-7の数値を得ることができます。

編集: これは最も単純で最も効率的な答えのようですので、ここにいくつかのコードがあります:

public static int random_7() {
    int returnValue = 0;
    while (returnValue == 0) {
        for (int i = 1; i <= 3; i++) {
            returnValue = (returnValue << 1) + random_5_output_2();
        }
    }
    return returnValue;
}

private static int random_5_output_2() {
    while (true) {
        int flip = random_5();

        if (flip < 3) {
            return 0;
        }
        else if (flip > 3) {
            return 1;
        }
    }
}

1
貧弱な乱数ジェネレーターは、ある時点で3を大量に生成する可能性があるため、停止問題のかすかな幽霊が常にあります。
Alex North-Keys

「結果が1または2の場合、ビットを0で埋めます。結果が4または5の場合、ビットを1で埋めます」1、2、4、5が受け入れられ、3が拒否されるロジックは何ですか。これを説明できますか?
gkns 2013

@gknsロジックはありません。1と2は0ビットで埋め、3と4は1で埋めることができます。重要なことは、各オプションが発生する可能性が50%であるため、関数のランダム性が保証されることです。少なくとも元のrand(5)関数と同じくらいランダムです。その素晴らしいソリューションです!
Mo Beigi

これは単純でも効率的でもありません。random_7あたりのrandom_5へのcalの数は、せいぜい3です。このページの他のソリューションは、2.2前後の実際のベストに近いものです。
Eyal

1
気にしないで、「while returnValue == 0 "の部分を見逃しました
NicholasFolk

19
int randbit( void )
{
    while( 1 )
    {
        int r = rand5();
        if( r <= 4 ) return(r & 1);
    }
}

int randint( int nbits )
{
    int result = 0;
    while( nbits-- )
    {
        result = (result<<1) | randbit();
    }
    return( result );
}

int rand7( void )
{
    while( 1 )
    {
        int r = randint( 3 ) + 1;
        if( r <= 7 ) return( r );
    }
}

2
正しい解決策、rand7()の呼び出しごとに平均30/7 = 4.29のrand5()の呼び出しを行う
Adam Rosenfield、

17
rand7() = (rand5()+rand5()+rand5()+rand5()+rand5()+rand5()+rand5())%7+1

編集:それはうまくいきません。1000で約2パーツずれています(完全なrand5を想定)。バケットには次のものが含まれます。

value   Count  Error%
1       11158  -0.0035
2       11144  -0.0214
3       11144  -0.0214
4       11158  -0.0035
5       11172  +0.0144
6       11177  +0.0208
7       11172  +0.0144

の合計に切り替えることにより

n   Error%
10  +/- 1e-3,
12  +/- 1e-4,
14  +/- 1e-5,
16  +/- 1e-6,
...
28  +/- 3e-11

2を追加するごとに1桁増加するようです

ところで、上記のエラーの表は、サンプリングではなく、次の繰り返し関係によって生成されました。

p[x,n]は、への呼び出しをoutput=x想定して発生する可能性のある方法の数です。nrand5

  p[1,1] ... p[5,1] = 1
  p[6,1] ... p[7,1] = 0

  p[1,n] = p[7,n-1] + p[6,n-1] + p[5,n-1] + p[4,n-1] + p[3,n-1]
  p[2,n] = p[1,n-1] + p[7,n-1] + p[6,n-1] + p[5,n-1] + p[4,n-1]
  p[3,n] = p[2,n-1] + p[1,n-1] + p[7,n-1] + p[6,n-1] + p[5,n-1]
  p[4,n] = p[3,n-1] + p[2,n-1] + p[1,n-1] + p[7,n-1] + p[6,n-1]
  p[5,n] = p[4,n-1] + p[3,n-1] + p[2,n-1] + p[1,n-1] + p[7,n-1]
  p[6,n] = p[5,n-1] + p[4,n-1] + p[3,n-1] + p[2,n-1] + p[1,n-1]
  p[7,n] = p[6,n-1] + p[5,n-1] + p[4,n-1] + p[3,n-1] + p[2,n-1]

8
これは均一な分布ではありません。それはです非常に近い均一に、しかし完全に均一ではありません。
Adam Rosenfield、

ああ!サイコロと7の。あなたが私が間違っていると言うつもりなら、読者のために演習として証明を残すべきではありません。
BCS

45
一様でないことの証明は単純です。ランダム性が進む可能性のある5 ^ 7の可能な方法があり、5 ^ 7は7の倍数ではないため、7つの合計すべてが等しく可能性があることは不可能です。(基本的に、それは5に比較的素数である7、または同等に1/7がベース5の終了10進数ではないことを意味します。)実際、この制約の下で可能な「最も均一」でさえありません。直接計算では、 5 ^ 7 = 78125の合計で、値1〜7を取得する回数は{1:11145、2:11120、3:11120、4:11145、5:11190、6:11215、7:11190}です。
ShreevatsaR

@ShreevatsaRでは、rand5()の合計を7回とる代わりに、5 * 7かかったとしたらどうでしょう。35 ^ 7%7 = 35 ^ 5%7 = 0
KBA

4
@KristianAntonsen:rand5()を何回実行しても、均一な分布は得られません。N回実行すると、5のN倍の出力が可能で、7で割り切れません(35回実行すると、35 ^ 7ではなく5 ^ 35になります)。使用する呼び出しの数を均一にします(これは任意の数にすることができ、7で割り切れる必要はありません)。しかし、rand()への非常に多くの呼び出しを使用する代わりに、確率を使用することもできます。上の答えのアルゴリズムは、正確に均一な分布を与え、rand()の予想される呼び出し数は少ないです。
ShreevatsaR 2012年

15
int ans = 0;
while (ans == 0) 
{
     for (int i=0; i<3; i++) 
     {
          while ((r = rand5()) == 3){};
          ans += (r < 3) >> i
     }
}

2
正しい解決策、rand7()の呼び出しごとに平均30/7 = 4.29のrand5()の呼び出しを行う
Adam Rosenfield、

3
ニーズがあることを左シフト:仕事へのアルゴリズムのans += (r < 3) << i
woolfie

13

以下は、乱数ジェネレータを使用して{1、2、3、4、5、6、7}に均一な分布を生成し、{1、2、3、4、5}に均一な分布を生成します。コードは乱雑ですが、ロジックは明確です。

public static int random_7(Random rg) {
    int returnValue = 0;
    while (returnValue == 0) {
        for (int i = 1; i <= 3; i++) {
            returnValue = (returnValue << 1) + SimulateFairCoin(rg);
        }
    }
    return returnValue;
}

private static int SimulateFairCoin(Random rg) {
    while (true) {
        int flipOne = random_5_mod_2(rg);
        int flipTwo = random_5_mod_2(rg);

        if (flipOne == 0 && flipTwo == 1) {
            return 0;
        }
        else if (flipOne == 1 && flipTwo == 0) {
            return 1;
        }
    }
}

private static int random_5_mod_2(Random rg) {
    return random_5(rg) % 2;
}

private static int random_5(Random rg) {
    return rg.Next(5) + 1;
}    

2
非常に効率的ではありませんが、正しい解決策(これにより、一歩先を行くことができます)。これにより、平均コインフリップごとにrandom_5_mod_2に対して平均25/6 = 4.17の呼び出しが行われ、random_7()への呼び出しごとにrandom / 7(1)に対する合計平均が100/7 = 14.3になります。
Adam Rosenfield、

他のソリューションに対するこのソリューションの利点は、簡単に拡張して、他の均一に分散された範囲を生成できることです。ランダムにビットを1つずつ選択し、無効な値(8つの数値を生成する現在のソリューションの0の値など)に再ロールします。
DenTheMan、2011年

1
無限ループなどの可能性
robermorales '21 / 09/21

1
@robermorales:ほとんどありません。
ジェイソン

13
int rand7() {
    int value = rand5()
              + rand5() * 2
              + rand5() * 3
              + rand5() * 4
              + rand5() * 5
              + rand5() * 6;
    return value%7;
}

選択したソリューションとは異なり、アルゴリズムは一定の時間で実行されます。ただし、選択したソリューションの平均実行時間よりもrand5を2回多く呼び出します。

このジェネレーターは完全ではないことに注意してください(数値0は他の数値よりも0.0064%高い可能性があります)。しかし、実際の目的では、一定の時間の保証はおそらくこの不正確さを上回ります。

説明

このソリューションは、15,624という数値が7で割り切れるという事実に基づいています。したがって、0から15,624までの数値をランダムかつ均一に生成し、mod 7を取得すると、ほぼ均一なrand7ジェネレーターが得られます。次のように、rand5を6回ローリングし、それらを使用して5を底とする数字を形成することにより、0〜15,624の数値を均一に生成できます。

rand5 * 5^5 + rand5 * 5^4 + rand5 * 5^3 + rand5 * 5^2 + rand5 * 5 + rand5

ただし、mod 7のプロパティにより、方程式を少し簡略化できます。

5^5 = 3 mod 7
5^4 = 2 mod 7
5^3 = 6 mod 7
5^2 = 4 mod 7
5^1 = 5 mod 7

そう

rand5 * 5^5 + rand5 * 5^4 + rand5 * 5^3 + rand5 * 5^2 + rand5 * 5 + rand5

なる

rand5 * 3 + rand5 * 2 + rand5 * 6 + rand5 * 4 + rand5 * 5 + rand5

理論

数値15,624はランダムに選択されたわけではありませんが、フェルマーの小さな定理を使用して発見できます。これは、pが素数の場合、

a^(p-1) = 1 mod p

これにより、

(5^6)-1 = 0 mod 7

(5 ^ 6)-1は次と等しい

4 * 5^5 + 4 * 5^4 + 4 * 5^3 + 4 * 5^2 + 4 * 5 + 4

これは5を底とする形式の数値であるため、このメソッドを使用して、任意の乱数ジェネレータから他の任意の乱数ジェネレータに移動できることがわかります。指数p-1を使用すると、常に0への小さなバイアスが導入されます。

このアプローチを一般化し、より正確にするには、次のような関数を使用できます。

def getRandomconverted(frm, to):
    s = 0
    for i in range(to):
        s += getRandomUniform(frm)*frm**i
    mx = 0
    for i in range(to):
        mx = (to-1)*frm**i 
    mx = int(mx/to)*to # maximum value till which we can take mod
    if s < mx:
        return s%to
    else:
        return getRandomconverted(frm, to)

1
このジェネレータは正確ですが完全に均一ではありません。これを確認するには、[0,15624]のユニフォームジェネレーターに15625の結果があり、7で割り切れないという事実を考慮してください。 2232/15625)。結局のところ、Fermatの小さな定理を使用すると、一見正しいように見えるかもしれませんが、(5 ^ 6)%7 = 1ではなく、(5 ^ 6)%7 = 0ではないことがわかります。5と7はどちらも素数であるため、後者は指数では明らかに不可能です。私はそれがまだ受け入れられる解決策だと思います、そして私はこれを反映するためにあなたの投稿を編集しました。
飛行士2017年

12

ここで宿題の問題は許されますか?

この関数は、粗い「ベース5」計算を実行して、0から6までの数を生成します。

function rnd7() {
    do {
        r1 = rnd5() - 1;
        do {
            r2=rnd5() - 1;
        } while (r2 > 1);
        result = r2 * 5 + r1;
    } while (result > 6);
    return result + 1;
}

3
非常に効率的ではありませんが、正しい解決策(これにより、一歩先を行くことができます)。これにより、rnd7()を呼び出すたびに、平均して5回のrnd5()が呼び出されます。
Adam Rosenfield、

もう少し説明が必要です
バリー

1
@バリー-最初に、2つの乱数を加算するだけではなく、線形の解が得られません(ダイスのペアを検討してください)。次に、「Base 5」について考えます。00、01、02、03、04、10、11。これは、基数5の0〜6です。したがって、基数5の数値の2桁を生成し、それらを追加して、範囲内のものを取得します。これが、r2 * 5 + r1が行うことです。R2>我々は> 1の高い数字を望むことはないので、1つのループがある
ウィルアルトゥング

このソリューションは均一な分布を生成しません。数値1と7は1つの方法でのみ生成できますが、2から6はそれぞれ2つの方法で生成できます。r1は数値から1を引いた値に等しく、r2は0に等しいか、r1は数値から2を引いてr2が等しい1.このように2〜6は、多くの場合、1または7のように平均二倍に返される
テッドのHopp

12

最も効率的な答えを出そうとするという追加の制約を考慮すると、I長さmが1〜5 の均一に分散された整数の入力ストリームが与えられた場合、O相対的に最も長い1〜7の均一に分散された整数のストリームが出力されます。とm言うL(m)

これを分析する最も簡単な方法は、ストリームIをOそれぞれ5進および7進の数値として扱うことです。これは、ストリームを取得するという主な回答の考え方a1, a2, a3,... -> a1+5*a2+5^2*a3+..と、同様にstream によって実現されますO

その後、我々は、長さの入力ストリームのセクション取る場合m choose n s.t. 5^m-7^n=cどこをc>0し、可能な限り小さくあります。次に、長さmの入力ストリームから1to までの整数へ5^mの均一マップと、整数1から7^n長さnの出力ストリームへの別の均一マップがあり、マップされた整数が入力ストリームからいくつかのケースを失う必要がある場合があります。を超え7^nます。

だから、これは値与えL(m)周りのm (log5/log7)約です.82m

上記の分析の難しさは、5^m-7^n=c正確に解くのが難しい方程式と、から1までの均一な値を5^m超えて7^n効率が低下する場合です。

問題は、mの可能な限り最高の値(log5 / log7)にどの程度近づくことができるかです。たとえば、この数が整数に近い場合、出力値のこの正確な整数を達成する方法を見つけることができますか?

もし5^m-7^n=c入力ストリームから、我々は効果的で一様乱数を生成し、その後0(5^m)-1、高以外の任意の値を使用していません7^n。ただし、これらの値は救出され、再度使用できます。それらは1からまでの数の均一なシーケンスを効果的に生成し5^m-7^nます。したがって、これらを使用して7進数に変換し、より多くの出力値を作成できるようにします。

我々はせた場合T7(X)の出力シーケンスの平均長さであることがrandom(1-7)均一な大きさの入力に由来する整数X、およびそれを仮定します5^m=7^n0+7^n1+7^n2+...+7^nr+s, s<7

それT7(5^m)=n0x7^n0/5^m + ((5^m-7^n0)/5^m) T7(5^m-7^n0)から、長さのシーケンスは確率7 ^ n0 / 5 ^ mであり、長さの残差は5^m-7^n0確率(5^m-7^n0)/5^m)です。

単に置き換え続けると、次のようになります。

T7(5^m) = n0x7^n0/5^m + n1x7^n1/5^m + ... + nrx7^nr/5^m  = (n0x7^n0 + n1x7^n1 + ... + nrx7^nr)/5^m

したがって

L(m)=T7(5^m)=(n0x7^n0 + n1x7^n1 + ... + nrx7^nr)/(7^n0+7^n1+7^n2+...+7^nr+s)

これを置く別の方法は次のとおりです。

If 5^m has 7-ary representation `a0+a1*7 + a2*7^2 + a3*7^3+...+ar*7^r
Then L(m) = (a1*7 + 2a2*7^2 + 3a3*7^3+...+rar*7^r)/(a0+a1*7 + a2*7^2 + a3*7^3+...+ar*7^r)

最良のケースは、私の元のwhere where 5^m=7^n+s、where s<7です。

その後T7(5^m) = nx(7^n)/(7^n+s) = n+o(1) = m (Log5/Log7)+o(1)、前と同じように。

最悪のケースは、kとst 5 ^ m = kx7 + sしか見つからない場合です。

Then T7(5^m) = 1x(k.7)/(k.7+s) = 1+o(1)

他のケースはその中間にあります。非常に大きなmに対してどれだけうまくできるか、つまり、エラー項をどれだけうまく取得できるかを見るのは興味深いでしょう。

T7(5^m) = m (Log5/Log7)+e(m)

e(m) = o(1)一般に達成することは不可能のようですが、うまくいけば証明することができe(m)=o(m)ます。

全体は5^m、のさまざまな値の7桁の数字の分布に依存しますm

これをカバーする多くの理論がそこにあると私は確信しています。


+2(もし可能なら)-これが唯一の良い答えでした(単に適切であるのではなく)。32ビット整数に収まる2番目に良い答えがあります。
レックスカー

10

これは、Adamの回答の実際のPython実装です。

import random

def rand5():
    return random.randint(1, 5)

def rand7():
    while True:
        r = 5 * (rand5() - 1) + rand5()
        #r is now uniformly random between 1 and 25
        if (r <= 21):
            break
    #result is now uniformly random between 1 and 7
    return r % 7 + 1

私が探しているアルゴリズムをPythonに投げて、それらをいじくり回したいのですが、一緒に投げるのに時間がかかったのではなく、誰かに役立つことを期待してここに投稿したいと思いました。


いいえ、それは私の回答とはかなり異なります。21回ループし、最初の20回の反復の結果を破棄しています。また、入力としてrand4()とrand5()を使用しているため、rand5()のみを使用する場合の規則に明らかに違反しています。最後に、不均一な分布を生成します。
Adam Rosenfield、

申し訳ありません。この質問を見たとき、私はかなり疲れていました。あなたのアルゴリズムを完全に読み違えるほど疲れていました。なぜ21回ループしたのか理解できなかったので、実際にはPythonに投入しました。今ではもっと理にかなっています。私はrandom.randint(1、4)のことを省略形で行いましたが、あなたが正しいと思います。それは質問の精神に反するものです。コードを修正しました。
James McMahon、

@robermorales-Adam Rosenfeldが彼の回答で説明しように [1、7 ]に真の均一な分布を与えるすべてのソリューションには、潜在的に無限であるある種の受け入れ/拒否ループが含まれます。(ただし、rand5()適切なPRNGの場合、ループは無限で5*(rand5() - 1) + rand5()はなくなります。最終的には21以下になります。)
Ted Hopp

10

なぜそれを簡単にしないのですか?

int random7() {
  return random5() + (random5() % 3);
}

このソリューションで1と7を取得する可能性は、モジュロのために低くなりますが、迅速で読みやすいソリューションが必要な場合は、これが適しています。


13
これは均一な分布を生み出しません。これにより、0〜6の数値が生成され、確率は2 / 25、4 / 25、5 / 25、5 / 25、5 / 25、3 / 25、1 / 25となります。25の可能なすべての結果をカウントすることで確認できます。
Adam Rosenfield、

8

ここでrand(n)が0からn-1までの一様分布のランダムな整数」を意味すると仮定すると、Pythonのrandintを使用したコードサンプルがあり、その効果があります。randint(5)と定数のみを使用して、randint(7)の効果を生成します。少しばかげた、実際

from random import randint
sum = 7
while sum >= 7:
    first = randint(0,5)   
    toadd = 9999
    while toadd>1:
        toadd = randint(0,5)
    if toadd:
        sum = first+5
    else:
        sum = first

assert 7>sum>=0 
print sum

1
@robermorales Pythonにはないのでdo ... while。それはされている可能性1337、または12345、または任意の数> 1
tckmn

8

Adam Rosenfieldの正解の背後にある前提は、次のとおりです。

  • x = 5 ^ n(彼の場合:n = 2)
  • n rand5呼び出しを操作して、範囲[1、x]内の数値yを取得します
  • z =((int)(x / 7))* 7
  • y> zの場合は、再試行してください。それ以外の場合はy%7 + 1を返します

nが2の場合、使い捨ての可能性は4つあります:y = {22、23、24、25}。n = 6を使用する場合、使い捨ては1つしかありません:y = {15625}。

5 ^ 6 = 15625
7 * 2232 = 15624

rand5をさらに呼び出します。ただし、スローアウェイ値(または無限ループ)を取得する可能性ははるかに低くなります。yの使い捨て可能な値を取得する方法がない場合、私はまだそれを見つけていません。


1
使い捨ての値がない場合はおそらくケースがありません。使い捨てがない場合、5 ^ nと7 ^ mには共通の要素があります。しかし、それらは素数(の力)なので、そうではありません。
レックスカー

8

これが私の答えです:

static struct rand_buffer {
  unsigned v, count;
} buf2, buf3;

void push (struct rand_buffer *buf, unsigned n, unsigned v)
{
  buf->v = buf->v * n + v;
  ++buf->count;
}

#define PUSH(n, v)  push (&buf##n, n, v)

int rand16 (void)
{
  int v = buf2.v & 0xf;
  buf2.v >>= 4;
  buf2.count -= 4;
  return v;
}

int rand9 (void)
{
  int v = buf3.v % 9;
  buf3.v /= 9;
  buf3.count -= 2;
  return v;
}

int rand7 (void)
{
  if (buf3.count >= 2) {
    int v = rand9 ();

    if (v < 7)
      return v % 7 + 1;

    PUSH (2, v - 7);
  }

  for (;;) {
    if (buf2.count >= 4) {
      int v = rand16 ();

      if (v < 14) {
        PUSH (2, v / 7);
        return v % 7 + 1;
      }

      PUSH (2, v - 14);
    }

    // Get a number between 0 & 25
    int v = 5 * (rand5 () - 1) + rand5 () - 1;

    if (v < 21) {
      PUSH (3, v / 7);
      return v % 7 + 1;
    }

    v -= 21;
    PUSH (2, v & 1);
    PUSH (2, v >> 1);
  }
}

他のものより少し複雑ですが、rand5の呼び出しを最小限に抑えると思います。他のソリューションと同様に、長時間ループする可能性はわずかです。


これにより、他のソリューションとほとんど変わらない分布が生成されますが、不必要に複雑になるという追加の欠点があります。また、数値が本当にランダムである場合は、不正確である可能性のある非決定論的ループが永久に続く可能性があります。まだ均一性はやや劣るが(まだ十分とは言えませんが)、確定的な動作が保証される方が良いと思います。
paxdiablo 2009

@Pax:これがどのように不均一な分布を生成するかについて教えてください。私のコードの分析と私自身のテストは、これが均一な分布を生み出すことを示しています。前に説明したように、完全に均一な分布を生成することと、実行時間の一定時間の上限を保証することの両方を行うことは不可能です。
Adam Rosenfield、


6

選択する可能性が7つ残っていない限り、別の乱数を引き、可能性の数に5を掛けます。Perlの場合:

$num = 0;
$possibilities = 1;

sub rand7
{
  while( $possibilities < 7 )
  {
    $num = $num * 5 + int(rand(5));
    $possibilities *= 5;
  }
  my $result = $num % 7;
  $num = int( $num / 7 );
  $possibilities /= 7;
  return $result;
}

少なくとも最初の呼び出しでは、分布は均一ではありません。実際、$possibilitiesループを終了して戻るには、常に25に成長する必要があります。したがって、最初の結果は[0-124] % 7であり、これは125 % 7 != 0(実際には6であるため)均一に分布されていません。
バーナードパウルス2013

6

私は1から始まる範囲が好きではないので、0から始めます:-)

unsigned rand5()
{
    return rand() % 5;
}

unsigned rand7()
{
    int r;

    do
    {
        r =         rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
    } while (r > 15623);

    return r / 2232;
}

これは勝者です。これにより、7つの結果すべてが等しい確率で生成されます。from collections import defaultdict def r7(n): if not n: yield [] else: for i in range(1, 6): for j in r7(n-1): yield [i] + j def test_r7(): d = defaultdict(int) for x in r7(6): s = (((((((((x[5] * 5) + x[4]) * 5) + x[3]) * 5) + x[2]) * 5) + x[1]) * 5) + x[0] if s <= 15623: d[s % 7] += 1 print d
hughdbrown、2010

5

これで、均一な配布とrand5呼び出しはゼロになりました。

def rand7:
    seed += 1
    if seed >= 7:
        seed = 0
    yield seed

事前に種子をセットする必要があります。


5

答えはわかっていますが、問題はないようですが、偏見があるかどうかはわかりません。私の「テスト」は、それが少なくとも合理的であることを示唆しています。

おそらく、アダム・ローゼンフィールドはコメントするのに十分親切でしょうか?

私の(素朴な?)アイデアはこれです:

rand7を作成するのに十分なランダムビットがあるまでrand5を累積します。これには最大2つのrand5が必要です。rand7の数値を取得するには、累積値mod 7を使用します。

アキュムレータのオーバーフローを回避するため、アキュムレータはmod 7であるため、アキュムレータのmod 7を使用します。

(5a + rand5) % 7 = (k*7 + (5a%7) + rand5) % 7 = ( (5a%7) + rand5) % 7

rand7()関数は次のとおりです。

(rand5の範囲を0〜4とし、rand7も同様に0〜6とします。)

int rand7(){
  static int    a=0;
  static int    e=0;
  int       r;
  a = a * 5 + rand5();
  e = e + 5;        // added 5/7ths of a rand7 number
  if ( e<7 ){
    a = a * 5 + rand5();
    e = e + 5;  // another 5/7ths
  }
  r = a % 7;
  e = e - 7;        // removed a rand7 number
  a = a % 7;
  return r;
}

編集:1億回の試行の結果が追加されました。

「実際の」rand関数mod 5または7

rand5:avg = 1.999802 0:20003944 1:19999889 2:20003690 3:19996938 4:19995539 ​​rand7:avg = 3.000111 0:14282851 1:14282879 2:14284554 3:14288546 4:14292388 5:14288736 6:14280046

私のrand7

平均は大丈夫に見え、数値分布も大丈夫に見えます。

randt:avg = 3.000080 0:14288793 1:14280135 2:14287848 3:14285277 4:14286341 5:14278663 6:14292943


おそらく、順次相関を確認する必要があります。連続するペア(それぞれの「ランダムな」番号とその前のペアとのペア)を取ると、驚くべきことがわかると思います。とにかく分布を均一に保つ必要がある理由を説明していません。正常に機能するプログラムは、通常、機能する理由の説明から始めます。
Ian

順次相関はこれらのソリューションの多くに適用されますか?
philcolbourn、2011

順次相関はこれらのソリューションの多くに適用されますか?やってみて久しぶりに説明したと思います。今それを見ると、rand5からプールにランダムなビットを蓄積しているように見えます。rand7番号を作成するのに十分な量を引き出す前に十分に蓄積されていることを確認し、アキュムレータがオーバーフローしないようにしています。
philcolbourn、2011

4

上で引用したエレガントなアルゴリズムがありますが、これは、それを回避する方法の1つです。0から生成された値を想定しています。

R2 = 2未満の値を与える乱数ジェネレーター(サンプルスペース= {0、1})
R8 = 8未満の値を与える乱数ジェネレーター(サンプルスペース= {0、1、2、3、4、5、6、7 })

R2からR8を生成するには、R2を3回実行し、3つすべての実行を組み合わせた結果を3桁の2進数として使用します。R2を3回実行したときの値の範囲は次のとおりです。

0 0 0-> 0


1 1 1-> 7

次に、R8からR7を生成するために、R7が7を返した場合は再度実行するだけです。

int R7() {
  do {
    x = R8();
  } while (x > 6)
  return x;
}

ラウンドアバウト交差点の解決策は、R5からR2を生成し(R8からR7を生成したのと同じように)、R2からR8を生成し、次にR8からR7を生成することです。


他の多くのように、このアプローチはR7から7の長い文字列を取得できるため、R7呼び出しごとに任意の時間がかかる可能性があります。
Alex North-Keys

4

完全に整数に収まり、最適値の約4%以内にある(つまり、{0..6}のすべての要素に対して、{0..4}の1.26の乱数を使用する)ソリューションは次のとおりです。コードはScalaで記述されていますが、数学はどの言語でもかなり明確である必要があります。7^ 9 + 7 ^ 8は5 ^ 11に非常に近いという事実を利用します。したがって、5進数で11桁の数値を選択し、範囲内にある場合は7桁で9桁の数値として解釈し(9桁の7進数を提供)、9桁の数値を超える場合は8桁の数値として解釈します。 。:

abstract class RNG {
  def apply(): Int
}

class Random5 extends RNG {
  val rng = new scala.util.Random
  var count = 0
  def apply() = { count += 1 ; rng.nextInt(5) }
}

class FiveSevener(five: RNG) {
  val sevens = new Array[Int](9)
  var nsevens = 0
  val to9 = 40353607;
  val to8 = 5764801;
  val to7 = 823543;
  def loadSevens(value: Int, count: Int) {
    nsevens = 0;
    var remaining = value;
    while (nsevens < count) {
      sevens(nsevens) = remaining % 7
      remaining /= 7
      nsevens += 1
    }
  }
  def loadSevens {
    var fivepow11 = 0;
    var i=0
    while (i<11) { i+=1 ; fivepow11 = five() + fivepow11*5 }
    if (fivepow11 < to9) { loadSevens(fivepow11 , 9) ; return }
    fivepow11 -= to9
    if (fivepow11 < to8) { loadSevens(fivepow11 , 8) ; return }
    fivepow11 -= to8
    if (fivepow11 < 3*to7) loadSevens(fivepow11 % to7 , 7)
    else loadSevens
  }
  def apply() = {
    if (nsevens==0) loadSevens
    nsevens -= 1
    sevens(nsevens)
  }
}

テストをインタープリターに貼り付けると(実際にはREPL)、次のようになります。

scala> val five = new Random5
five: Random5 = Random5@e9c592

scala> val seven = new FiveSevener(five)
seven: FiveSevener = FiveSevener@143c423

scala> val counts = new Array[Int](7)
counts: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0)

scala> var i=0 ; while (i < 100000000) { counts( seven() ) += 1 ; i += 1 }
i: Int = 100000000

scala> counts
res0: Array[Int] = Array(14280662, 14293012, 14281286, 14284836, 14287188,
14289332, 14283684)

scala> five.count
res1: Int = 125902876

分布は適切で平坦です(ほぼガウス分布から予想されるように、各ビンの10 ^ 8の1/7の約10k以内)。


3

ローリング合計を使用すると、両方を実行できます

  • 均等な分布を維持します。そして
  • ランダムなシーケンスの要素を犠牲にする必要はありません。

これらの問題はどちらも、単純なrand(5)+rand(5)...タイプのソリューションの問題です。次のPythonコードはそれを実装する方法を示しています(これのほとんどはディストリビューションを証明しています)。

import random
x = []
for i in range (0,7):
    x.append (0)
t = 0
tt = 0
for i in range (0,700000):
    ########################################
    #####            qq.py             #####
    r = int (random.random () * 5)
    t = (t + r) % 7
    ########################################
    #####       qq_notsogood.py        #####
    #r = 20
    #while r > 6:
        #r =     int (random.random () * 5)
        #r = r + int (random.random () * 5)
    #t = r
    ########################################
    x[t] = x[t] + 1
    tt = tt + 1
high = x[0]
low = x[0]
for i in range (0,7):
    print "%d: %7d %.5f" % (i, x[i], 100.0 * x[i] / tt)
    if x[i] < low:
        low = x[i]
    if x[i] > high:
        high = x[i]
diff = high - low
print "Variation = %d (%.5f%%)" % (diff, 100.0 * diff / tt)

そしてこの出力は結果を示しています:

pax$ python qq.py
0:   99908 14.27257
1:  100029 14.28986
2:  100327 14.33243
3:  100395 14.34214
4:   99104 14.15771
5:   99829 14.26129
6:  100408 14.34400
Variation = 1304 (0.18629%)

pax$ python qq.py
0:   99547 14.22100
1:  100229 14.31843
2:  100078 14.29686
3:   99451 14.20729
4:  100284 14.32629
5:  100038 14.29114
6:  100373 14.33900
Variation = 922 (0.13171%)

pax$ python qq.py
0:  100481 14.35443
1:   99188 14.16971
2:  100284 14.32629
3:  100222 14.31743
4:   99960 14.28000
5:   99426 14.20371
6:  100439 14.34843
Variation = 1293 (0.18471%)

rand(5)+rand(5)これが6を超える場合を無視して単純化すると、典型的な変動は18%になり、上記の方法の100倍になります。

pax$ python qq_notsogood.py
0:   31756 4.53657
1:   63304 9.04343
2:   95507 13.64386
3:  127825 18.26071
4:  158851 22.69300
5:  127567 18.22386
6:   95190 13.59857
Variation = 127095 (18.15643%)

pax$ python qq_notsogood.py
0:   31792 4.54171
1:   63637 9.09100
2:   95641 13.66300
3:  127627 18.23243
4:  158751 22.67871
5:  126782 18.11171
6:   95770 13.68143
Variation = 126959 (18.13700%)

pax$ python qq_notsogood.py
0:   31955 4.56500
1:   63485 9.06929
2:   94849 13.54986
3:  127737 18.24814
4:  159687 22.81243
5:  127391 18.19871
6:   94896 13.55657
Variation = 127732 (18.24743%)

そして、Nixuzのアドバイスに基づいて、スクリプトをクリーンアップして、rand7...内容を抽出して使用できるようにしました。

import random

# rand5() returns 0 through 4 inclusive.

def rand5():
    return int (random.random () * 5)

# rand7() generator returns 0 through 6 inclusive (using rand5()).

def rand7():
    rand7ret = 0
    while True:
        rand7ret = (rand7ret + rand5()) % 7
        yield rand7ret

# Number of test runs.

count = 700000

# Work out distribution.

distrib = [0,0,0,0,0,0,0]
rgen =rand7()
for i in range (0,count):
    r = rgen.next()
    distrib[r] = distrib[r] + 1

# Print distributions and calculate variation.

high = distrib[0]
low = distrib[0]
for i in range (0,7):
    print "%d: %7d %.5f" % (i, distrib[i], 100.0 * distrib[i] / count)
    if distrib[i] < low:
        low = distrib[i]
    if distrib[i] > high:
        high = distrib[i]
diff = high - low
print "Variation = %d (%.5f%%)" % (diff, 100.0 * diff / count)

2
エラー、言い換えさせてください。特定のxがシーケンスのある時点で生成されたとすると、シーケンスの次の数に対して生成できるのは7つの数字のうち5つだけです。真のRNGではすべてのサンプルが互いに独立していますが、この場合は明らかにそうではありません。
Adam Rosenfield、

3
元の質問では、入力関数と出力関数が独立して同一に分布した(iid)サンプルを生成するかどうかを指定していないことは事実ですが、入力rand5()がiidである場合、出力rand7() iidもする必要があります。それが妥当だと思わない場合は、非iid RNGを使用して楽しんでください。
Adam Rosenfield、

1
それで、大学の数学者からの言葉は何ですか?
Adam Rosenfield、

1
このソリューションは明らかに壊れています。rand7の呼び出しごとにrand5を(平均して)複数回呼び出す必要があることは明らかですが、このソリューションではそうではありません。したがって、ランダムの正しい定義によって結果がランダムになることはありません。
Chris Suter、

1
@Pax関数のすべての反復で、5つの異なる値のうちの1つのみを返すことができます(ただし0から6の範囲です)。最初の反復では、0〜4の範囲の数値のみを返すことができます。したがって、関数が均一に分布している場合でも、サンプルは独立していない、つまり相関関係があるため、乱数ジェネレータで必要なものではないことは明らかです。
Chris Suter、

3

この答えは、Rand5関数から可能な限り最大のエントロピーを得るための実験です。したがって、tはやや不明確であり、ほぼ確実に他の実装よりもかなり低速です。

0から4までの均一分布と、0から6までの均一分布を仮定すると、

public class SevenFromFive
{
  public SevenFromFive()
  {
    // this outputs a uniform ditribution but for some reason including it 
    // screws up the output distribution
    // open question Why?
    this.fifth = new ProbabilityCondensor(5, b => {});
    this.eigth = new ProbabilityCondensor(8, AddEntropy);
  } 

  private static Random r = new Random();
  private static uint Rand5()
  {
    return (uint)r.Next(0,5);
  }

  private class ProbabilityCondensor
  {
    private readonly int samples;
    private int counter;
    private int store;
    private readonly Action<bool> output;

    public ProbabilityCondensor(int chanceOfTrueReciprocal,
      Action<bool> output)
    {
      this.output = output;
      this.samples = chanceOfTrueReciprocal - 1;  
    }

    public void Add(bool bit)
    {
      this.counter++;
      if (bit)
        this.store++;   
      if (counter == samples)
      {
        bool? e;
        if (store == 0)
          e = false;
        else if (store == 1)
          e = true;
        else
          e = null;// discard for now       
        counter = 0;
        store = 0;
        if (e.HasValue)
          output(e.Value);
      }
    }
  }

  ulong buffer = 0;
  const ulong Mask = 7UL;
  int bitsAvail = 0;
  private readonly ProbabilityCondensor fifth;
  private readonly ProbabilityCondensor eigth;

  private void AddEntropy(bool bit)
  {
    buffer <<= 1;
    if (bit)
      buffer |= 1;      
    bitsAvail++;
  }

  private void AddTwoBitsEntropy(uint u)
  {
    buffer <<= 2;
    buffer |= (u & 3UL);    
    bitsAvail += 2;
  }

  public uint Rand7()
  {
    uint selection;   
    do
    {
      while (bitsAvail < 3)
      {
        var x = Rand5();
        if (x < 4)
        {
          // put the two low order bits straight in
          AddTwoBitsEntropy(x);
          fifth.Add(false);
        }
        else
        { 
          fifth.Add(true);
        }
      }
      // read 3 bits
      selection = (uint)((buffer & Mask));
      bitsAvail -= 3;     
      buffer >>= 3;
      if (selection == 7)
        eigth.Add(true);
      else
        eigth.Add(false);
    }
    while (selection == 7);   
    return selection;
  }
}

Rand5の呼び出しごとにバッファに追加されるビット数は、現在4/5 * 2、つまり1.6です。1/5の確率値が含まれている場合、0.05増加するため、1.65ですが、これを無効にする必要があるコード内のコメントを参照してください。

Rand7への呼び出しによって消費されるビット= 3 + 1/8 *(3 + 1/8 *(3 + 1/8 *(...
これは3 + 3/8 + 3/64 + 3/512 ...なので、約3.42

セブンズから情報を抽出することで、1コールあたり1/8 * 1/7ビットを回収するので、約0.018

これにより、1コールあたりの純消費量は3.4ビットになります。つまり、Rand7ごとにRand5への呼び出しが2.125になります。最適値は2.1です。

このアプローチは、Rand5の呼び出しのコストが非常に高くない限り(エントロピーの外部ソースを呼び出すなど)、他の多くのアプローチよりもかなり遅いと思います。


「if(count> 1)」は「if(count <= 1)」であり、その後すぐに発生する「i ++」はその前の波括弧内にあるはずです。BitsSet()が正しいかどうかはわかりませんが、それは多少関係ありません。
Adam Rosenfield、

ただし、全体として、関数の理解は非常に困難です。それは作るん少しより多くの合併症のコストで、それはそうでない可能性よりも、エントロピーをより有効に利用します。また、最初の呼び出しで最初にバッファを35個のランダムビットで埋める理由はありませんが、3個で十分です。
Adam Rosenfield、

私は<=ありがとうを修正しましたが、i ++は本当にそこにあるはずです。これは、ゼロと1のケースで発生するはずです(バッファーにそれぞれ1または0を追加します)。これは絶対に私が使用することを提案するものではなく、恐ろしく複雑です。私は問題に固有の理論的なエントロピー限界にどれだけ近づくことができるかに興味を持っていました...フィードバックをありがとう 皮肉なことに、最初の呼び出しでのバッファの充てんは、書き込みを簡単にすることでした:)
ShuggyCoUk 2009年

私はこれを理解しやすくするために(スピードを犠牲にして)作り直しましたが、それも正しくしました。それはまだ最適ではありません。何らかの理由で、1/5ビットはカウントが均一であっても問題を引き起こします。
ShuggyCoUk 2009年

3

PHPで

function rand1to7() {
    do {
        $output_value = 0;
        for ($i = 0; $i < 28; $i++) {
            $output_value += rand1to5();
        }
    while ($output_value != 140);
    $output_value -= 12;
    return floor($output_value / 16);
}

ループして16から127までの乱数を生成し、16で除算して1から7.9375までの浮動小数点数を作成し、次に切り捨てて1から7までの整数を取得します。間違いがない場合は、16/112の確率で7つの結果のいずれか。


おそらく、条件付きループを使用せず、床の代わりにモジュロを使用するこれに似たより簡単な答えがあります。私は今、数を数えることができません。
dqhendricks、2011

3
extern int r5();

int r7() {
    return ((r5() & 0x01) << 2 ) | ((r5() & 0x01) << 1 ) | (r5() & 0x01);
}

問題:これは0-6ではなく0-7の範囲で不均一に戻ります。確かに、あなたが持つことができる7 = 111bp(7) = 8 / 125
ベルナールパウルス

3

私は4つの答えがあると思います。2つは、@ Adam Rosenfieldのような正確なソリューションを提供しますが、無限ループの問題はありません。他の2つは、ほぼ完全なソリューションですが、最初のものよりも実装が高速です。

最良の正確なソリューションには、7回の呼び出しが必要ですrand5が、理解するために先に進みましょう。

方法1-完全

Adamの答えの強みは、それが完全に均一な分布を与えることであり、rand5()への2つの呼び出しのみが必要となる可能性が非常に高い(21/25)です。ただし、最悪のケースは無限ループです。

以下の最初のソリューションでも完全に均一な分布が得られますが、への合計42回の呼び出しが必要rand5です。無限ループはありません。

Rの実装は次のとおりです。

rand5 <- function() sample(1:5,1)

rand7 <- function()  (sum(sapply(0:6, function(i) i + rand5() + rand5()*2 + rand5()*3 + rand5()*4 + rand5()*5 + rand5()*6)) %% 7) + 1

Rに慣れていない人のために、ここに簡略化したバージョンがあります。

rand7 = function(){
  r = 0 
  for(i in 0:6){
    r = r + i + rand5() + rand5()*2 + rand5()*3 + rand5()*4 + rand5()*5 + rand5()*6
  }
  return r %% 7 + 1
}

の配布はrand5保持されます。計算を行うと、ループの7回の反復にはそれぞれ5 ^ 6の可能な組み合わせがあるため、可能な組み合わせの総数はになります(7 * 5^6) %% 7 = 0。したがって、生成された乱数を7の等しいグループに分割できます。これについての詳細は、方法2を参照してください。

可能なすべての組み合わせは次のとおりです。

table(apply(expand.grid(c(outer(1:5,0:6,"+")),(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6),1,sum) %% 7 + 1)

    1     2     3     4     5     6     7 
15625 15625 15625 15625 15625 15625 15625 

Adamの方法がはるかに高速に実行されることを示すのは簡単です。rand5Adamのソリューションで42以上の呼び出しがある確率は非常に小さいです((4/25)^21 ~ 10^(-17))。

方法2-正確ではない

2つ目の方法はほぼ均一ですが、次の6つの呼び出しが必要rand5です。

rand7 <- function() (sum(sapply(1:6,function(i) i*rand5())) %% 7) + 1

簡略版は次のとおりです。

rand7 = function(){
  r = 0 
  for(i in 1:6){
    r = r + i*rand5()
  }
  return r %% 7 + 1
}

これは基本的に方法1の1回の反復です。考えられるすべての組み合わせを生成すると、結果のカウントが次のようになります。

table(apply(expand.grid(1:5,(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6),1,sum) %% 7 + 1)

   1    2    3    4    5    6    7 
2233 2232 2232 2232 2232 2232 2232

5^6 = 15625トライアルでは1つの番号がもう一度表示されます。

ここで、方法1で1に6を追加して、2233を連続する各ポイントに移動します。したがって、組み合わせの総数は一致します。これは、5 ^ 6 %% 7 = 1であり、7つの適切なバリエーションを実行するため機能します(7 * 5 ^ 6 %% 7 = 0)。

方法3-完全

メソッド1と2の引数が理解されると、メソッド3が続き、への呼び出しは7回だけ必要rand5です。この時点で、これは正確なソリューションに必要な最小呼び出し数だと思います。

Rの実装は次のとおりです。

rand5 <- function() sample(1:5,1)

rand7 <- function()  (sum(sapply(1:7, function(i) i * rand5())) %% 7) + 1

Rに慣れていない人のために、ここに簡略化したバージョンがあります。

rand7 = function(){
  r = 0 
  for(i in 1:7){
    r = r + i * rand5()
  }
  return r %% 7 + 1
}

の配布はrand5保持されます。計算を行うと、ループの7回の反復にはそれぞれ5つの可能な結果があるため、可能な組み合わせの総数はになります(7 * 5) %% 7 = 0。したがって、生成された乱数を7の等しいグループに分割できます。これについての詳細は、方法1および2を参照してください。

可能なすべての組み合わせは次のとおりです。

table(apply(expand.grid(0:6,(1:5)),1,sum) %% 7 + 1)

1 2 3 4 5 6 7  
5 5 5 5 5 5 5 

Adamの方法がより速く実行されることを示すのは簡単です。rand5Adamのソリューションで7つ以上の呼び出しがある確率はまだ小さいです((4/25)^3 ~ 0.004)。

方法4-正確ではない

これは2番目の方法のマイナーバリエーションです。ほぼ同じrand5ですが、への7回の呼び出しが必要です。これは、方法2の追加です。

rand7 <- function() (rand5() + sum(sapply(1:6,function(i) i*rand5())) %% 7) + 1

簡略版は次のとおりです。

rand7 = function(){
  r = 0 
  for(i in 1:6){
    r = r + i*rand5()
  }
  return (r+rand5()) %% 7 + 1
}

考えられるすべての組み合わせを生成した場合、結果のカウントは次のようになります。

table(apply(expand.grid(1:5,(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6,1:5),1,sum) %% 7 + 1)

    1     2     3     4     5     6     7 
11160 11161 11161 11161 11161 11161 11160

5^7 = 78125トライアルでは、2つの数字が1度少なく表示されます。ほとんどの目的で、私はそれと共存できます。


1
私はRに精通していませんが、これらがどのように機能するかを誤解していない限り、方法1は正確ではありません。(5 ^ 6)^ 7 =ではなく、(5 ^ 6)^ 7 = 5 ^ 42の可能な結果があります。5 ^ 42は7で割り切れません。同様に、方法3は正確ではありません。5 * 7ではなく、5 ^ 7の可能な結果があります。(にi=7追加7*rand5()rてもrmod 7 の値は変更されないため、メソッド3の最後のループ反復も効果がありません。)
Adam Rosenfield '31

2

必要な関数はrand1_7()です。テストしてプロットできるようにrand1_5()を作成しました。

import numpy
def rand1_5():
    return numpy.random.randint(5)+1

def rand1_7():
    q = 0
    for i in xrange(7):  q+= rand1_5()
    return q%7 + 1
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.