1から5の範囲のランダムな整数を生成する関数を指定して、1から7の範囲のランダムな整数を生成する関数を記述します。
- シンプルなソリューションとは何ですか?
- メモリ使用量を減らす、またはより遅いCPUで実行するための効果的なソリューションは何ですか?
7 * rand5() / 5
ですか?
1から5の範囲のランダムな整数を生成する関数を指定して、1から7の範囲のランダムな整数を生成する関数を記述します。
7 * rand5() / 5
ですか?
回答:
これは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インデックスはダーツボード上の場所をランダムに選択し、良好な結果が得られない場合はダーツを投げ続けます。
アダムが言ったように、これは最悪の場合に永遠に実行することができますが、統計的に最悪のケースは決して起こりません。:)
rand5
均一の場合、vals
グリッド内のすべてのセルが選択される確率が等しくなります。グリッドには、区間[1、7]の各整数の正確に3つのコピーと、4つのゼロが含まれます。そのため、結果の「生の」ストリームは、[1、7]値と、個々の許容値よりも少し頻繁に発生するいくつかのゼロの偶数混合の傾向があります。しかし、ゼロは取り除かれ、[1、7]値の偶数の混合を残すため、それは問題ではありません。
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反復の予想ランタイムがありますが、無限にループする可能性は無限に小さいです。
N
呼び出しを行うだけであることが保証された一定の時間で実行されるソリューションがあったとしましょうrand5()
。次に、への呼び出しのシーケンスの5 ^ Nの可能な結果がありrand5
、それぞれの出力は1〜7です。したがって、k
1≤k≤7ごとに出力される呼び出しのすべての可能なシーケンスを合計すると、出力k
がm / 5 ^ Nになる確率はm です(mはそのようなシーケンスの数)。したがって、m / 5 ^ N = 1/7ですが、これに対する可能な整数解(N、m)はありません==>矛盾。
最初の回答に加えて、別の回答を追加したいと思います。この回答は、への呼び出し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. a
1 a
2 a
3 ...をi
選択しrand5()
ます。各数字a は、への呼び出しによって選択されます。たとえば、RNGがa i
= 1 for allを選択した場合i
、それがあまりランダムではないという事実を無視すると、それは実数1/5 + 1/5 2 + 1/5 3 + ... =に対応します1/4(幾何学的系列の合計)。
わかりましたので、0から1の間のランダムな実数を選びました。そのような乱数は均一に分布していると主張します。直感的には、各桁が均一に選択され、数が無限に正確であるため、これは理解しやすいです。ただし、ここでは離散分布ではなく連続分布を扱っているため、これの正式な証明はやや複雑です。したがって、数が区間[ a
、b
]にある確率がの長さに等しいことを証明する必要があります。その間隔、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つの方法は次のとおりです。
無限の精度の問題に対処するために、部分的な結果を計算し、結果の上限を保存します。つまり、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ビットの整数であっても、あまり増加しないはずです。
(私は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()
5 * rand5() + rand5()
です。
アルゴリズム:
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;
}
}
}
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 );
}
}
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
想定して発生する可能性のある方法の数です。n
rand5
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]
int ans = 0;
while (ans == 0)
{
for (int i=0; i<3; i++)
{
while ((r = rand5()) == 3){};
ans += (r < 3) >> i
}
}
ans += (r < 3) << i
以下は、乱数ジェネレータを使用して{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;
}
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)
ここで宿題の問題は許されますか?
この関数は、粗い「ベース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;
}
最も効率的な答えを出そうとするという追加の制約を考慮すると、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の入力ストリームから1
to までの整数へ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
。
これをカバーする多くの理論がそこにあると私は確信しています。
これは、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に投げて、それらをいじくり回したいのですが、一緒に投げるのに時間がかかったのではなく、誰かに役立つことを期待してここに投稿したいと思いました。
なぜそれを簡単にしないのですか?
int random7() {
return random5() + (random5() % 3);
}
このソリューションで1と7を取得する可能性は、モジュロのために低くなりますが、迅速で読みやすいソリューションが必要な場合は、これが適しています。
ここで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
do ... while
。それはされている可能性1337
、または12345
、または任意の数> 1
Adam Rosenfieldの正解の背後にある前提は、次のとおりです。
nが2の場合、使い捨ての可能性は4つあります:y = {22、23、24、25}。n = 6を使用する場合、使い捨ては1つしかありません:y = {15625}。
5 ^ 6 = 15625
7 * 2232 = 15624
rand5をさらに呼び出します。ただし、スローアウェイ値(または無限ループ)を取得する可能性ははるかに低くなります。yの使い捨て可能な値を取得する方法がない場合、私はまだそれを見つけていません。
これが私の答えです:
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の呼び出しを最小限に抑えると思います。他のソリューションと同様に、長時間ループする可能性はわずかです。
シンプルで効率的:
int rand7 ( void )
{
return 4; // this number has been calculated using
// rand5() and is in the range 1..7
}
選択する可能性が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であるため)均一に分布されていません。
私は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;
}
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
答えはわかっていますが、問題はないようですが、偏見があるかどうかはわかりません。私の「テスト」は、それが少なくとも合理的であることを示唆しています。
おそらく、アダム・ローゼンフィールドはコメントするのに十分親切でしょうか?
私の(素朴な?)アイデアはこれです:
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
上で引用したエレガントなアルゴリズムがありますが、これは、それを回避する方法の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を生成することです。
完全に整数に収まり、最適値の約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以内)。
ローリング合計を使用すると、両方を実行できます
これらの問題はどちらも、単純な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)
この答えは、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の呼び出しのコストが非常に高くない限り(エントロピーの外部ソースを呼び出すなど)、他の多くのアプローチよりもかなり遅いと思います。
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つの結果のいずれか。
extern int r5();
int r7() {
return ((r5() & 0x01) << 2 ) | ((r5() & 0x01) << 1 ) | (r5() & 0x01);
}
7 = 111b
とp(7) = 8 / 125
私は4つの答えがあると思います。2つは、@ Adam Rosenfieldのような正確なソリューションを提供しますが、無限ループの問題はありません。他の2つは、ほぼ完全なソリューションですが、最初のものよりも実装が高速です。
最良の正確なソリューションには、7回の呼び出しが必要ですrand5
が、理解するために先に進みましょう。
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の方法がはるかに高速に実行されることを示すのは簡単です。rand5
Adamのソリューションで42以上の呼び出しがある確率は非常に小さいです((4/25)^21 ~ 10^(-17)
)。
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)。
メソッド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の方法がより速く実行されることを示すのは簡単です。rand5
Adamのソリューションで7つ以上の呼び出しがある確率はまだ小さいです((4/25)^3 ~ 0.004
)。
これは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度少なく表示されます。ほとんどの目的で、私はそれと共存できます。
i=7
追加7*rand5()
しr
てもr
mod 7 の値は変更されないため、メソッド3の最後のループ反復も効果がありません。)