1から5の範囲の乱数整数を生成する関数を考えて、1から7の範囲の乱数整数を生成する関数を書いてください。
これは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インデックスはランダムにDartボード上の場所を選択します、そして、もし我々が良い結果が得られないならば、我々はダーツを投げ続けます。
アダムが言ったように、これは最悪のケースで永遠に続くことができますが、統計的に最悪のケースは決して起こりません。 :)
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反復の予想実行時間を持ちますが、無限にループする可能性は無限に小さいです。
私の最初の答え に加えて、別の答えを追加したいのですが。この答えは、ランダム性を最大限に利用するために、Rand5()
の呼び出しごとのRand7()
の呼び出し数を最小限に抑えることを目的としています。つまり、ランダム性を貴重なリソースと見なしている場合は、ランダムなビットを捨てずにできるだけ多くのリソースを使用します。この答えは、 Ivanの答え に提示されている論理といくつかの類似点も持っています。
確率変数の エントロピー は、明確に定義された量です。等しい確率(一様分布)でN個の状態をとる確率変数の場合、エントロピーはlogです。2 したがって、Rand5()
は約2.32193ビットのエントロピーを持ち、Rand7()
は約2.80735ビットのエントロピーを持ちます。ランダム性を最大限に利用したい場合は、Rand5()
の呼び出しごとに2.32193ビットのエントロピーをすべて使用し、Rand7()
の呼び出しごとに必要な2.80735ビットのエントロピーを生成するためにそれらを適用する必要があります。したがって、根本的な限界は、Rand5()
の呼び出しごとにRand7()
を呼び出すlog(7)/ log(5)= 1.20906を超えることができないことです。
補足:この回答のすべての対数は、特に指定がない限り2の基数になります。 Rand5()
は[0、4]の範囲の数値を返すものとし、Rand7()
は[0、6]の範囲の数値を返すものとします。範囲を[1、5]と[1、7]にそれぞれ調整するのは簡単です。
じゃあどうやってやるの? 0から1までの無限精度の乱数実数を生成します(このような無限精度の数値を実際に計算して格納できるようにします - 後で修正します)。 。基数5で数字を生成することでこのような数を生成することができます。乱数0を選択します。a
1a
2a
3...、各桁はi
Rand5()
の呼び出しによって選択されます。たとえば、私たちのRNGがi
すべてのi
に対して= 1を指定しますが、それがあまりランダムではないという事実を無視します。これは、実数1/5 + 1/5に対応します。2 + 1/53 + ... = 1/4(幾何学的級数の合計)。
わかりました、だから私たちは0と1の間のランダムな実数を選びました。私は今そのような乱数が一様に分布していると主張します。直感的に、これは理解しやすいです、なぜなら各ディジットは一様に選ばれた、そして数は無限に正確です。しかし、これについての正式な証明はやや複雑です。今は離散的な分布ではなく連続的な分布を扱っているので、私たちの数が区間[a
、b
]にある確率は次のようになることを証明する必要があります。その区間の長さ、b - a
。証明は読者のための演習として残されています=)。
[0、1]の範囲から一様にランダムな実数が選択されたので、Rand7()
の出力を生成するためにそれを[0、6]の範囲の一様乱数の列に変換する必要があります。どうやってこれをするのですか?ちょうどそれを逆にしました - それを7進法で無限に正確な10進数に変換します、そして7進法の各桁は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桁の無限ストリームが何であるかに関係なく、それらは基数7表現の次のk
桁に影響を与えることは決してありません!
それがアルゴリズムです - Rand7()
の次の出力を生成するには、変換時の次の桁の値を確実に知るために必要な数のRand5()
だけを生成します。テスト用ハーネスを使った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 __== '__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()
を10000回呼び出して10000個の乱数を生成し、その分布を測定します。これは、next(r7)
が生成器を返すことに注意してください。整数演算のみが使用されるので、結果は正確に正しいです。
また、ここでの数は非常に大きく、非常に速くなることにも注意してください。 5と7の力は急速に成長します。そのため、2進数演算により、乱数をたくさん生成した後でパフォーマンスが著しく低下し始めます。しかし、ここで覚えておいてください、私の目標はパフォーマンスを最大にすることではなく、ランダムビットの使用を最大にすることでした(それは副次的な目標です)。
この1回の実行で、Rand5()
への10000回の呼び出しに対してRand7()
への12091回の呼び出しを行い、log(7)/ log(5)呼び出しの平均最小4有効数字を達成しました。出力は均一でした。
このコードを任意の大きさの整数が組み込まれていない言語に移植するには、pow5
とpow7
の値をネイティブ整数型の最大値に制限する必要があります。それからすべてをリセットして最初からやり直してください。これにより、Rand5()
の呼び出しごとの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から最大の%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()
アルゴリズム:
7は3ビットのシーケンスで表すことができます。
Rand(5)を使用して、各ビットを0または1でランダムに埋めます。
例えば:Rand(5)に電話して
結果が1または2の場合は、ビットを0で埋めます。
結果が4または5の場合、ビットを1で埋めます。
結果が3の場合は、無視してもう一度実行します(拒否)。
このようにして、3つのビットをランダムに0/1で埋めて、1から7までの数字を得ることができます。
EDIT:これは最も単純で最も効率的な答えのように思われるので、ここにいくつかのコードを示します。
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]
は、n
をoutput=x
に呼び出したときに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
}
}
以下は、{1、2、3、4、5}上に一様分布を生成する乱数ジェネレータを使用して、{1、2、3、4、5、6、7}上に一様分布を生成します。コードは面倒ですが、論理は明らかです。
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;
}
最も効率的な答え、つまり1から5までの長さI
の一様分布整数の入力ストリームm
を与えようとするという追加の制約を考えると、ストリームO
[m
に相対的な最長の1から7までの一様に分布する整数の_、例えばL(m)
。
これを分析する最も簡単な方法は、ストリームIとO
をそれぞれ5進数と7進数として扱うことです。これは、ストリームa1, a2, a3,... -> a1+5*a2+5^2*a3+..
を取得するという主な回答の考え方、およびストリームO
の場合と同様に実現されます。
それでは、長さm choose n s.t. 5^m-7^n=c
の入力ストリームの一部を取る場合、ここでc>0
はできるだけ小さいです。次に、長さmの入力ストリームから1
から5^m
の整数へのユニフォームマップと、長さnの出力ストリームへの1から7^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)
をサイズX
の一様な入力から派生したrandom(1-7)
整数の出力シーケンスの平均長とし、その5^m=7^n0+7^n1+7^n2+...+7^nr+s, s<7
とします。
確率7 ^ n0/5 ^ mの長さのないシーケンスと確率5^m-7^n0
の長さ(5^m-7^n0)/5^m)
の残差があるので、T7(5^m)=n0x7^n0/5^m + ((5^m-7^n0)/5^m) T7(5^m-7^n0)
となります。
代入し続けるだけで、次のようになります。
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)
可能な限り最高のケースは、5^m=7^n+s
、s<7
の上にある私のオリジナルのケースです。
それからT7(5^m) = nx(7^n)/(7^n+s) = n+o(1) = m (Log5/Log7)+o(1)
は以前と同じです。
最悪の場合は、kとsしか見つけられない場合です。t5 ^ 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)
を証明することができます。
全体的なことは、m
のさまざまな値に対する7桁の5^m
の配布にかかっています。
これをカバーする理論がたくさんあると確信しています。ある時点で見て報告することができます。
宿題の問題はここで許可されていますか?
この関数は、0から6までの数を生成するために、大まかな「基数5」の計算を行います。
function rnd7() {
do {
r1 = rnd5() - 1;
do {
r2=rnd5() - 1;
} while (r2 > 1);
result = r2 * 5 + r1;
} while (result > 6);
return result + 1;
}
なぜそれを簡単にしないのですか?
int random7() {
return random5() + (random5() % 3);
}
このソリューションで1と7を取得する可能性はモジュロのために低くなります、しかしあなたがただ速くて読みやすいソリューションを望むならば、これは行くべき道です。
これは 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 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生成器を得ることができます。 0から15,624までの数字は、Rand5を6回回転させ、それらを使用して次のように基数5の数字を形成することによって、一様に生成することができます。
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に向かう小さなバイアスが導入されます。
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への呼び出しを最小にすると思います。他のソリューションと同様に、それが長時間ループする可能性はわずかにあります。
ここで、Rand(n)は、 "(からnまでの一様分布の乱数-1 "、これはPythonのrandintを使ったコードサンプルです。 randint(7)の効果を生成するために、randint(5)と定数のみを使用します。ちょっとばかげて、実際に
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
シンプルで効率的
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;
}
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;
}
あなたはそこに行きます、一様分布とゼロRand5呼び出し。
def Rand7:
seed += 1
if seed >= 7:
seed = 0
yield seed
事前に種を設定する必要があります。
私はそれが答えられたことを知っています、しかしこれはうまくいくように思えます、しかしそれがバイアスを持っているかどうか私はあなたに言うことができません。私の「テスト」は、少なくともそれが妥当であることを示唆しています。
おそらくAdam Rosenfieldがコメントに十分親切でしょうか?
私の(素朴な?)考えはこれです:
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億件の試験の結果を追加しました。
'Real' 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:14882336 6:14280046
私のRand7
平均は大丈夫に見え、数分布も大丈夫に見えます。
randt:avg = 3.000080 0:14288793 1:14280135 2:14287848 3:14285277 4:14286341 5:14278663 6:14292943
これは完全に整数内に収まり、最適値の約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以内)。
上で引用したエレガントなアルゴリズムがありますが、これはアプローチする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を生成するために、それが7を返したらR7をもう一度実行します。
int R7() {
do {
x = R8();
} while (x > 6)
return x;
}
ロータリーの解決策は、R5からR2を生成し(R8からR7を生成したように)、次にR2からR8を生成し、次にR8からR7を生成することです。
積算合計を使用すると、両方を実行できます。
これらの問題はどちらも、単純化された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%)
これが6を超える値を返すような場合を無視した、単純なRand(5)+Rand(5)
では、通常の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)
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の間の乱数を生成するためにループし、1と7.9375の間のfloatを作成するために16で割り、それから1と7の間のintを得るために切り捨てます。 7つの結果のうちの1つ。
この答えは、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
7から情報を抽出することによって、私は1コールあたり1/8 * 1/7ビットを再生するので、約0.018
これにより、1コールあたり3.4ビットの純消費量が得られます。これは、Rand7ごとにRand5に対して2.125コールであることを意味します。最適値は2.1です。
私はこの方法がここで他の多くのものよりかなり遅いと想像するでしょう。エントロピーの外部ソース).
extern int r5();
int r7() {
return ((r5() & 0x01) << 2 ) | ((r5() & 0x01) << 1 ) | (r5() & 0x01);
}
Rand25() =5*(Rand5()-1) + Rand5()
Rand7() {
while(true) {
int r = Rand25();
if (r < 21) return r%3;
}
}
これがなぜ機能するのか:ループが永遠に実行される確率は0です。
あなたが必要とする関数は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
最初の関数から出力を拡大縮小するだけ
0) you have a number in range 1-5
1) subtract 1 to make it in range 0-4
2) multiply by (7-1)/(5-1) to make it in range 0-6
3) add 1 to increment the range: Now your result is in between 1-7
値0〜7の場合、以下のものがあります。
0 000
1 001
2 010
3 011
4 100
5 101
6 110
7 111
左から右へビットごとに、Rand5()はp(1) = {2/5、2/5、3/5}を持ちます。したがって、それらの確率分布(〜Rand5())を補完すれば、それを使用して自分の数値を生成できるはずです。解決策を挙げて後で報告します。誰か何か考えがありますか?
R
int getOneToSeven(){
int added = 0;
for(int i = 1; i<=7; i++){
added += getOneToFive();
}
return (added)%7+1;
}
ここではRand(n) -> [0, n - 1]
という表記法を使っています
私が読んだ答えの多くから、それらは統一性または停止保証を提供しますが、両方を提供しません(adam rosenfeldの第2の答えはあるかもしれません)。
しかし、それは可能です。私たちは基本的にこの分布をしています:
これは私達に[0-6]
上の分布に穴を残します:5と6は発生する可能性がありません。今、確率分布をシフトして合計することによって穴を埋めようとしていると想像してください。
確かに、初期分布をそれ自体で1つシフトし、得られた分布を2つシフトした後、3つずつシフトして7まで繰り返すことによって繰り返すことができます(範囲全体をカバーします)。これを次の図に示します。色の順序は、ステップに対応して、青 - >緑 - >シアン - >白 - >マゼンタ - >黄色 - >赤です。
各スロットは7つのシフトされた分布のうちの5つでカバーされ(シフトは0から6まで変化します)、そして乱数はran5()
の呼び出しから独立していると仮定するので、
p(x) = 5 / 35 = 1 / 7 for all x in [0, 6]
これは、ran5()
からの7つの独立した乱数が与えられると、[0-6]
の範囲で一様な確率で乱数を計算できることを意味します。実際、ran5()確率分布は、サンプルが独立している限り、均一である必要すらありません(したがって、分布は試行ごとに同じままです)。また、これは5と7以外の数値にも有効です。
これは私たちに次のpython関数を与えます:
def Rand_range_transform(rands):
"""
returns a uniform random number in [0, len(rands) - 1]
if all r in rands are independent random numbers from the same uniform distribution
"""
return sum((x + i) for i, x in enumerate(rands)) % len(rands) # a single modulo outside the sum is enough in modulo arithmetic
これは次のように使用できます。
Rand5 = lambda : random.randrange(5)
def Rand7():
return Rand_range_transform([Rand5() for _ in range(7)])
Rand7()
を7万回呼び出すと、次のようになります。
max: 6 min: 0 mean: 2.99711428571 std: 2.00194697049
0: 10019
1: 10016
2: 10071
3: 10044
4: 9775
5: 10042
6: 10033
完璧ではありませんが、これは良いことです。実際には、この実装では想定の1つが誤っている可能性があります。PRNGを使用するため、次の呼び出しの結果は最後の結果に依存します。
そうは言っても、真にランダムな数のソースを使用すると、出力も真にランダムになるはずです。そして、このアルゴリズムはどの場合でも終了します。
しかし、これにはコストが伴います。単一のRand5()
呼び出しにはRand7()
への7回の呼び出しが必要です。
私は4つの答えを持っていると思います。2つは厳密な解決策を与えています @ Adam Rosenfieldのように 無限ループの問題はありません。
最も正確な解決策はRand5
への7回の呼び出しを必要とします、しかし理解するために先に進みましょう。
Adamの答えの強みは、それが完全に一様な分布を与えることであり、Rand5()への2回の呼び出しだけが必要とされる可能性が非常に高い(21/25)ということです。ただし、最悪の場合は無限ループです。
以下の最初の解決策も完全に一様な分布を与えますが、Rand5
への合計42の呼び出しを必要とします。無限ループはありません。
これが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の方法がはるかに速く実行されることを示すのは簡単なことだと思います。 AdamのソリューションでRand5
への42以上の呼び出しがある可能性は非常に小さいです((4/25)^21 ~ 10^(-17)
)。
2番目の方法は、ほぼ一様ですが、Rand5
を6回呼び出す必要があります。
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に6を加えることによって、2233という数字を連続する各点に移動します。したがって、組み合わせの総数は一致します。これは5 ^ 6 %% 7 = 1なので、7つの適切な変形をします(7 * 5 ^ 6 %% 7 = 0)。
方法1と2の引数が理解されると、方法3が続き、Rand5
への7回の呼び出しのみが必要になります。現時点で、私はこれが正確な解決のために必要とされる呼び出しの最小数であると感じます。
これが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の方法がまだ速く実行されることを示すのは簡単だと思います。 AdamのソリューションでRand5
への7回以上の呼び出しがある可能性はまだ小さいです((4/25)^3 ~ 0.004
)。
これは2番目の方法のマイナーなバリエーションです。ほぼ一様ですが、Rand5
を7回呼び出す必要があります。これは、方法2の追加1つです。
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回少なくなります。ほとんどの目的のために、私はそれと共に生きることができます。
この解はエントロピーを無駄にせず、範囲内で最初に利用可能な真の乱数を与えます。 各反復で、答えが得られない確率は明らかに減少します。N回の反復で答えが得られる確率は、0から0までの乱数である確率です。そして、max(5 ^ N)はその範囲の最大の7の倍数よりも小さくなります(max-max%7)。少なくとも2回繰り返す必要があります。しかし、それはすべてのソリューションに必ず当てはまります。
int random7() {
range = 1;
remainder = 0;
while (1) {
remainder = remainder * 5 + random5() - 1;
range = range * 5;
limit = range - (range % 7);
if (remainder < limit) return (remainder % 7) + 1;
remainder = remainder % 7;
range = range % 7;
}
}
数値的には次のものと同等です。
r5=5;
num=random5()-1;
while (1) {
num=num*5+random5()-1;
r5=r5*5;
r7=r5-r5%7;
if (num<r7) return num%7+1;
}
最初のコードはモジュロ形式でそれを計算します。 2番目のコードは単なる数学です。それともどこかでミスをしました。 :-)
package CareerCup;
public class RangeTransform {
static int counter = (int)(Math.random() * 5 + 1);
private int func() {
return (int) (Math.random() * 5 + 1);
}
private int getMultiplier() {
return counter % 5 + 1;
}
public int rangeTransform() {
counter++;
int count = getMultiplier();
int mult = func() + 5 * count;
System.out.println("Mult is : " + 5 * count);
return (mult) % 7 + 1;
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
RangeTransform rangeTransform = new RangeTransform();
for (int i = 0; i < 35; i++)
System.out.println("Val is : " + rangeTransform.rangeTransform());
}
}
Rand
がすべてのビットに等しい重みを与えると仮定すると、上限でマスクされます。
int i = Rand(5) ^ (Rand(5) & 2);
Rand(5)
は1b
、10b
、11b
、100b
、101b
のみを返すことができます。あなたは時々2ビットをセットすることであなた自身を心配する必要があるだけです。
function Rand7
put 200 into x
repeat while x > 118
put ((random(5)-1) * 25) + ((random(5)-1) * 5) + (random(5)-1) into x
end repeat
return (x mod 7) + 1
end Rand7
Rand5への3回の呼び出しは、平均して125回のうち6回だけ繰り返されます。
それを1〜7回繰り返し、6個の空白で埋められた5×5×5の3D配列と考えてください。ブランクを巻き直します。 Rand5呼び出しは、その配列に3桁の5進数のインデックスを作成します。
4D以上のN次元配列での繰り返しは少なくなりますが、これはRand5関数への呼び出しが標準化されることを意味します。高次元では効率の低下が見られます。 3つは私にとっては良い妥協案のように思えますが、念のために互いに対してそれらをテストしていません。そしてそれはRand5実装固有のものです。
なぜこれがうまくいかないのですか?他にRand5()への1つの追加呼び出し?
i = Rand5() + Rand5() + (Rand5() - 1) //Random number between 1 and 14
i = i % 7 + 1;
これは、他人の答えを検討した後に作成できる最も簡単な答えです。
def r5tor7():
while True:
cand = (5 * r5()) + r5()
if cand < 27:
return cand
cand
は[6、27]の範囲にあり、r5()からの可能な結果が均等に分布している場合、可能な結果は均等に分布しています。あなたはこのコードで私の答えをテストすることができます:
from collections import defaultdict
def r5_outcome(n):
if not n:
yield []
else:
for i in range(1, 6):
for j in r5_outcome(n-1):
yield [i] + j
def test_r7():
d = defaultdict(int)
for x in r5_outcome(2):
s = sum([x[i] * 5**i for i in range(len(x))])
if s < 27:
d[s] += 1
print len(d), d
r5_outcome(2)
は、r5()の結果のすべての可能な組み合わせを生成します。私は自分のソリューションコードと同じフィルタをテストに使います。あなたは、すべての結果が同様におそらく同じ価値を持っているからであることがわかります。
これが私が見つけたものです:
それから私達が探しているRandom7である1〜7の範囲を得ます。
これが私の一般的な実装です、範囲[0、B-1]の一様ジェネレータを与えて範囲[0、N-1]のユニフォームを生成します。
public class RandomUnif {
public static final int BASE_NUMBER = 5;
private static Random Rand = new Random();
/** given generator, returns uniform integer in the range 0.. BASE_NUMBER-1
public static int randomBASE() {
return Rand.nextInt(BASE_NUMBER);
}
/** returns uniform integer in the range 0..n-1 using randomBASE() */
public static int randomUnif(int n) {
int Rand, factor;
if( n <= 1 ) return 0;
else if( n == BASE_NUMBER ) return randomBASE();
if( n < BASE_NUMBER ) {
factor = BASE_NUMBER / n;
do
Rand = randomBASE() / factor;
while(Rand >= n);
return Rand;
} else {
factor = (n - 1) / BASE_NUMBER + 1;
do {
Rand = factor * randomBASE() + randomUnif(factor);
} while(Rand >= n);
return Rand;
}
}
}
壮観ではないが一般的でコンパクト。基本ジェネレータへの平均呼び出し:
n calls
2 1.250
3 1.644
4 1.252
5 1.000
6 3.763
7 3.185
8 2.821
9 2.495
10 2.250
11 3.646
12 3.316
13 3.060
14 2.853
15 2.650
16 2.814
17 2.644
18 2.502
19 2.361
20 2.248
21 2.382
22 2.277
23 2.175
24 2.082
25 2.000
26 5.472
27 5.280
28 5.119
29 4.899
(Rand5() + Rand5()) % 7 + 1
Yes, this is effective as it calls Rand5() only twice and have O(1) space complexity
Rand5()
が1から5までの乱数を与えると考えてください。
(1 + 1)%7 + 1 = 3
(1 + 2)%7 + 1 = 4
(1 + 3)%7 + 1 = 5
(1 + 4)%7 + 1 = 6
(1 + 5)%7 + 1 = 7
(2 + 1)%7 + 1 = 4
(2 + 2)%7 + 1 = 5
(2 + 3)%7 + 1 = 6
(2 + 4)%7 + 1 = 7
(2 + 5)%7 + 1 = 1
...
(5 + 1)%7 + 1 = 7
(5 + 2)%7 + 1 = 1
(5 + 3)%7 + 1 = 2
(5 + 4)%7 + 1 = 3
(5 + 5)%7 + 1 = 4
...
等々
まず頭に浮かんだのはこれです。しかし、私はそれが一様に分布しているかどうかわかりません。 pythonで実装されています
ランダムにインポート
def Rand5():
random.randint(1,5)を返します
def Rand7():
return((((Rand5()-1)* Rand5())%7)+1
フロートレンジを広げることからのリンクからここに来ました。これはもっと楽しいです。私が結論を出した方法の代わりに、 "base"b(4の場合、私はその理由を教えてくれる)を持つ与えられたランダム整数生成関数f
に対して私はそれを思いつきました)、それは以下のように拡張することができます:
(b^0 * f() + b^1 * f() + b^2 * f() .... b^p * f()) / (b^(p+1) - 1) * (b-1)
これはランダムジェネレータをFLOATジェネレータに変換します。ここでb
とp
の2つのパラメータを定義します。ここでの "base"は4ですが、bは実際には何でもかまいませんが、無理数などにすることもできます。p
、精度とは、floatジェネレータをどの程度細かくしたいかの程度です。これをRand5
の呼び出しごとにRand7
に対して行われた呼び出しの数と考えてください。
しかし、bをbase + 1(この場合は4 + 1 = 5)に設定すると、良い点になり、一様分布になります。最初にこの1-5ジェネレータを取り除きなさい、それは真実Rand4()+ 1にある:
function Rand4(){
return Math.random() * 5 | 0;
}
そこに着くために、あなたはRand5()-1
でRand4
を代用することができます
次に、Rand4を整数ジェネレータからfloatジェネレータに変換します。
function toFloat(f,b,p){
b = b || 2;
p = p || 3;
return (Array.apply(null,Array(p))
.map(function(d,i){return f()})
.map(function(d,i){return Math.pow(b,i)*d})
.reduce(function(ac,d,i){return ac += d;}))
/
(
(Math.pow(b,p) - 1)
/(b-1)
)
}
これは最初に書いた関数を与えられたRand関数に適用します。それを試してみてください:
toFloat(Rand4) //1.4285714285714286 base = 2, precision = 3
toFloat(Rand4,3,4) //0.75 base = 3, precision = 4
toFloat(Rand4,4,5) //3.7507331378299122 base = 4, precision = 5
toFloat(Rand4,5,6) //0.2012288786482335 base = 5, precision =6
...
これで、この浮動小数点範囲(0-4 INCLUSIVE)を他の浮動小数点範囲に変換してから、整数になるようにダウングレードすることができます。ここでは4
を扱っているので、ここではRand4
をベースにしています。したがって、値b=5
は一様分布になります。 bが4を超えると、分布に周期的なギャップが生じ始めます。私は3000ポイントで2から8の範囲のb値をテストし、JavaScriptのネイティブMath.randomと比較して、ネイティブのものよりも私にはさらに良く見えます:
http://jsfiddle.net/ibowankenobi/r57v432t/
上記のリンクについては、分布の上部にある[bin]ボタンをクリックして、ビニングサイズを小さくしてください。最後のグラフはネイティブのMath.randomです。4番目のグラフはd = 5で均一です。
浮動小数点数の範囲が得られたら、7を掛けて小数部をスローするか、7を掛けて、0.5を引いて丸めます。
((toFloat(Rand4,5,6)/4 * 7) | 0) + 1 ---> occasionally you'll get 8 with 1/4^6 probability.
Math.round((toFloat(Rand4,5,6)/4 * 7) - 0.5) + 1 --> between 1 and 7
私はこの問題に対する興味深い解決策を考え、それを共有したいと思いました。
function Rand7() {
var returnVal = 4;
for (var n=0; n<3; n++) {
var Rand = Rand5();
if (Rand==1||Rand==2){
returnVal+=1;
}
else if (Rand==3||Rand==4) {
returnVal-=1;
}
}
return returnVal;
}
Rand7()を1万回ループし、すべての戻り値を合計して1万で除算するテスト関数を作成しました。 Rand7()が正しく機能している場合、計算された平均値は4になります。たとえば、(1 + 2 + 3 + 4 + 5 + 6 + 7/7)= 4です。 :)
ここでは一様分布を生成しない多くの解決策とそれを指摘する多くのコメントがありますが、質問では要件としてそれを述べていません 。最も簡単な解決策は、
int Rand_7() { return Rand_5(); }
1 - 5の範囲の乱数整数は、明らかに1 - 7の範囲です。まあ、技術的に最も簡単な解決策は定数を返すことですが、それはあまりにも些細なことです。
しかし、私はRand_5関数の存在はニシンだと思います。 「1 - 7の範囲の整数出力を持つ一様分布の擬似乱数ジェネレータを生成する」と質問されたとします。これは単純な問題です(技術的には単純ではありませんが、既に解決されているので、調べることができます)。
一方、質問が1から5までの範囲の整数のための真の乱数ジェネレータ(擬似乱数ではない)を実際に持っていることを意味すると解釈されるならば、解決策は以下のとおりです。
1) examine the Rand_5 function
2) understand how it works
3) profit
これはどう
Rand5()%2 + Rand5()%2 + Rand5()%2 + Rand5()%2 + Rand5()%2 + Rand5()%2
これが一様分布かどうかわからない。助言がありますか?
Martin's answer に似ていますが、エントロピーを捨てる頻度をはるかに低くしています:
int Rand7(void) {
static int m = 1;
static int r = 0;
for (;;) {
while (m <= INT_MAX / 5) {
r = r + m * (Rand5() - 1);
m = m * 5;
}
int q = m / 7;
if (r < q * 7) {
int i = r % 7;
r = r / 7;
m = q;
return i + 1;
}
r = r - q * 7;
m = m - q * 7;
}
}
ここで、0
とm-1
の間のランダムな値を作成し、オーバーフローせずに収まるだけの状態を追加してm
を最大化しようとします(INT_MAX
はCのint
に収まるか、または言語とアーキテクチャで意味のある大きな値に置き換えることができます)。
それから; r
が7で割り切れる可能な最大間隔内に収まる場合、実行可能な結果が含まれ、その間隔を7で除算し、残りを結果として取得し、残りの値をエントロピープールに返すことができます。それ以外の場合、r
は、均等に分割されない他の間隔にあり、その不適合間隔からエントロピープールを破棄して再起動する必要があります。
ここでの一般的な回答と比較すると、Rand5()
を呼び出す頻度は平均して約半分です。
分割は、パフォーマンスのために簡単なビットツイドルとLUTに分解できます。
ここではカバーされていないように思われるもう一つの答え:
int Rand7() {
int r = 7 / 2;
for (int i = 0; i < 28; i++)
r = ((Rand5() - 1) * 7 + r) / 5;
return r + 1;
}
繰り返しごとに、r
は0から6までのランダムな値です。これは、0から4までの範囲のランダム値に追加され(基数7)、結果は5で除算され、0から6までの範囲の新しいランダム値が得られます。 r
はかなりの偏りから始まります(r = 3
は非常に偏っています!)が、各反復はその偏りを5で割ります。
このメソッドは完全に統一されているわけではありません。ただし、バイアスはほとんどなくなります。 1 /(2 ** 64)のオーダーの何か。このアプローチの重要な点は、実行時間が一定であることです(Rand5()
も実行時間が一定であると仮定して)。不運な呼び出しが永遠に無効な値を選んで繰り返す可能性があるという理論上の懸念はありません。
また、良い測定のための皮肉な答え(故意かどうかは関係ありません):
1-5はすでに1-7の範囲内にあるため、以下は有効な実装です。
int Rand7() {
return Rand5();
}
質問は一様分布を求めていませんでした。
このソリューションは、Rob McAfeeの影響を受けています。
しかし、それはループを必要とせず、結果は一様分布になります。
// Returns 1-5
var rnd5 = function(){
return parseInt(Math.random() * 5, 10) + 1;
}
// Helper
var lastEdge = 0;
// Returns 1-7
var rnd7 = function () {
var map = [
[ 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 ]
];
var result = map[rnd5() - 1][rnd5() - 1];
if (result > 0) {
return result;
}
lastEdge++;
if (lastEdge > 7 ) {
lastEdge = 1;
}
return lastEdge;
};
// Test the a uniform distribution
results = {}; for(i=0; i < 700000;i++) { var Rand = rnd7(); results[Rand] = results[Rand] ? results[Rand] + 1 : 1;}
console.log(results)
結果:[1: 99560, 2: 99932, 3: 100355, 4: 100262, 5: 99603, 6: 100062, 7: 100226]
function Rand7() {
while (true) { //lowest base 5 random number > 7 reduces memory
int num = (Rand5()-1)*5 + Rand5()-1;
if (num < 21) // improves performance
return 1 + num%7;
}
}
Pythonコード:
from random import randint
def Rand7():
while(True):
num = (randint(1, 5)-1)*5 + randint(1, 5)-1
if num < 21:
return 1 + num%7
100000回のテスト配布
>>> rnums = []
>>> for _ in range(100000):
rnums.append(Rand7())
>>> {n:rnums.count(n) for n in set(rnums)}
{1: 15648, 2: 15741, 3: 15681, 4: 15847, 5: 15642, 6: 15806, 7: 15635}
1から5の範囲のランダムな整数を生成する関数Rand5()
を与え、1から7の範囲のランダムな整数を生成する関数を書くRand7()
提案されたソリューションでは、Rand5
を1回だけ呼び出します
実際のソリューション
float Rand7()
{
return (Rand5() * 7.0) / 5.0 ;
}
ここでの分布はスケーリングされているため、Rand5
の分布に直接依存します
整数解
int Rand7()
{
static int prev = 1;
int cur = Rand5();
int r = cur * prev; // 1-25
float f = r / 4.0; // 0.25-6.25
f = f - 0.25; // 0-6
f = f + 1.0; // 1-7
prev = cur;
return (int)f;
}
ここでの分布はシリーズRand7(i) ~ Rand5(i) * Rand5(i-1)
に依存します
Rand7(0) ~ Rand5(0) * 1
これはC++ 11の機能を利用した答えです。
#include <functional>
#include <iostream>
#include <ostream>
#include <random>
int main()
{
std::random_device rd;
unsigned long seed = rd();
std::cout << "seed = " << seed << std::endl;
std::mt19937 engine(seed);
std::uniform_int_distribution<> dist(1, 5);
auto Rand5 = std::bind(dist, engine);
const int n = 20;
for (int i = 0; i != n; ++i)
{
std::cout << Rand5() << " ";
}
std::cout << std::endl;
// Use a lambda expression to define Rand7
auto Rand7 = [&Rand5]()->int
{
for (int result = 0; ; result = 0)
{
// Take advantage of the fact that
// 5**6 = 15625 = 15624 + 1 = 7 * (2232) + 1.
// So we only have to discard one out of every 15625 numbers generated.
// Generate a 6-digit number in base 5
for (int i = 0; i != 6; ++i)
{
result = 5 * result + (Rand5() - 1);
}
// result is in the range [0, 15625)
if (result == 15625 - 1)
{
// Discard this number
continue;
}
// We now know that result is in the range [0, 15624), a range that can
// be divided evenly into 7 buckets guaranteeing uniformity
result /= 2232;
return 1 + result;
}
};
for (int i = 0; i != n; ++i)
{
std::cout << Rand7() << " ";
}
std::cout << std::endl;
return 0;
}
int Rand7()
{
return ( Rand5() + (Rand5()%3) );
}
これは@RobMcAfeeと似ていますが、2次元配列ではなくマジックナンバーを使用する点が異なります。
int Rand7() {
int m = 1203068;
int r = (m >> (Rand5() - 1) * 5 + Rand5() - 1) & 7;
return (r > 0) ? r : Rand7();
}
私はあなたがこれをひっくり返していると思います。この単純な解決策はうまくいきませんか?
int Rand7(void)
{
static int startpos = 0;
startpos = (startpos+5) % (5*7);
return (((startpos + Rand5()-1)%7)+1);
}
誰かが私にこれについてのフィードバックを与えることができればクールだろう、私はそれをEclipseで実行するのが簡単で速いので私はちょうど主なメソッドを定義したかもしれないのでassertパターンなしでJUNITを使いました。ちなみに、Rand5は0〜4の値を与え、1を加えると1〜5になります(Rand7と同じ)。したがって、議論は解決策であり、分布であり、0〜4から始まるとは限りません。または1-5 ...
package random;
import Java.util.Random;
import org.junit.Test;
public class RandomTest {
@Test
public void testName() throws Exception {
long times = 100000000;
int indexes[] = new int[7];
for(int i = 0; i < times; i++) {
int Rand7 = Rand7();
indexes[Rand7]++;
}
for(int i = 0; i < 7; i++)
System.out.println("Value " + i + ": " + indexes[i]);
}
public int Rand7() {
return (Rand5() + Rand5() + Rand5() + Rand5() + Rand5() + Rand5() + Rand5()) % 7;
}
public int Rand5() {
return new Random().nextInt(5);
}
}
実行すると、この結果が得られます。
Value 0: 14308087
Value 1: 14298303
Value 2: 14279731
Value 3: 14262533
Value 4: 14269749
Value 5: 14277560
Value 6: 14304037
これはとても公平なディストリビューションのようですね。
Rand5()の追加回数を少なくしても複数回にしても(回数が7で割り切れない場合)、分布は明らかにオフセットを示しています。たとえば、Rand5()
を3回追加します。
Value 0: 15199685
Value 1: 14402429
Value 2: 12795649
Value 3: 12796957
Value 4: 14402252
Value 5: 15202778
Value 6: 15200250
したがって、これは次のようになります。
public int Rand(int range) {
int randomValue = 0;
for(int i = 0; i < range; i++) {
randomValue += Rand5();
}
return randomValue % range;
}
それから私はさらに進むことができます:
public static final int ORIGN_RANGE = 5;
public static final int DEST_RANGE = 7;
@Test
public void testName() throws Exception {
long times = 100000000;
int indexes[] = new int[DEST_RANGE];
for(int i = 0; i < times; i++) {
int Rand7 = convertRand(DEST_RANGE, ORIGN_RANGE);
indexes[Rand7]++;
}
for(int i = 0; i < DEST_RANGE; i++)
System.out.println("Value " + i + ": " + indexes[i]);
}
public int convertRand(int destRange, int originRange) {
int randomValue = 0;
for(int i = 0; i < destRange; i++) {
randomValue += Rand(originRange);
}
return randomValue % destRange;
}
public int Rand(int range) {
return new Random().nextInt(range);
}
DestRangeとoriginRangeをさまざまな値(Originでは7、DESTでは13)に置き換えて試してみたところ、次のような分布になりました。
Value 0: 7713763
Value 1: 7706552
Value 2: 7694697
Value 3: 7695319
Value 4: 7688617
Value 5: 7681691
Value 6: 7674798
Value 7: 7680348
Value 8: 7685286
Value 9: 7683943
Value 10: 7690283
Value 11: 7699142
Value 12: 7705561
ここから私が結論を下すことができるのはあなたがOriginのランダムな "目的地"回を使用することによって他のものに任意のランダムを変更できるということです。これは一種のガウス分布になるでしょう(中間値がよりありそうで、エッジ値がより珍しいです)。しかし、目的地の係数は、このガウス分布全体に均等に分布しているようです。数学者からのフィードバックがあると便利です。
クールなのは、コストが100%予測可能で一定であるのに対し、他のソリューションでは無限ループが発生する可能性が小さいことです。
まず、ramdom5()を1点に6回移動して、7つの乱数を取得します。次に、7つの数字を足して共通の合計を求めます。最後に、1から7までの結果を取得するために1を追加します。少し高い確率。
public int random7(){
Random random = new Random();
//function (1 + random.nextInt(5)) is given
int random1_5 = 1 + random.nextInt(5); // 1,2,3,4,5
int random2_6 = 2 + random.nextInt(5); // 2,3,4,5,6
int random3_7 = 3 + random.nextInt(5); // 3,4,5,6,7
int random4_8 = 4 + random.nextInt(5); // 4,5,6,7,8
int random5_9 = 5 + random.nextInt(5); // 5,6,7,8,9
int random6_10 = 6 + random.nextInt(5); //6,7,8,9,10
int random7_11 = 7 + random.nextInt(5); //7,8,9,10,11
//sumOfRandoms is between 28 and 56
int sumOfRandoms = random1_5 + random2_6 + random3_7 +
random4_8 + random5_9 + random6_10 + random7_11;
//result is number between 0 and 6, and
//equals 0 if sumOfRandoms = 28 or 35 or 42 or 49 or 56 , 5 options
//equals 1 if sumOfRandoms = 29 or 36 or 43 or 50, 4 options
//equals 2 if sumOfRandoms = 30 or 37 or 44 or 51, 4 options
//equals 3 if sumOfRandoms = 31 or 38 or 45 or 52, 4 options
//equals 4 if sumOfRandoms = 32 or 39 or 46 or 53, 4 options
//equals 5 if sumOfRandoms = 33 or 40 or 47 or 54, 4 options
//equals 6 if sumOfRandoms = 34 or 41 or 48 or 55, 4 options
//It means that the probabilities of getting numbers between 0 and 6 are almost equal.
int result = sumOfRandoms % 7;
//we should add 1 to move the interval [0,6] to the interval [1,7]
return 1 + result;
}
私の考えでは、これは複数のMath.random()
関数呼び出しからRand5()
を再作成し、「重み付き分数」(?)で再構築することによって単位区間(Math.random()
の出力範囲)を再構築しようとします。次に、このランダムな単位間隔を使用して、1から7の間のランダムな整数を生成します。
function Rand5(){
return Math.floor(Math.random()*5)+1;
}
function Rand7(){
var uiRandom=0;
var div=1;
for(var i=0; i<7; i++){
div*=5;
var term=(Rand5()-1)/div;
uiRandom+=term;
}
//return uiRandom;
return Math.floor(uiRandom*7)+1;
}
言い換えると、0〜4の間のランダムな整数(ちょうどRand5()-1
)を取り、それぞれの結果に1/5、1/25、1/125、...を掛けてから合計します。バイナリ加重分数がどのように機能するかに似ています。代わりに、これを2進(base-5)加重小数と呼びます。一連の(1/5)^ n項として、0から0.999999までの数を生成します。
任意の入出力ランダム整数範囲をとるように関数を修正することは簡単なはずです。上のコードはクロージャとして書き直すと最適化できます。
あるいは、これを行うこともできます。
function Rand5(){
return Math.floor(Math.random()*5)+1;
}
function Rand7(){
var buffer=[];
var div=1;
for (var i=0; i<7; i++){
buffer.Push((Rand5()-1).toString(5));
div*=5;
}
var n=parseInt(buffer.join(""),5);
var uiRandom=n/div;
//return uiRandom;
return Math.floor(uiRandom*7)+1;
}
2進数(5進法)の重み付き分数の作成をいじるのではなく、実際に2進数を作成してそれを分数(前と同様に0--0.9999 ...)にしてから、ランダムな1-7桁を計算するそこから。
上記の結果(コードスニペット#2:それぞれ10万呼び出しの3回の実行):
1:14263。 2:14414。 3:14249。 4:14109。 5:14217。 6:14361。 7:14387
1:14205。 2:14394。 3:14238。 4:14187。 5:14384。 6:14224。 7:14368
1:14425。 2:14236。 3:14334。 4:14232。 5:14160。 6:14320。 7:14293
どうして5で割って7で乗じてから丸めないのですか? (もちろん、あなたは浮動小数点数を使わなければならないでしょう。)
それは他の解決策よりもはるかに簡単で信頼性が高い(本当に?)。例えば。 Pythonでは:
def ranndomNo7():
import random
Rand5 = random.randint(4) # Produces range: [0, 4]
Rand7 = int(Rand5 / 5 * 7) # /5, *7, +0.5 and floor()
return Rand7
そんなに簡単じゃなかった?
単純な解決策は十分にカバーされています。1つのrandom5
結果に対して2つのrandom7
サンプルを取得し、結果が一様分布を生成する範囲外の場合はそれを実行します。あなたの目標がrandom5
への呼び出しの数を減らすことであるならば、これは非常に無駄です - 捨てられたサンプルの数のためにrandom5
の各出力に対するrandom7
への平均呼び出しの数は2.38です。
一度に複数のrandom5
出力を生成するには、より多くのrandom7
入力を使用することで、より効果的に行うことができます。 31ビット整数で計算された結果の場合、9個のrandom5
出力を生成するために12個のrandom7
の呼び出しを使用し、1出力あたり平均1.34個の呼び出しを使用する場合が最適です。 244140625の結果のうち2018983だけが廃棄される必要がある、または1%未満であるため効率的です。
Pythonでのデモ:
def random5():
return random.randint(1, 5)
def random7gen(n):
count = 0
while n > 0:
samples = 6 * 7**9
while samples >= 6 * 7**9:
samples = 0
for i in range(12):
samples = samples * 5 + random5() - 1
count += 1
samples //= 6
for outputs in range(9):
yield samples % 7 + 1, count
samples //= 7
count = 0
n -= 1
if n == 0: break
>>> from collections import Counter
>>> Counter(x for x,i in random7gen(10000000))
Counter({2: 1430293, 4: 1429298, 1: 1428832, 7: 1428571, 3: 1428204, 5: 1428134, 6: 1426668})
>>> sum(i for x,i in random7gen(10000000)) / 10000000.0
1.344606
この問題の主な概念は正規分布に関するものであり、ここでこの問題に対する単純で再帰的な解決法を提供しました。
私たちのスコープに既にRand5()
があるとします。
def Rand7():
# twoway = 0 or 1 in the same probability
twoway = None
while not twoway in (1, 2):
twoway = Rand5()
twoway -= 1
ans = Rand5() + twoway * 5
return ans if ans in range(1,8) else Rand7()
このプログラムは2つの部分に分けられます。
twoway
に1または2が含まれる確率は1/2になります。ans
Rand5() + twoway * 5
、これはまさにRand10()
の結果です。これが私たちの必要性(1〜7)と一致しない場合は、Rand7を再度実行します。P.S twoway
の各確率が個別である必要があるため、weは2番目の部分でwhileループを直接実行することはできません。
しかし、最初のセクションのwhileループとreturn文の再帰のためにトレードオフがあります。この関数は実行時間を保証するものではなく、実際には効果的ではありません。
私は自分の答えに対する分布を観察するための簡単なテストをしました。
result = [ Rand7() for x in xrange(777777) ]
ans = {
1: 0,
2: 0,
3: 0,
4: 0,
5: 0,
6: 0,
7: 0,
}
for i in result:
ans[i] += 1
print ans
与えた
{1: 111170, 2: 110693, 3: 110651, 4: 111260, 5: 111197, 6: 111502, 7: 111304}
したがって、この答えが正規分布にあることがわかります。
この関数の実行時間を気にしないのであれば、上記の回答に基づいた簡単な回答です。
def Rand7():
ans = Rand5() + (Rand5()-1) * 5
return ans if ans < 8 else Rand7()
これは8より大きい価値の可能性を増大させますが、おそらくこの問題に対する最短の回答となるでしょう。
この式は1から7までの間のランダムな整数を得るのに十分です。
int j = ( Rand5()*2 + 4 ) % 7 + 1;
このアルゴリズムはRand5の呼び出し数を理論上の最小値7/5に減らします。それを7回呼び出すと、次の5つのRand7番号が生成されます。
任意のランダムビットを拒否することはなく、結果を常に待機し続ける可能性もありません。
#!/usr/bin/env Ruby
# random integer from 1 to 5
def Rand5
STDERR.putc '.'
1 + Rand( 5 )
end
@bucket = 0
@bucket_size = 0
# random integer from 1 to 7
def Rand7
if @bucket_size == 0
@bucket = 7.times.collect{ |d| Rand5 * 5**d }.reduce( &:+ )
@bucket_size = 5
end
next_Rand7 = @bucket%7 + 1
@bucket /= 7
@bucket_size -= 1
return next_Rand7
end
35.times.each{ putc Rand7.to_s }
実装をシンプルかつ効率的に保ちながら、Rand5()の呼び出し回数を最小限にしようとするソリューションがあります。特に、Adam Rosenfieldの2番目の答えとは異なり、任意の大きな整数を必要としません。 23/19 = 1.21052 ...がlog(7)/ log(5)= 1.20906 ...の合理的な近似であるという事実を利用しているため、{1、...、7の19個のランダム要素を生成できます。 } {1、...、5}の23個のランダムな要素のうち、わずかな棄却確率の棄却サンプリングによるもの。平均して、以下のアルゴリズムは、Rand7()の呼び出しごとに、約1.266回のRand5()呼び出しを必要とします。 Rand5()の分布が均一であれば、Rand7()も均一です。
uint_fast64_t pool;
int capacity = 0;
void new_batch (void)
{
uint_fast64_t r;
int i;
do {
r = 0;
for (i = 0; i < 23; i++)
r = 5 * r + (Rand5() - 1);
} while (r >= 11398895185373143ULL); /* 7**19, a bit less than 5**23 */
pool = r;
capacity = 19;
}
int Rand7 (void)
{
int r;
if (capacity == 0)
new_batch();
r = pool % 7;
pool /= 7;
capacity--;
return r + 1;
}