O(1)の一意の(繰り返さない)乱数?


179

私は0から1000の間で繰り返されない(つまり、6が2回表示されない)ユニークな乱数を生成したいのですが、以前の値のO(N)検索のようなものに頼っていません。これは可能ですか?


4
これは、stackoverflow.com

2
0は0から1000の間ですか?
ピートカーカム

4
一定の時間(O(n)時間やメモリなど)にわたって何かを禁止している場合、受け入れられた回答を含め、以下の回答の多くは間違っています。
jww 2014

カードのパックをどうやってシャッフルしますか?
大佐パニック

9
警告!真にランダムなシーケンスを生成しないための以下の回答の多くは、O(n)よりも遅いか、そうでなければ欠陥があります!codinghorror.com/blog/archives/001015.htmlは、それらを使用したり、独自のものを作成したりする前に必ず読んでください。
ivan_pozdeev 2016

回答:


247

1001整数の配列を0〜1000の値で初期化し、変数maxを配列の現在の最大インデックス(1000から開始)に設定します。0からmaxまでの乱数rを選択し、位置rの数値を位置maxの数値と交換して、位置maxの数値を返します。最大値を1減らして続行します。maxが0の場合、maxを配列のサイズ-1に設定し直して、配列を再初期化する必要なく再起動します。

更新: 質問に答えたとき、私は自分でこの方法を思いつきましたが、いくつかの調査の結果、これがDurstenfeld-Fisher-YatesまたはKnuth-Fisher-Yatesとして知られるフィッシャーイェイツの修正版であることに気付きました。説明を理解するのは少し難しいかもしれないので、以下の例を示します(1001ではなく11要素を使用)。

配列は、11個の要素がarray [n] = nに初期化された状態で始まり、最大値は10から始まります。

+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|
+--+--+--+--+--+--+--+--+--+--+--+
                                ^
                               max    

各反復で、乱数rが0からmaxの間で選択され、array [r]とarray [max]が交換され、新しいarray [max]が返され、maxがデクリメントされます。

max = 10, r = 3
           +--------------------+
           v                    v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2|10| 4| 5| 6| 7| 8| 9| 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 9, r = 7
                       +-----+
                       v     v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2|10| 4| 5| 6| 9| 8| 7: 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 8, r = 1
     +--------------------+
     v                    v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 8| 2|10| 4| 5| 6| 9| 1: 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 7, r = 5
                 +-----+
                 v     v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 8| 2|10| 4| 9| 6| 5: 1| 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

...

11回の反復の後、配列内のすべての数値が選択され、max == 0になり、配列要素がシャッフルされます。

+--+--+--+--+--+--+--+--+--+--+--+
| 4|10| 8| 6| 2| 0| 9| 5| 1| 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

この時点で、maxを10にリセットして、プロセスを続行できます。


6
シャフリングに関するジェフの投稿は、これは良い乱数を返さないことを示唆しています。codinghorror.com/ blog / archives / 001015.html
プロ

14
@ピーター・ランス:私はそうは思わない。これは、Jeffの投稿でも引用されているフィッシャーイェーツアルゴリズムに似ています(善人として)。
ブレントロングボロー、2009年

3
@robert:質問の名前のように、「O(1)の一意の乱数」を生成しないことを指摘したかっただけです。
チャールズ

3
@mikera:同意しますが、技術的には固定サイズの整数を使用している場合は、リスト全体をO(1)で生成できます(大きな定数、つまり2 ^ 32)。また、実用的な目的では、「ランダム」の定義が重要です。システムのエントロピープールを本当に使用したい場合、制限は、計算自体ではなくランダムビットの計算であり、その場合、n log nが関係します。再び。しかし、/ dev / randomではなく/ dev / urandom(と同等のもの)を使用する可能性が高い場合は、「実際に」O(n)に戻ります。
Charles

4
私は少し混乱していますが、N毎回目的の結果を得るために反復(この例では11)を実行する必要があるのではないでしょうO(n)か?同じ初期状態から組み合わせNを取得するには反復を行う必要があるのでN!、そうしないと出力はN状態の1つだけになります。
SEPH

71

あなたはこれを行うことができます:

  1. リスト0..1000を作成します。
  2. リストをシャッフルします。(これを行うための良い方法については、Fisher-Yates shuffleを参照してください。)
  3. シャッフルされたリストから順番に番号を返します。

そのため、毎回古い値を検索する必要はありませんが、最初のシャッフルにはO(N)が必要です。しかし、ニルスがコメントで指摘したように、これは償却されたO(1)です。


5
@Just Some Guy N = 1000、つまりO(N / N)はO(1)であると言っている
Guvante

1
シャッフルされた配列への各挿入が操作である場合、1つの値を挿入した後、1つのランダムな値を取得できます。2つの値の場合は2、以下同様。nの値の場合はn。リストを生成するにはn回の操作が必要なので、アルゴリズム全体はO(n)です。1,000,000のランダムな値が必要な場合、1,000,000の演算が必要になります
Kibbee

3
このように考えてみてください。もしそれが一定の時間であったとしたら、10億の乱数と100億の乱数は同じ時間かかります。しかし、O(n)を取るシャッフルのため、これは真実ではありません。
Kibbee 2009年

