ランダムな順序で繰り返しのない数値を生成できる擬似乱数ジェネレータが必要です。
例えば:
ランダム(10)
5、9、1、4、2、8、3、7、6、10を返す場合があります
数値の範囲を作り、それらをシャッフルするか、生成されたリストの繰り返しをチェックする以外の方法がありますか?
また、範囲全体を使わずに大きな数を生成するのに効率的であることを望んでいます。
シャッフルアルゴリズムを提案しているすべての人を見かけます。しかし、大きな乱数(1024バイト以上)を生成したい場合、そのメソッドは通常のRNGを使用し、指定された長さになるまでSetに挿入した場合よりも多くのメモリを消費しますか?これに適した数学的アルゴリズムはありませんか。
線形フィードバックシフトレジスタに興味があるかもしれません。以前はハードウェアからこれらを構築していましたが、ソフトウェアでも実行しました。シフトレジスタを使用し、一部のビットをxorして入力にフィードバックします。適切な「タップ」を選択すると、レジスタサイズの長さのシーケンスを取得できます。つまり、16ビットのlfsrは、繰り返しのない長さ65535のシーケンスを生成できます。統計的にランダムですが、もちろん非常に繰り返し可能です。また、それが間違っていた場合、いくつかの恥ずかしいほど短いシーケンスを取得できます。 lfsrを調べると、それらを適切に構成する方法の例があります(つまり、「最大長」)。
シャッフルはこれを行うのに最適な方法です(単純なアルゴリズムを使用してバイアスを導入しない場合)。 Fisher-Yates shuffle を参照してください。
リストが繰り返されないようにするには、以前に返された数値のリストを保持する必要があります。したがって、アルゴリズムの最後までにリスト全体を生成する必要があるため、これはストレージ要件において、順序付きリストを生成してからシャッフルすることと同等です。
ここでのシャッフルの詳細: 順序付きリストからランダムな順序付きリストを作成
ただし、乱数の範囲が非常に大きいが、必要な数が少ない場合(これがコメントの実際の要件であることをほのめかした)、完全なリストを生成し、それを浪費します。巨大な配列でのシャッフルには、OSのページングシステムを(定義により)無効にする方法で仮想メモリのページにアクセスすることが含まれます(小規模では、CPUのメモリキャッシュで同じ問題が発生します)。
この場合、これまでのリストを検索する方がはるかに効率的です。したがって、理想は、ヒューリスティック(実験により決定)を使用して、指定された引数に適切な実装を選択することです。 (C++ではなくC#で例を与えることをおologiesび申し上げますが、 ASFAC++ B 私はC#で考えるように訓練しています)。
IEnumerable<int> GenerateRandomNumbers(int range, int quantity)
{
int[] a = new int[quantity];
if (range < Threshold)
{
for (int n = 0; n < range; n++)
a[n] = n;
Shuffle(a);
}
else
{
HashSet<int> used = new HashSet<int>();
for (int n = 0; n < quantity; n++)
{
int r = Random(range);
while (!used.Add(r))
r = Random(range);
a[n] = r;
}
}
return a;
}
繰り返される番号のチェック、衝突がある間のループなどのコストは高くなりますが、範囲全体に割り当てるよりも高速になるThreshold
値が存在する可能性があります。
十分に少量の要件の場合、used
に配列を使用し、その中で線形検索を実行する方が、局所性が高く、オーバーヘッドが低く、比較が安価であるため、高速になる場合があります...
また、大量かつ大きな範囲の場合、結果の配列を事前に割り当てるのではなく、要求に応じてシーケンス内の数値を生成するオブジェクトを返すことをお勧めします。 yield return
キーワードのおかげで、これはC#で非常に簡単に実装できます。
IEnumerable<int> ForLargeQuantityAndRange(int quantity, int range)
{
for (int n = 0; n < quantity; n++)
{
int r = Random(range);
while (!used.Add(r))
r = Random(range);
yield return r;
}
}
乱数が決して繰り返されないことが保証されている場合、それはもはやランダムではなく、数字が生成されるにつれてrandomnessの量は減少します(9つの数字random(10)
はかなり予測可能であり、 8つは50-50のチャンスがあります)。
あなたはそうするためにリスト全体を保存しなければならないので、私はtouが広い範囲のシャッフルを望まないことを理解しています。
代わりに、可逆的な擬似ランダムハッシュを使用します。次に、値0 1 2 3 4 5 6などを順番に入力します。
このようなハッシュの数は無限です。 2のべき乗に制限されている場合、生成するのはそれほど難しくありませんが、任意のベースを使用できます。
たとえば、すべての2 ^ 32 32ビット値を調べたい場合に機能するものを次に示します。この場合、整数演算の暗黙のmod 2 ^ 32が有効であるため、記述するのが最も簡単です。
unsigned int reversableHash(unsigned int x)
{
x*=0xDEADBEEF;
x=x^(x>>17);
x*=0x01234567;
x+=0x88776655;
x=x^(x>>4);
x=x^(x>>9);
x*=0x91827363;
x=x^(x>>7);
x=x^(x>>11);
x=x^(x>>20);
x*=0x77773333;
return x;
}
平凡なランダム性プロパティを気にせず、要素の数がそれを許容する場合、 線形合同乱数ジェネレータ を使用できます。
シャッフルは、繰り返しのない特定の範囲の乱数に対してできる最善の方法です。記述する方法(指定された長さに達するまでランダムに数を生成し、それらをSetに入れる)の効率が低いのは、重複のためです。理論的には、そのアルゴリズムは決して終わらないかもしれません。シャッフルは常に予測可能な時間で実行されるのに対し、せいぜいそれは不確定な時間で終了します。
コメントで示すように、数値の範囲が非常に大きく、繰り返しのないランダムに比較的少ない数を選択する場合、繰り返しの可能性は急速に減少します。範囲と選択数のサイズの差が大きいほど、繰り返し選択の可能性が小さくなり、質問で説明する選択とチェックのアルゴリズムのパフォーマンスが向上します。
GUIDジェネレーター(.NETのようなもの)の使用についてはどうですか。重複がないことは保証されていませんが、重複する可能性はかなり低くなっています。
番号を生成するときに、 ブルームフィルター を使用して重複を検出します。これにより、最小限のメモリが使用されます。シリーズの以前の番号を保存する必要はまったくありません。
トレードオフは、あなたのリストがあなたの範囲で網羅的でなかったかもしれないということです。数字が本当に256 ^ 1024のオーダーであれば、それはほとんどトレードオフではありません。
(もちろん、それらが実際にその規模でランダムである場合、重複を検出するのを煩わせても時間の無駄です。地球上のすべてのコンピューターが数兆年にわたって毎秒サイズの1兆個の乱数を生成した場合、衝突の可能性は絶対にあります無視できます。)
[〜#〜] lfsr [〜#〜] の使用に関する2番目のgbarryの答え。ソフトウェアでも非常に効率的で簡単に実装でき、Nビットシフトレジスタを使用したLFSRの(2 ^ N-1)の使用で繰り返されないことが保証されています。
ただし、いくつかの欠点があります:RNGからの少数の出力を監視することにより、LFSRを再構築し、生成されるすべての値を予測できます。 2番目の問題は、LFSRの実装に応じて、すべてゼロのワードまたはすべて1(ビット)のいずれかのワードが無効であることです。質問に関連する3番目の問題は、LFSRによって生成される最大数が常に2の累乗(または2の累乗)であることです。
最初の欠点は、アプリケーションによっては問題にならない場合があります。あなたが与えた例から、あなたは答えの中にゼロがあると期待していないようです。そのため、2番目の問題はあなたのケースに関連していないようです。最大値(および範囲)の問題は、範囲内の数値が得られるまでLFSRを再利用することで解決できます。以下に例を示します。
(例のように)1から10までの数字を持ちたいとします。範囲[1、15]を含む4ビットLFSRを使用します。 [1,10]の範囲の数値を取得する方法に関する擬似コードを次に示します。
x = LFSR.getRandomNumber();
while (x > 10) {
x = LFSR.getRandomNumber();
}
前のコードをRNGに埋め込む必要があります。呼び出し元が実装を気にしないように。大きなシフトレジスタを使用し、必要な最大数が2-1の累乗でない場合、これによりRNGが遅くなることに注意してください。
これは以前に尋ねられました- 前の質問に対する私の答え を参照してください。簡単に言うと、ブロック暗号を使用して、任意の範囲で置換全体を保存することなく、任意の範囲で安全な(ランダムな)置換を生成できます。
大きな(たとえば、64ビット以上の)乱数を繰り返しなしで作成する場合は、それらを作成します。実際に十分なエントロピーを持つ優れた乱数ジェネレーターを使用している場合、繰り返しを生成する確率は非常に小さいため、心配する価値はありません。
たとえば、暗号化キーを生成するとき、同じキーを以前に生成したことがあるかどうかを実際に確認する人はいません。専用の攻撃者は同じキーを取得できないと乱数ジェネレーターを信頼しているのに、間違って同じキーを思い付くと思われるのはなぜですか?
もちろん、悪い乱数ジェネレーター( Debian SSL乱数ジェネレーターの脆弱性 など)を持っている場合、または birthday paradox が高いほど小さい数を生成している場合衝突の可能性がある場合は、繰り返し発生しないように実際に何かをする必要があります。ただし、優れたジェネレーターを備えた大きな乱数の場合は、繰り返しが発生しないように確率を信頼してください。
生成されたシーケンスの貧弱な統計的特性を意味しない場合、1つの方法があります。
それぞれが1024ビットのN個の数値を生成するとします。生成された数ビットを犠牲にして「カウンター」にすることができます。
したがって、各乱数を生成しますが、選択したいくつかのビットにバイナリエンコードカウンターを配置します(変数から、次の乱数が生成されるたびに増加します)。
その数を単一のビットに分割し、生成された数の下位ビットのいくつかに入れることができます。
そうすれば、毎回一意の番号を取得できます。
たとえば、生成された各数値は次のようになります。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyxxxxyxyyyyxxyxxここで、xはジェネレータから直接取得され、ysはカウンタ変数から取得されます。
で回答を確認してください
リスト全体を前もって構築せずに整数のシーケンスをランダムな順序で生成します
また、私の答えは
very simple random is 1+((power(r,x)-1) mod p) will be from 1 to p for values of x from 1 to p and will be random where r and p are prime numbers and r <> p.
乱数または擬似乱数ジェネレーターがあると仮定すると、一意の値を返すことが保証されていない場合でも、このコードを使用して毎回一意の値を返すジェネレーターを実装できます。 random(10)
、およびrandom(10); random(11)
で呼び出さないでください。
コードはエラーをチェックしません。必要に応じて自分で追加できます。
大きな範囲の数値が必要な場合も、大量のメモリが必要です。
/* the function returns a random number between 0 and max -1
* not necessarily unique
* I assume it's written
*/
int random(int max);
/* the function returns a unique random number between 0 and max - 1 */
int unique_random(int max)
{
static int *list = NULL; /* contains a list of numbers we haven't returned */
static int in_progress = 0; /* 0 --> we haven't started randomizing numbers
* 1 --> we have started randomizing numbers
*/
static int count;
static prev_max = 0;
// initialize the list
if (!in_progress || (prev_max != max)) {
if (list != NULL) {
free(list);
}
list = malloc(sizeof(int) * max);
prev_max = max;
in_progress = 1;
count = max - 1;
int i;
for (i = max - 1; i >= 0; --i) {
list[i] = i;
}
}
/* now choose one from the list */
int index = random(count);
int retval = list[index];
/* now we throw away the returned value.
* we do this by shortening the list by 1
* and replacing the element we returned with
* the highest remaining number
*/
swap(&list[index], &list[count]);
/* when the count reaches 0 we start over */
if (count == 0) {
in_progress = 0;
free(list);
list = 0;
} else { /* reduce the counter by 1 */
count--;
}
}
/* swap two numbers */
void swap(int *x, int *y)
{
int temp = *x;
*x = *y;
*y = temp;
}
N個の要素をシャッフルしても、過剰なメモリを消費することはありません...一度に1つの要素のみを交換するため、使用される最大メモリはN + 1要素のメモリです。
static std::unordered_set<long> s;
long l = 0;
for(; !l && (s.end() != s.find(l)); l = generator());
v.insert(l);
generator()は乱数ジェネレータです。エントリがセットにない限り数字を転がし、そこにあるものを追加します。あなたはアイデアを得る。
この例ではlongを使用しましたが、PRNGがテンプレート化されている場合はテンプレートを作成する必要があります。
別の方法は、暗号的に安全なPRNGを使用することです。これは、同じ数値を2回生成する確率が非常に低くなります。
実際、ここで重要な点があります。繰り返しが許可されていない乱数ジェネレータはランダムではありません。
シーケンスがランダムであるためには、自動相関関係はありません。番号が繰り返されるべきではないという制限は、次の番号が以前のすべての番号に依存することを意味します。これは、もはやランダムではないことを意味します。
結果を繰り返さずにランダム化する方法を次に示します。文字列に対しても機能します。そのC#ですが、logigは多くの場所で動作するはずです。ランダムな結果をリストに入れ、新しいランダム要素がそのリストにあるかどうかを確認します。そうでない場合は、新しいランダム要素があります。そのリストにある場合は、そのリストにない要素を取得するまでランダムに繰り返します。
List<string> Erledigte = new List<string>();
private void Form1_Load(object sender, EventArgs e)
{
label1.Text = "";
listBox1.Items.Add("a");
listBox1.Items.Add("b");
listBox1.Items.Add("c");
listBox1.Items.Add("d");
listBox1.Items.Add("e");
}
private void button1_Click(object sender, EventArgs e)
{
Random Rand = new Random();
int index=Rand.Next(0, listBox1.Items.Count);
string rndString = listBox1.Items[index].ToString();
if (listBox1.Items.Count <= Erledigte.Count)
{
return;
}
else
{
if (Erledigte.Contains(rndString))
{
//MessageBox.Show("vorhanden");
while (Erledigte.Contains(rndString))
{
index = Rand.Next(0, listBox1.Items.Count);
rndString = listBox1.Items[index].ToString();
}
}
Erledigte.Add(rndString);
label1.Text += rndString;
}
}
繰り返しなしで一連の256個の乱数を生成するとします。
b
と呼びましょうn
(まだ生成されていない数字の数)になりますn = 256
からn = 1
へのループ[0, n)
の範囲で乱数r
を生成しますr
でb
- th番目のゼロビットを見つけ、p
と呼びましょうp
という配列、q
を入れますp
のb
番目のビットを1
に反転しますn = 1
パスの後、数字のリストの生成が完了します最初にn
= 4を使用して、私が話していることの簡単な例を示します。
**Setup**
b = 0000
q = []
**First loop pass, where n = 4**
r = 2
p = 2
b = 0010
q = [2]
**Second loop pass, where n = 3**
r = 2
p = 3
b = 0011
q = [2, 3]
**Third loop pass, where n = 2**
r = 0
p = 0
b = 1011
q = [2, 3, 0]
** Fourth and final loop pass, where n = 1**
r = 0
p = 1
b = 1111
q = [2, 3, 0, 1]
私は前に同様の質問をしましたが、私のものはintの範囲全体でした ハッシュ関数/ Ordered Int /を/ Shuffled Int /を探しています
問題は、範囲1..MからN個の一意の数字の「ランダムな」シーケンスを選択することです。ここで、NとMの関係に制約はありません(Mは、Nよりはるかに大きく、ほぼ同じ、またはさらに小さくなります。比較的素数ではないかもしれません)。
線形フィードバックシフトレジスタの答えの拡張:与えられたMに対して、Mより大きい最小の2のべき乗の最大LFSRを構築します。その後、LFSRから数字を取得して、Mより大きい数字を捨てます。平均すると、生成された数値の最大で半分を捨てる(LFSRの範囲の半分以上がMより小さいため)ので、数値を取得するための予想実行時間はO(1)です。以前に生成された数値を保存していないので、スペースの消費量はO(1)です。N個の数値を取得する前に循環すると、MはN未満になります。
最大168ビットの最大長LFSRのパラメーターは、こちら(ウィキペディアから)にあります。 http://www.xilinx.com/support/documentation/application_notes/xapp052.pdf
Javaコード:
/ ** * [0、M)で一意の「乱数」のシーケンスを生成します* @author dkoes * * /
パブリッククラスUniqueRandom {long lfsr;ロングマスク;ロングマックス;
private static long seed = 1;
//indexed by number of bits
private static int [][] taps = {
null, // 0
null, // 1
null, // 2
{3,2}, //3
{4,3},
{5,3},
{6,5},
{7,6},
{8,6,5,4},
{9,5},
{10,7},
{11,9},
{12,6,4,1},
{13,4,3,1},
{14,5,3,1},
{15,14},
{16,15,13,4},
{17,14},
{18,11},
{19,6,2,1},
{20,17},
{21,19},
{22,21},
{23,18},
{24,23,22,17},
{25,22},
{26,6,2,1},
{27,5,2,1},
{28,25},
{29,27},
{30,6,4,1},
{31,28},
{32,22,2,1},
{33,20},
{34,27,2,1},
{35,33},
{36,25},
{37,5,4,3,2,1},
{38,6,5,1},
{39,35},
{40,38,21,19},
{41,38},
{42,41,20,19},
{43,42,38,37},
{44,43,18,17},
{45,44,42,41},
{46,45,26,25},
{47,42},
{48,47,21,20},
{49,40},
{50,49,24,23},
{51,50,36,35},
{52,49},
{53,52,38,37},
{54,53,18,17},
{55,31},
{56,55,35,34},
{57,50},
{58,39},
{59,58,38,37},
{60,59},
{61,60,46,45},
{62,61,6,5},
{63,62},
};
//m is upperbound; things break if it isn't positive
UniqueRandom(long m)
{
max = m;
lfsr = seed; //could easily pass a starting point instead
//figure out number of bits
int bits = 0;
long b = m;
while((b >>>= 1) != 0)
{
bits++;
}
bits++;
if(bits < 3)
bits = 3;
mask = 0;
for(int i = 0; i < taps[bits].length; i++)
{
mask |= (1L << (taps[bits][i]-1));
}
}
//return -1 if we've cycled
long next()
{
long ret = -1;
if(lfsr == 0)
return -1;
do {
ret = lfsr;
//update lfsr - from wikipedia
long lsb = lfsr & 1;
lfsr >>>= 1;
if(lsb == 1)
lfsr ^= mask;
if(lfsr == seed)
lfsr = 0; //cycled, stick
ret--; //zero is stuck state, never generated so sub 1 to get it
} while(ret >= max);
return ret;
}
}
この回答は、すでによく知られているアルゴリズムを使用して、必要なものを取得し、ランダムな順序にするためのいくつかの戦略を提案しています。
Durstenfeldバージョンと呼ばれるFisher-Yatesシャッフルアルゴリズムのインサイドアウトバージョンがあります。これは、配列またはコレクションの読み込み中に、連続して取得したアイテムを配列およびコレクションにランダムに分散します。
覚えておくべきことの1つは、オブジェクトへの参照ポインターのみが移動され、オブジェクト自体は移動する必要がないため、オブジェクトの配列ではフィッシャーイエーツ(別名Knuth)シャッフルまたはロード時に使用されるDurstenfeldバージョンが非常に効率的であることですアルゴリズムの一部として調べたり、他のオブジェクトと比較したりします。
両方のアルゴリズムを以下でさらに説明します。
1024バイト以上の非常に大きな乱数が必要な場合は、一度に符号なしバイトまたはワードを生成できる非常に優れたランダムジェネレーターで十分です。数を構成するのに必要な数のバイトまたはワードをランダムに生成し、それへの参照ポインターを使用してオブジェクトにすると、実際には非常に大きなランダムな整数になります。特定の非常に大きな範囲が必要な場合は、0バイトのベース値をバイトシーケンスの下位に追加して、値をシフトできます。これが最良の選択肢かもしれません。
本当に巨大な乱数の重複を排除する必要がある場合、それはよりトリッキーです。本当に巨大な乱数であっても、重複を削除すると、それらは著しく偏り、ランダムではなくなります。複製されていない非常に大きな非常に大きな乱数のセットがあり、まだ選択されていないものからランダムに選択した場合、バイアスは、選択する実際の非常に大きな数字のセットに対して巨大な値を作成する際のバイアスにすぎません。 DurstenfeldのYates-Fisherのバージョンの逆バージョンを使用して、それらの非常に大きなセットから値をランダムに選択し、選択する残りの値からそれらを削除して、サブセットである新しい配列に挿入し、実行できますこれは、その場でソースとターゲットのアレイだけで行われます。これは非常に効率的です。
これは、それらが複製されていない非常に大きな乱数セットから膨大な値を持つ少数の乱数を取得するための優れた戦略です。ソースセットのランダムな場所を選択し、その値を取得し、その値をソースセットの一番上の要素と交換し、ソースセットのサイズを1つ減らし、十分な値を選択するまでサイズの小さいソースセットで繰り返します。これは、フィッシャー・イェーツのダーステンフェルド版とは逆に不可欠です。その後、Fisher-YatesアルゴリズムのDursenfeldバージョンを使用して、取得した値を宛先セットに挿入できます。ただし、ここで示すようにランダムに選択し、ランダムに順序付けする必要があるため、これはやり過ぎです。
両方のアルゴリズムは、ゼロからsetSizeまでのランダムな整数を生成する乱数インスタンスメソッドnextInt(int setSize)があることを想定しています。これは、setSizeの可能な値があることを意味します。この場合、配列の最後のインデックスがサイズ-1であるため、配列のサイズになります。
最初のアルゴリズムは、任意の長さの配列に適用されるFisher-Yates(別名Knuth)シャッフルアルゴリズムのDurstenfeldバージョンで、0から配列の長さまでの整数を単純に配列にランダムに配置します。配列は整数の配列である必要はありませんが、シーケンシャルに取得されるオブジェクトの配列にすることができます。これにより、事実上、参照ポインターの配列になります。シンプルで短く、非常に効果的です
int size = someNumber;
int[] int array = new int[size]; // here is the array to load
int location; // this will get assigned a value before used
// i will also conveniently be the value to load, but any sequentially acquired
// object will work
for (int i = 0; i <= size; i++) { // conveniently, i is also the value to load
// you can instance or acquire any object at this place in the algorithm to load
// by reference, into the array and use a pointer to it in place of j
int j = i; // in this example, j is trivially i
if (i == 0) { // first integer goes into first location
array[i] = j; // this may get swapped from here later
} else { // subsequent integers go into random locations
// the next random location will be somewhere in the locations
// already used or a new one at the end
// here we get the next random location
// to preserve true randomness without a significant bias
// it is REALLY IMPORTANT that the newest value could be
// stored in the newest location, that is,
// location has to be able to randomly have the value i
int location = nextInt(i + 1); // a random value between 0 and i
// move the random location's value to the new location
array[i] = array[location];
array[location] = j; // put the new value into the random location
} // end if...else
} // end for
出来上がり、これで既にランダム化された配列ができました。
既に持っている配列をランダムにシャッフルしたい場合は、標準のFisher-Yatesアルゴリズムがあります。
type[] array = new type[size];
// some code that loads array...
// randomly pick an item anywhere in the current array segment,
// swap it with the top element in the current array segment,
// then shorten the array segment by 1
// just as with the Durstenfeld version above,
// it is REALLY IMPORTANT that an element could get
// swapped with itself to avoid any bias in the randomization
type temp; // this will get assigned a value before used
int location; // this will get assigned a value before used
for (int i = arrayLength -1 ; i > 0; i--) {
int location = nextInt(i + 1);
temp = array[i];
array[i] = array[location];
array[location] = temp;
} // end for
シーケンスされたコレクションとセット、つまりある種のリストオブジェクトの場合、どこにでもアイテムを挿入できるインデックス値を持つ追加/挿入を使用できますが、バイアスの作成を避けるために現在の最後のアイテムの後に追加または追加できるようにする必要がありますランダム化。