1
n lg n個のランダムなビットを生成する必要があるため、これには実際には償却時間O(log n)がかかります。
Charles

2
そして今、私にはそれを行う正当な理由がすべてあります!meta.stackoverflow.com/q/252503/13
Chris Jester-Young

60

最大線形フィードバックシフトレジスタを使用します

これは、Cの数行で実装可能であり、実行時にいくつかのテスト/ブランチを実行し、追加とビットシフトを実行します。ランダムではありませんが、ほとんどの人を騙します。


12
「ランダムではないが、ほとんどの人をだます」。これは、すべての疑似乱数ジェネレータと、この質問に対するすべての可能な答えに適用されます。しかし、ほとんどの人はそれについて考えません。したがって、このメモを省略すると、賛成票が増える可能性があります...
f3lix 2009年

3
@bobobobo:O(1)メモリが理由です。
Ash

3
Nit:O(log N)メモリです。
ポールハンキン、2013

2
その方法を使用して、どのようにして数値を生成するのでしょうか。一部は1048575(2 ^ 20-1)のLFSRを使用し、数値が範囲外の場合は次のLFSRを取得するかもしれませんが、これは効率的ではありません。
tigrou

1
LFSRとして、これは均一に分散されたシーケンスを生成しません。生成されるシーケンス全体が最初の要素によって定義されます。
ivan_pozdeev 2016

21

あなたは線形合同ジェネレータを使用することができます。どこm(モジュラス)は、範囲外の数を取得すると、ちょうど次のいずれかを取得1000より最寄りのプライム大きくなるでしょう。すべての要素が発生したときにのみシーケンスが繰り返され、テーブルを使用する必要はありません。ただし、このジェネレーターの欠点(ランダム性の欠如を含む)に注意してください。


1
1009は1000
。– Teepeemm 2014年

LCGは、連続する番号間の相関が高いため、組み合わせは全体として非常にランダムではありません(たとえばk、シーケンス内で離れている番号が同時に発生することはありません)。
ivan_pozdeev 2016

mは要素数1001(ゼロの場合は1000 + 1)でなければならず、Next =(1002 * Current + 757)mod 1001を使用できます。
マックスアブラモビッチ2016

21

フォーマットを保持する暗号化を使用できますを、カウンターを暗号化。カウンターは0から上向きになり、暗号化は選択したキーを使用して、任意の基数と幅のランダムに見える値に変換します。たとえば、この質問の例:基数10、幅3。

ブロック暗号は通常、たとえば64ビットまたは128ビットの固定ブロックサイズを持っています。ただし、フォーマットを保持する暗号化を使用すると、AESなどの標準的な暗号を使用し、基数と幅を問わず、暗号学的に堅牢なアルゴリズムを使用して、より狭い幅の暗号を作成できます。

衝突がないことは保証されています(暗号化アルゴリズムが1:1マッピングを作成するため)。また、これは可逆的(双方向マッピング)なので、結果の数値を取得して、最初のカウンター値に戻すことができます。

この手法は、シャッフルされた配列などを格納するためにメモリを必要としません。これは、メモリが限られているシステムでは有利です。

AES-FFXは、これを実現するために提案された標準的な方法の1つです。AES-FFXのアイデアに基づいたいくつかの基本的なPythonコードを実験しましたが、完全に準拠しているわけではありません。Pythonコードはこちらを参照してください。たとえば、カウンターをランダムに見える7桁の10進数、または16ビットの数値に暗号化できます。質問が述べたように、基数10、幅3(0から999までの数値を与えるため)の例を次に示します。

000   733
001   374
002   882
003   684
004   593
005   578
006   233
007   811
008   072
009   337
010   119
011   103
012   797
013   257
014   932
015   433
...   ...

異なる非反復疑似ランダムシーケンスを取得するには、暗号化キーを変更します。各暗号化キーは、異なる非反復疑似ランダムシーケンスを生成します。


これは基本的には単純なマッピングであり、LCGやLFSRと何ら変わりはなく、関連するすべてのねじれがあります(たとえばk、シーケンス内で離れている値が同時に発生することはありません)。
ivan_pozdeev 2016

@ivan_pozdeev:コメントの意味を理解できません。このマッピングの何が問題になっているのか、「関連するすべてのねじれ」とは何か、そして何が何なのかを説明できますkか?
Craig McQueen

ここで「暗号化」が効果的に行うことは、シーケンス1,2,...,Nを同じ番号のシーケンスに置き換えることですが、一定の順序のままです。次に、このシーケンスから番号が1つずつ取り出されます。k選択された値の数です(OPは文字を指定しなかったため、1つ紹介する必要がありました)。
ivan_pozdeev

3
@ivan_pozdeev FPEが特定の静的マッピングを実装する必要がある場合や、「返される組み合わせが最初の数値で完全に定義されている」というわけではありません。構成パラメーターは最初の数値(1000の状態しかない)のサイズよりもはるかに大きいため、同じ初期値で始まり、その後に続く異なる値に進むシーケンスが複数あるはずです。現実的なジェネレーターは、順列の可能な空間全体をカバーできなくなります。OPがそれを要求しなかったときに、その障害モードを上げることは価値がありません。
sh1 2016

4
+1。正しく実装されている場合、一様にランダムに選択されたキーを持つセキュアブロック暗号を使用すると、このメソッドを使用して生成されたシーケンスは、真のランダムシャッフルと計算上区別できなくなります。つまり、このメソッドの出力を真のランダムシャッフルから、可能なすべてのブロック暗号キーをテストし、それらのいずれかが同じ出力を生成するかどうかを確認するよりもはるかに速く区別する方法はありません。128ビットのキースペースを持つ暗号の場合、これはおそらく人類が現在利用できる計算能力を超えています。256ビットのキーの場合、それはおそらく永遠に残ります。
Ilmari Karonen

7

0 ... 1000のような低い数値の場合、すべての数値を含むリストを作成し、それをシャッフルするのは簡単です。ただし、取得する数値のセットが非常に大きい場合は、別のエレガントな方法があります。キーと暗号化ハッシュ関数を使用して、疑似ランダム置換を作成できます。次のC ++風の疑似コードの例を参照してください。

unsigned randperm(string key, unsigned bits, unsigned index) {
  unsigned half1 =  bits    / 2;
  unsigned half2 = (bits+1) / 2;
  unsigned mask1 = (1 << half1) - 1;
  unsigned mask2 = (1 << half2) - 1;
  for (int round=0; round<5; ++round) {
    unsigned temp = (index >> half1);
    temp = (temp << 4) + round;
    index ^= hash( key + "/" + int2str(temp) ) & mask1;
    index = ((index & mask2) << half1) | ((index >> half2) & mask1);
  }
  return index;
}

ここでhashは、文字列を巨大な符号なし整数にマッピングする任意の疑似ランダム関数をいくつか示します。関数randpermは、固定キーを想定して、0 ... pow(2、bits)-1内のすべての数値の順列です。これindexは、変数を変更するすべてのステップがリバーシブルであるため、構造に基づいています。これはFeistel暗号に触発されています。


stackoverflow.com/a/16097246/648265と同じで、まったく同じシーケンスのランダム性に失敗します。
ivan_pozdeev 2016

1
@ivan_pozdeev:理論的には、無限の計算能力を想定している、はい。ただし、hash()上記のコードで使用されているように、安全な疑似ランダム関数であると仮定すると、この構造は証明可能な疑似ランダム順列を生成します。キースペース全体の検索。これは、キーの長さで指数関数的です。適度なサイズのキー(128ビットなど)の場合でも、これは地球で利用可能な総計算能力を超えています。
Ilmari Karonen

(ところで、この引数をもう少し厳密にするために、私はhash( key + "/" + int2str(temp) )上記のアドホック構造をHMACに置き換えることをお勧めします。そのセキュリティは、基礎となるハッシュ圧縮関数のセキュリティに低下する可能性があります。また、HMACを使用すると、安全ではない非暗号ハッシュ関数を使用して、誰かが誤ってこの構造を使用しようとする可能性は低くなります。)
Ilmari Karonen

6

ここで説明されている私のXincrolアルゴリズムを使用できます。

http://openpatent.blogspot.co.il/2013/04/xincrol-unique-and-random-number.html

これは、配列、リスト、順列、または重いCPU負荷のない、ランダムだが一意の数値を生成する純粋なアルゴリズム手法です。

最新バージョンでは、番号の範囲を設定することもできます。たとえば、0〜1073741821の範囲の一意の乱数が必要な場合。

私は実際にそれを使用しました

  • すべての曲をランダムに再生するが、アルバム/ディレクトリごとに1回だけ再生するMP3プレーヤー
  • ピクセル単位のビデオフレーム溶解効果(高速かつスムーズ)
  • 署名とマーカーの画像に秘密の「ノイズ」フォグを作成する(ステガノグラフィー)
  • データベースを介して大量のJavaオブジェクトをシリアル化するためのデータオブジェクトID
  • トリプルマジョリティメモリビット保護
  • アドレス+値の暗号化(すべてのバイトが暗号化されるだけでなく、バ​​ッファー内の新しい暗号化された場所に移動されます)。これは本当に私に暗号解読の仲間を怒らせました:-)
  • プレーンテキストからプレーンライクな暗号化テキストへのSMS、電子メールなどの暗号化
  • テキサスホールデムポーカーカリキュレーター(THC)
  • シミュレーション用のゲームのいくつか、「シャッフル」、ランキング
  • もっと

オープンで無料です。試してみる...


その方法は10進数値に対して機能しますか?たとえば、3桁の10進カウンターをスクランブルして常に3桁の10進数の結果が得られるようにしますか?
Craig McQueen

Xorshiftアルゴリズムの例として、これはLFSRであり、関連するすべてのねじれがあります(たとえばk、シーケンス内で離れている値が同時に発生することはありません)。
ivan_pozdeev 2016

5

これを解決するために配列さえ必要ありません。

ビットマスクとカウンターが必要です。

カウンターをゼロに初期化し、連続する呼び出しでそれをインクリメントします。カウンターをビットマスク(起動時にランダムに選択、または固定)とXORして、疑似乱数を生成します。1000を超える数を使用できない場合は、9ビットよりも広いビットマスクを使用しないでください。(つまり、ビットマスクは511以下の整数です。)

カウンタが1000を超えたら、カウンタをゼロにリセットしてください。この時点で、別のランダムビットマスクを選択して(必要に応じて)、同じ番号のセットを異なる順序で生成できます。


2
それはLFSRよりも少ない人々をだますでしょう。
スターブルー09/10/22

512 ... 1023内の「ビットマスク」もOKです。もう少し偽のランダム性については、私の答えを参照してください。:-)
10

基本的にstackoverflow.com/a/16097246/648265と同等で、シーケンスのランダム性も失敗します。
ivan_pozdeev 2016

4

線形合同ジェネレーターが最も簡単な解決策になると思います。

ここに画像の説明を入力してください

また、acmの値には3つの制限しかない

  1. m cは比較的素数です。
  2. a-1 mのすべての素因数で割り切れる
  3. m 4で割り切れる場合、a-1 4で割り切れる

PSメソッドは既に言及されましたが、投稿には定数値に関する誤った仮定があります。以下の定数はあなたのケースではうまくいくはずです

あなたのケースでは、あなたが使用することができa = 1002c = 757m = 1001

X = (1002 * X + 757) mod 1001

3

最初に入力したコードは、最初のソリューションのロジックを使用しています。私はこれが「言語にとらわれない」ことを知っていますが、誰かが迅速で実用的な解決策を探している場合に備えて、これをC#の例として提示したかっただけです。

// Initialize variables
Random RandomClass = new Random();
int RandArrayNum;
int MaxNumber = 10;
int LastNumInArray;
int PickedNumInArray;
int[] OrderedArray = new int[MaxNumber];      // Ordered Array - set
int[] ShuffledArray = new int[MaxNumber];     // Shuffled Array - not set

// Populate the Ordered Array
for (int i = 0; i < MaxNumber; i++)                  
{
    OrderedArray[i] = i;
    listBox1.Items.Add(OrderedArray[i]);
}

// Execute the Shuffle                
for (int i = MaxNumber - 1; i > 0; i--)
{
    RandArrayNum = RandomClass.Next(i + 1);         // Save random #
    ShuffledArray[i] = OrderedArray[RandArrayNum];  // Populting the array in reverse
    LastNumInArray = OrderedArray[i];               // Save Last Number in Test array
    PickedNumInArray = OrderedArray[RandArrayNum];  // Save Picked Random #
    OrderedArray[i] = PickedNumInArray;             // The number is now moved to the back end
    OrderedArray[RandArrayNum] = LastNumInArray;    // The picked number is moved into position
}

for (int i = 0; i < MaxNumber; i++)                  
{
    listBox2.Items.Add(ShuffledArray[i]);
}

3

この方法は、制限が高く、少数の乱数のみを生成する場合に適しています。

#!/usr/bin/perl

($top, $n) = @ARGV; # generate $n integer numbers in [0, $top)

$last = -1;
for $i (0 .. $n-1) {
    $range = $top - $n + $i - $last;
    $r = 1 - rand(1.0)**(1 / ($n - $i));
    $last += int($r * $range + 1);
    print "$last ($r)\n";
}

番号は昇順で生成されますが、後でシャッフルできることに注意してください。


これは順列ではなく組み合わせを生成するため、stackoverflow.com
questions / 2394246 /…

1
テストは、これが低い数値に向かってバイアスがあることを示しています。2Mサンプルの測定された確率(top,n)=(100,10)は次のとおり(0.01047705, 0.01044825, 0.01041225, ..., 0.0088324, 0.008723, 0.00863635)です。私はPythonでテストしたので、ここで数学のわずかな違いが役割を果たすかもしれません(計算のためのすべての演算rが浮動小数点であることを確認しました)。
ivan_pozdeev 2016

はい、このメソッドが正しく機能するためには、上限が抽出される値の数よりもはるかに大きい必要があります。
salva

「上限が値の数よりもはるかに大きい」としても、「正しく」機能しません。確率は均一ではなく、マージンが少ないだけです。
ivan_pozdeev 2017

2

10ビットの優れた疑似乱数ジェネレータを使用して、1001〜1023を破棄し、0〜1000を残すことができます。

ここから、10ビットPRNGのデザインを取得します。

  • 10ビット、フィードバック多項式x ^ 10 + x ^ 7 + 1(期間1023)

  • ガロアLFSRを使用して高速コードを取得する


@Phobいいえ、線形フィードバックシフトレジスタに基づく10ビットのPRNGは通常、最初の値に戻る前にすべての値(1つを除く)を1回想定する構成から作成されるため、発生しません。つまり、サイクル中に1001を1回だけ選択します。
Nuoji 2013年

1
この質問の要点は、各番号を1回だけ選択することです。そして、あなたは1001が2回続けて発生しないと文句を言うのですか?最適な広がりを持つLFSRは、その空間内のすべての数値を疑似ランダムにトラバースし、サイクルを再開します。つまり、通常のランダム関数としては使用されません。ランダムとして使用する場合、通常はビットのサブセットのみを使用します。それについて少し読んでください、それはすぐに理にかなっています。
Nuoji 2013

1
唯一の問題は、特定のLFSRにはシーケンスが1つしかないため、選択された数値間に強い相関があり、特にすべての可能な組み合わせが生成されないことです。
ivan_pozdeev

2
public static int[] randN(int n, int min, int max)
{
    if (max <= min)
        throw new ArgumentException("Max need to be greater than Min");
    if (max - min < n)
        throw new ArgumentException("Range needs to be longer than N");

    var r = new Random();

    HashSet<int> set = new HashSet<int>();

    while (set.Count < n)
    {
        var i = r.Next(max - min) + min;
        if (!set.Contains(i))
            set.Add(i);
    }

    return set.ToArray();
}

N非反復乱数は、必要に応じてO(n)複雑になります。
注:ランダムは、スレッドセーフが適用された状態で静的でなければなりません。


O(n ^ 2)。再試行の数は、平均して、これまでに選択された要素の数に比例するため。
ivan_pozdeev 2016

考えてみてください。min= 0 max = 10000000およびN = 5を選択した場合、選択した数に関係なく〜= 0を再試行します。しかし、はい、あなたはmax-minが小さい場合、o(N)が分裂するという点を持っています。
Erez Robinson、

N <<(max-min)の場合、それでも比例します。係数が非常に小さいだけです。そして、係数は漸近的推定では重要ではありません。
ivan_pozdeev 16

これはO(n)ではありません。セットに値が含まれるたびに、これは追加のループです。
パパラッツォ2017年

2

シャッフルされたリストを何度も何度もやり直したいとしO(n)ます。最初からやり直すたびに遅延は発生しませんが、その場合は次のようにします。

  1. AとBの2つのリストを作成します(0から1000まで)2n

  2. フィッシャーイェイツを使用してリストAをシャッフルするには、時間がかかりますn

  3. 数字を描くときは、他のリストで1ステップのフィッシャーイェーツのシャッフルを行います。

  4. カーソルがリストの最後にあるとき、他のリストに切り替えます。

前処理

cursor = 0

selector = A
other    = B

shuffle(A)

ドロー

temp = selector[cursor]

swap(other[cursor], other[random])

if cursor == N
then swap(selector, other); cursor = 0
else cursor = cursor + 1

return temp

2つのリストを保持する必要はありません。または、見つめる前にリストを使い果たす必要はありません。フィッシャーイェイツは、初期状態からランダムにランダムな結果を返します。説明については、stackoverflow.com / a / 158742/648265を参照してください。
ivan_pozdeev 2016

@ivan_pozdeevはい、同じ結果ですが、ここでの私の考えは、描画アクションのシャッフル部分を作成してO(1)を償却することです。
Khaled.K 2016

分かりませんでした あなたはすべてのリストをリセットする必要はありません再シャッフルする前に。シャッフル[1,3,4,5,2]すると、シャッフルと同じ結果になり[1,2,3,4,5]ます。
ivan_pozdeev

2

質問0と上限Nの間のKの非繰り返し整数のリストを効率的に生成するにはどうすればよいですか?スタートアップ費用))受け入れられた答えの簡単な微調整があります。

初期化された配列を使用する代わりに、整数から整数へ、空の順序付けられていないマップを作成します(空の順序付けされたマップは要素ごとにO(log k)を取ります)。それが最大の場合、最大値を1000に設定し、

  1. 0から最大の間の乱数rを選択します。
  2. マップ要素rとmaxの両方が順序付けられていないマップに存在することを確認してください。それらが存在しない場合は、インデックスと等しい値でそれらを作成します。
  3. 要素rとmaxを入れ替えます
  4. 要素の最大値を返し、最大値を1だけ減らします(最大値が負になる場合は完了です)。
  5. 手順1に戻ります。

初期化された配列を使用した場合との唯一の違いは、要素の初期化が延期/スキップされることですが、同じPRNGからまったく同じ数が生成されます。


1

別の可能性:

フラグの配列を使用できます。それがすでに選択されている場合は、次のものを選択してください。

ただし、1000回の呼び出しの後、関数が終了することはないため、安全策を講じる必要があることに注意してください。


これはO(k ^ 2)であり、これまでに選択された値の数に平均して比例する追加のステップ数があります。
ivan_pozdeev 2016

1

これは、試してみることができるサンプルCOBOLコードです。
私はあなたにRANDGEN.exeファイルを送ることができるので、それを使って遊んで、あなたが望んでいるかどうかを確認することができます。

   IDENTIFICATION DIVISION.
   PROGRAM-ID.  RANDGEN as "ConsoleApplication2.RANDGEN".
   AUTHOR.  Myron D Denson.
   DATE-COMPILED.
  * ************************************************************** 
  *  SUBROUTINE TO GENERATE RANDOM NUMBERS THAT ARE GREATER THAN
  *    ZERO AND LESS OR EQUAL TO THE RANDOM NUMBERS NEEDED WITH NO
  *    DUPLICATIONS.  (CALL "RANDGEN" USING RANDGEN-AREA.)
  *     
  *  CALLING PROGRAM MUST HAVE A COMPARABLE LINKAGE SECTION
  *    AND SET 3 VARIABLES PRIOR TO THE FIRST CALL IN RANDGEN-AREA     
  *
  *    FORMULA CYCLES THROUGH EVERY NUMBER OF 2X2 ONLY ONCE. 
  *    RANDOM-NUMBERS FROM 1 TO RANDOM-NUMBERS-NEEDED ARE CREATED 
  *    AND PASSED BACK TO YOU.
  *
  *  RULES TO USE RANDGEN:
  *
  *    RANDOM-NUMBERS-NEEDED > ZERO 
  *     
  *    COUNT-OF-ACCESSES MUST = ZERO FIRST TIME CALLED.
  *         
  *    RANDOM-NUMBER = ZERO, WILL BUILD A SEED FOR YOU
  *    WHEN COUNT-OF-ACCESSES IS ALSO = 0 
  *     
  *    RANDOM-NUMBER NOT = ZERO, WILL BE NEXT SEED FOR RANDGEN
  *    (RANDOM-NUMBER MUST BE <= RANDOM-NUMBERS-NEEDED)       
  *     
  *    YOU CAN PASS RANDGEN YOUR OWN RANDOM-NUMBER SEED
  *     THE FIRST TIME YOU USE RANDGEN.
  *     
  *    BY PLACING A NUMBER IN RANDOM-NUMBER FIELD
  *      THAT FOLLOWES THESE SIMPLE RULES:
  *        IF COUNT-OF-ACCESSES = ZERO AND 
  *        RANDOM-NUMBER > ZERO AND 
  *        RANDOM-NUMBER <= RANDOM-NUMBERS-NEEDED
  *       
  *    YOU CAN LET RANDGEN BUILD A SEED FOR YOU
  *     
  *      THAT FOLLOWES THESE SIMPLE RULES:
  *        IF COUNT-OF-ACCESSES = ZERO AND 
  *        RANDOM-NUMBER = ZERO AND 
  *        RANDOM-NUMBER-NEEDED > ZERO  
  *         
  *     TO INSURING A DIFFERENT PATTERN OF RANDOM NUMBERS
  *        A LOW-RANGE AND HIGH-RANGE IS USED TO BUILD
  *        RANDOM NUMBERS.
  *        COMPUTE LOW-RANGE =
  *             ((SECONDS * HOURS * MINUTES * MS) / 3).         
  *        A HIGH-RANGE = RANDOM-NUMBERS-NEEDED + LOW-RANGE
  *        AFTER RANDOM-NUMBER-BUILT IS CREATED 
  *        AND IS BETWEEN LOW AND HIGH RANGE
  *        RANDUM-NUMBER = RANDOM-NUMBER-BUILT - LOW-RANGE
  *               
  * **************************************************************         
   ENVIRONMENT DIVISION.
   INPUT-OUTPUT SECTION.
   FILE-CONTROL.
   DATA DIVISION.
   FILE SECTION.
   WORKING-STORAGE SECTION.
   01  WORK-AREA.
       05  X2-POWER                     PIC 9      VALUE 2. 
       05  2X2                          PIC 9(12)  VALUE 2 COMP-3.
       05  RANDOM-NUMBER-BUILT          PIC 9(12)  COMP.
       05  FIRST-PART                   PIC 9(12)  COMP.
       05  WORKING-NUMBER               PIC 9(12)  COMP.
       05  LOW-RANGE                    PIC 9(12)  VALUE ZERO.
       05  HIGH-RANGE                   PIC 9(12)  VALUE ZERO.
       05  YOU-PROVIDE-SEED             PIC X      VALUE SPACE.
       05  RUN-AGAIN                    PIC X      VALUE SPACE.
       05  PAUSE-FOR-A-SECOND           PIC X      VALUE SPACE.   
   01  SEED-TIME.
       05  HOURS                        PIC 99.
       05  MINUTES                      PIC 99.
       05  SECONDS                      PIC 99.
       05  MS                           PIC 99. 
  *
  * LINKAGE SECTION.
  *  Not used during testing  
   01  RANDGEN-AREA.
       05  COUNT-OF-ACCESSES            PIC 9(12) VALUE ZERO.
       05  RANDOM-NUMBERS-NEEDED        PIC 9(12) VALUE ZERO.
       05  RANDOM-NUMBER                PIC 9(12) VALUE ZERO.
       05  RANDOM-MSG                   PIC X(60) VALUE SPACE.
  *    
  * PROCEDURE DIVISION USING RANDGEN-AREA.
  * Not used during testing 
  *  
   PROCEDURE DIVISION.
   100-RANDGEN-EDIT-HOUSEKEEPING.
       MOVE SPACE TO RANDOM-MSG. 
       IF RANDOM-NUMBERS-NEEDED = ZERO
         DISPLAY 'RANDOM-NUMBERS-NEEDED ' NO ADVANCING
         ACCEPT RANDOM-NUMBERS-NEEDED.
       IF RANDOM-NUMBERS-NEEDED NOT NUMERIC 
         MOVE 'RANDOM-NUMBERS-NEEDED NOT NUMERIC' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF RANDOM-NUMBERS-NEEDED = ZERO
         MOVE 'RANDOM-NUMBERS-NEEDED = ZERO' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF COUNT-OF-ACCESSES NOT NUMERIC
         MOVE 'COUNT-OF-ACCESSES NOT NUMERIC' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF COUNT-OF-ACCESSES GREATER THAN RANDOM-NUMBERS-NEEDED
         MOVE 'COUNT-OF-ACCESSES > THAT RANDOM-NUMBERS-NEEDED'
           TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF YOU-PROVIDE-SEED = SPACE AND RANDOM-NUMBER = ZERO
         DISPLAY 'DO YOU WANT TO PROVIDE SEED  Y OR N: '
           NO ADVANCING
           ACCEPT YOU-PROVIDE-SEED.  
       IF RANDOM-NUMBER = ZERO AND
          (YOU-PROVIDE-SEED = 'Y' OR 'y')
         DISPLAY 'ENTER SEED ' NO ADVANCING
         ACCEPT RANDOM-NUMBER. 
       IF RANDOM-NUMBER NOT NUMERIC
         MOVE 'RANDOM-NUMBER NOT NUMERIC' TO RANDOM-MSG
         GO TO 900-EXIT-RANDGEN.
   200-RANDGEN-DATA-HOUSEKEEPING.      
       MOVE FUNCTION CURRENT-DATE (9:8) TO SEED-TIME.
       IF COUNT-OF-ACCESSES = ZERO
         COMPUTE LOW-RANGE =
                ((SECONDS * HOURS * MINUTES * MS) / 3).
       COMPUTE RANDOM-NUMBER-BUILT = RANDOM-NUMBER + LOW-RANGE.  
       COMPUTE HIGH-RANGE = RANDOM-NUMBERS-NEEDED + LOW-RANGE.
       MOVE X2-POWER TO 2X2.             
   300-SET-2X2-DIVISOR.
       IF 2X2 < (HIGH-RANGE + 1) 
          COMPUTE 2X2 = 2X2 * X2-POWER
           GO TO 300-SET-2X2-DIVISOR.    
  * *********************************************************         
  *  IF FIRST TIME THROUGH AND YOU WANT TO BUILD A SEED.    *
  * ********************************************************* 
       IF COUNT-OF-ACCESSES = ZERO AND RANDOM-NUMBER = ZERO
          COMPUTE RANDOM-NUMBER-BUILT =
                ((SECONDS * HOURS * MINUTES * MS) + HIGH-RANGE).
       IF COUNT-OF-ACCESSES = ZERO        
         DISPLAY 'SEED TIME ' SEED-TIME 
               ' RANDOM-NUMBER-BUILT ' RANDOM-NUMBER-BUILT 
               ' LOW-RANGE  ' LOW-RANGE.          
  * *********************************************     
  *    END OF BUILDING A SEED IF YOU WANTED TO  * 
  * *********************************************               
  * ***************************************************
  * THIS PROCESS IS WHERE THE RANDOM-NUMBER IS BUILT  *  
  * ***************************************************   
   400-RANDGEN-FORMULA.
       COMPUTE FIRST-PART = (5 * RANDOM-NUMBER-BUILT) + 7.
       DIVIDE FIRST-PART BY 2X2 GIVING WORKING-NUMBER 
         REMAINDER RANDOM-NUMBER-BUILT. 
       IF RANDOM-NUMBER-BUILT > LOW-RANGE AND
          RANDOM-NUMBER-BUILT < (HIGH-RANGE + 1)
         GO TO 600-RANDGEN-CLEANUP.
       GO TO 400-RANDGEN-FORMULA.
  * *********************************************     
  *    GOOD RANDOM NUMBER HAS BEEN BUILT        *               
  * *********************************************
   600-RANDGEN-CLEANUP.
       ADD 1 TO COUNT-OF-ACCESSES.
       COMPUTE RANDOM-NUMBER = 
            RANDOM-NUMBER-BUILT - LOW-RANGE. 
  * *******************************************************
  * THE NEXT 3 LINE OF CODE ARE FOR TESTING  ON CONSOLE   *  
  * *******************************************************
       DISPLAY RANDOM-NUMBER.
       IF COUNT-OF-ACCESSES < RANDOM-NUMBERS-NEEDED
        GO TO 100-RANDGEN-EDIT-HOUSEKEEPING.     
   900-EXIT-RANDGEN.
       IF RANDOM-MSG NOT = SPACE
        DISPLAY 'RANDOM-MSG: ' RANDOM-MSG.
        MOVE ZERO TO COUNT-OF-ACCESSES RANDOM-NUMBERS-NEEDED RANDOM-NUMBER. 
        MOVE SPACE TO YOU-PROVIDE-SEED RUN-AGAIN.
       DISPLAY 'RUN AGAIN Y OR N '
         NO ADVANCING.
       ACCEPT RUN-AGAIN.
       IF (RUN-AGAIN = 'Y' OR 'y')
         GO TO 100-RANDGEN-EDIT-HOUSEKEEPING.
       ACCEPT PAUSE-FOR-A-SECOND.
       GOBACK.

1
これが実際にOPのニーズを満たすことができるかどうかはわかりませんが、COBOLの貢献に賛成です!
Mac

1

ここでの回答のほとんどは、同じ数値を2回返さないことを保証できません。これが正しい解決策です:

int nrrand(void) {
  static int s = 1;
  static int start = -1;
  do {
    s = (s * 1103515245 + 12345) & 1023;
  } while (s >= 1001);
  if (start < 0) start = s;
  else if (s == start) abort();

  return s;
}

制約が適切に指定されているかどうかはわかりません。他の1000個の出力の後に値を繰り返すことが許可されていると想定しますが、どちらも1000のセットの最初と最後に表示される限り、0の直後に0が続くことを単純に許可します。逆に、繰り返しの間に他の1000個の値があると、その制限の外で発生した他の値がないため、シーケンスは毎回まったく同じ方法でシーケンス自体を再生します。

値を繰り返す前に、少なくとも500の他の値を常に保証する方法を次に示します。

int nrrand(void) {
  static int h[1001];
  static int n = -1;

  if (n < 0) {
    int s = 1;
    for (int i = 0; i < 1001; i++) {
      do {
        s = (s * 1103515245 + 12345) & 1023;
      } while (s >= 1001);
      /* If we used `i` rather than `s` then our early results would be poorly distributed. */
      h[i] = s;
    }
    n = 0;
  }

  int i = rand(500);
  if (i != 0) {
      i = (n + i) % 1001;
      int t = h[i];
      h[i] = h[n];
      h[n] = t;
  }
  i = h[n];
  n = (n + 1) % 1001;

  return i;
}

これは、stackoverflow.com/ a/196164/648265のようなLCGであり、シーケンスやその他の関連するキンクはランダムではありません。
ivan_pozdeev 2016

@ivan_pozdeev鉱山はLCGよりも優れています。これは、1001番目の呼び出しで重複を返さないことを保証するためです。
sh1 2016

1

Nが1000より大きく、K個のランダムサンプルを描画する必要がある場合、これまでのサンプルを含むセットを使用できます。ドローごとに拒否サンプリングを使用します。これは「ほぼ」O(1)操作になるため、合計実行時間はO(N)ストレージでほぼO(K)になります。

このアルゴリズムは、KがNに「近い」ときに衝突します。つまり、実行時間はO(K)よりもはるかに悪くなります。単純な修正は、論理を逆にすることです。これにより、K> N / 2の場合、まだ描画されていないすべてのサンプルの記録を保持できます。ドローごとに、拒否セットからサンプルが削除されます。

拒否サンプリングのもう1つの明白な問題は、それがO(N)ストレージであることです。これは、Nが数十億以上の場合は悪いニュースです。ただし、その問題を解決するアルゴリズムがあります。このアルゴリズムは、発明者からVitterのアルゴリズムと呼ばれています。アルゴリズムはここで説明されています。Vitterのアルゴリズムの要点は、各描画の後に、一定のサンプリングを保証する特定の分布を使用してランダムスキップを計算することです。


皆さん、お願いします!フィッシャーイェイツの方法は壊れています。最初の確率を1 / Nの確率で選択し、2番目の確率を1 /(N-1)!= 1 / Nの確率で選択します。これは偏ったサンプリング方法です!バイアスを解決するには、ビッターのアルゴリズムが本当に必要です。
Emanuel Landeholm

0

フィッシャーイェイツ

for i from n−1 downto 1 do
     j ← random integer such that 0 ≤ j ≤ i
     exchange a[j] and a[i]

最後の2つのスワップは1つだけ必要なので、実際にはO(n-1)です。
これはC#です。

public static List<int> FisherYates(int n)
{
    List<int> list = new List<int>(Enumerable.Range(0, n));
    Random rand = new Random();
    int swap;
    int temp;
    for (int i = n - 1; i > 0; i--)
    {
        swap = rand.Next(i + 1);  //.net rand is not inclusive
        if(swap != i)  // it can stay in place - if you force a move it is not a uniform shuffle
        {
            temp = list[i];
            list[i] = list[swap];
            list[swap] = temp;
        }
    }
    return list;
}

そここれで答えがすでにあるが、それはかなり長い息切れして、あなたは1(0ではない)で停止することができます認識していない
パパラッチ


-1

誰かが「Excelで乱数を作成する」と投稿しました。私はこの理想を使用しています。str.indexとstr.ranの2つの部分で構造を作成します。10個の乱数に対して、10個の構造の配列を作成します。str.indexを0から9に設定し、str.ranを別の乱数に設定します。

for(i=0;i<10; ++i) {
      arr[i].index = i;
      arr[i].ran   = rand();
}

配列をarr [i] .ranの値でソートします。str.indexはランダムな順序になりました。以下はCコードです:

#include <stdio.h>
#include <stdlib.h>

struct RanStr { int index; int ran;};
struct RanStr arr[10];

int sort_function(const void *a, const void *b);

int main(int argc, char *argv[])
{
   int cnt, i;

   //seed(125);

   for(i=0;i<10; ++i)
   {
      arr[i].ran   = rand();
      arr[i].index = i;
      printf("arr[%d] Initial Order=%2d, random=%d\n", i, arr[i].index, arr[i].ran);
   }

   qsort( (void *)arr, 10, sizeof(arr[0]), sort_function);
   printf("\n===================\n");
   for(i=0;i<10; ++i)
   {
      printf("arr[%d] Random  Order=%2d, random=%d\n", i, arr[i].index, arr[i].ran);
   }

   return 0;
}

int sort_function(const void *a, const void *b)
{
   struct RanStr *a1, *b1;

   a1=(struct RanStr *) a;
   b1=(struct RanStr *) b;

   return( a1->ran - b1->ran );
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.