web-dev-qa-db-ja.com

Pythonで繰り返しのない乱数を生成する

これは、質問のように聞こえるよりもトリッキーなものの1つなので、良い答えを考えることができないので、スタックオーバーフローに切り替えます。これが私が欲しいものです:シリアル番号に使用するためにランダムな順序で0から1,000,000,000までの数字の単純なリストを生成するためにPythonが必要です(あなたができないように乱数を使用して、割り当てられた数、またはタイミング攻撃を行う数を簡単に、つまり次の攻撃を推測します。これらの数値は、それらにリンクされた情報とともにデータベーステーブル(インデックス付き)に保存されます。これらを生成するプログラムは、永久に実行されるため、内部状態に依存できません。

大したことないですか?数のリストを生成し、それらを配列に押し込み、Python "random.shuffle(big_number_array)"を使用すれば完了です。問題は、数値のリスト(したがって、ファイルを読み取り、上から1つポップし、ファイルを保存して閉じます)むしろ、オンザフライで生成したいと思います。問題は、考えられる解決策に問題があることです。

1)乱数を生成し、それがすでに使用されているかどうかを確認します。それが使用されている場合は、新しい番号を生成し、チェックし、未使用の番号が見つかるまで必要に応じて繰り返します。ここでの問題は、使用されていない番号を取得する前に、不運になり、多くの使用済み番号を生成する可能性があることです。考えられる修正:非常に大きな数のプールを使用して、この可能性を減らします(ただし、私は愚かな長い数になってしまいます)。

2)乱数を生成し、それがすでに使用されているかどうかを確認します。使用されている場合は、番号から1を加算または減算して、もう一度確認します。未使用の番号に到達するまで繰り返します。問題は、バイアスを導入したため、これはもはや乱数ではないことです(最終的には数のまとまりが発生し、成功の可能性がより高い次の数を予測できるようになります)。

3)乱数を生成し、それがすでに使用されているかどうかを確認します。ランダムに生成された別の乱数を加算または減算して再度使用した場合、問題は、乱数を生成してソリューション1のようにチェックすることに戻ることです。

4)それを吸い上げ、ランダムリストを生成して保存し、デーモンにそれらをキューに入れて、利用可能な数があるようにします(代わりに、ファイルを常に開いたり閉じたりしないで、バッチ処理します)。

5)はるかに大きな乱数を生成し、ハッシュして(つまり、MD5を使用して)小さな数値を取得します。衝突が発生することはめったにありませんが、結局、必要以上の数になってしまいます。

6)衝突の可能性を減らすために、時間ベースの情報を乱数(つまり、unixタイムスタンプ)に追加または追加します。ここでも、必要以上の数を取得します。

誰もが「衝突」の可能性を減らす(つまり、すでに使用されている乱数を生成する)優れたアイデアを持っていますが、その数を「小さく」(つまり、10億未満(またはあなたのヨーロッパ人=))。

答えと私がそれを受け入れた理由:

だから私は単に1を使い、それが問題にならないことを望みますが、もしそうなら、私はすべての数を生成して格納する決定論的解決策を取り、新しい乱数を取得する保証があるようにします。 「小さな」数字を使用します(MD5などの代わりに9桁)。

38
bigredbob

これはきちんとした問題であり、私はしばらく考えていました( Sjoerd's に似た解決策を使用して)。

あなたのポイント1)を使い、心配しないでください。

実際のランダム性を想定すると、以前に乱数が選択されている確率は、以前に選択された数をプールのサイズで割った数、つまり最大数です。

10億の数値、つまり9桁しか必要としない場合:あと3桁にして、12桁のシリアル番号を取得します(4桁の3つのグループ–わかりやすく読みやすい)。

以前に10億の数値を選択したことに近い場合でも、新しい数値が既に使用されている確率はまだ0.1%です。

手順1を実行して、もう一度描画します。 「無限」ループがないかどうかを確認することもできます。たとえば、1000回を超えないようにしてから、1(またはその他)を追加するようにフォールバックします。

あなたはそのフォールバックが使用される前に宝くじに勝つでしょう。

24
balpha

Format-Preserving Encryption を使用してカウンターを暗号化できます。カウンターは0から上向きになり、暗号化は選択したキーを使用して、任意の基数と幅のランダムに見える値に変換します。

ブロック暗号は通常、例えば64ビットまたは128ビット。ただし、フォーマットを保持する暗号化を使用すると、AESなどの標準的な暗号を使用して、任意の基数と幅(たとえば、基数10、質問のパラメーターの幅9)の幅の狭い暗号を、まだアルゴリズムを使用して作成できます。暗号的に堅牢です。

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

AES-FFX は、これを実現するために提案された標準的な方法の1つです。

私はいくつかの基本的なPython AES-FFXのコードを試してみました-- Pythonコードはこちら)を参照してください (ただし、 AES-FFX仕様に完全には準拠していません。たとえば、カウンターをランダムに見える7桁の10進数に暗号化できます。例:

0000000   0731134
0000001   6161064
0000002   8899846
0000003   9575678
0000004   3030773
0000005   2748859
0000006   5127539
0000007   1372978
0000008   3830458
0000009   7628602
0000010   6643859
0000011   2563651
0000012   9522955
0000013   9286113
0000014   5543492
0000015   3230955
...       ...

Pythonでの別の例として、AES-FFX以外の(私が思う)方法を使用する場合は、Feistel暗号を使用してFPEを実行する このブログ投稿「アカウント番号を生成する方法」 を参照してください。 0から2 ^ 32-1までの数値を生成します。

12
Craig McQueen

一部のモジュラー算術および素数を使用すると、0から大きな素数までのすべての数を順不同で作成できます。 あなたがあなたの数を注意深く選ぶならば、次の数は推測するのが難しいです。

modulo = 87178291199 # prime
incrementor = 17180131327 # relative prime

current = 433494437 # some start value
for i in xrange(1, 100):
    print current
    current = (current + incrementor) % modulo
8
Sjoerd

それらがランダムである必要はないが、明らかに線形ではない場合(1、2、3、4、...)、次に簡単なアルゴリズムを示します。

2つの素数を選びます。そのうちの1つは、生成できる最大数になるため、約10億個になるはずです。もう一方はかなり大きいはずです。

max_value = 795028841
step = 360287471
previous_serial = 0
for i in xrange(0, max_value):
    previous_serial += step
    previous_serial %= max_value
    print "Serial: %09i" % previous_serial

前回のシリアルを毎回保存するだけで、どこで中断したかがわかります。これが機能することを数学的に証明することはできません(これらの特定のクラスから時間がかかりすぎる)が、素数が小さい場合は明らかに正しいです。

s = set()
with open("test.txt", "w+") as f:
    previous_serial = 0
    for i in xrange(0, 2711):
        previous_serial += 1811
        previous_serial %= 2711
        assert previous_serial not in s
        s.add(previous_serial)

また、9桁の素数を使って経験的に証明することもできます。少し作業(またはメモリ)が増えるだけです。

これは、いくつかのシリアル番号が与えられた場合、値が何であるかを理解することが可能であることを意味します。

6
Glenn Maynard

暗号的に安全なものは必要ないが、「十分に難読化されている」だけの場合...

Galois Fields

Galois Fields で操作を試すことができます。 GF(2)32、単純なインクリメントカウンターxを一見ランダムなシリアル番号yにマップするには:

x = counter_value
y = some_galois_function(x)
  • 定数を掛ける
    • 逆は定数の逆数を掛けることです
  • 累乗x
  • 逆数x-1
    • 累乗の特別な場合n
    • それはそれ自身の逆です
  • 累乗 プリミティブ要素:aバツ
    • これには簡単に計算できる逆数(離散対数)がないことに注意してください
    • aprimitive element 、別名 generator であることを確認します

これらの操作の多くには逆の操作があります。つまり、シリアル番号を指定すると、それが導出された元のカウンター値を計算できます。

Galois Field for Pythonのライブラリを見つけることに関しては...いい質問です。あなたがスピードを必要としないなら(これには必要ないでしょう)、あなたはあなた自身を作ることができます。私はこれらを試していません:

GF(2)の行列乗算

GF(2)の適切な32×32可逆行列を選択し、32ビットの入力カウンターを乗算します。 S.Lott's answer で説明されているように、これは概念的にはLFSRに関連しています。

[〜#〜] crc [〜#〜]

関連する可能性は [〜#〜] crc [〜#〜] 計算を使用することです。 GF(2)の既約多項式による長除算の残りに基づいています。 PythonコードはCRC( crcmodpycrc )ですぐに利用できますが、通常使用されているものとは異なる既約多項式を選択することもできます、あなたの目的のため。私は理論に少しあいまいですが、32ビットCRCは4バイト入力の可能なすべての組み合わせに対して一意の値を生成するはずだと思います。これを確認してください。これを実験的に確認することは非常に簡単です。出力を入力にフィードバックし、それが長さ2の完全なサイクルを生成することを確認する32-1(ゼロは単にゼロにマップされます)。このチェックを機能させるには、CRCアルゴリズムの初期XORまたは最終XORを取り除く必要がある場合があります。

6
Craig McQueen

アプローチ1)の問題を過大評価していると思います。ハードリアルタイムの要件がない限り、ランダムな選択によるチェックだけでかなり速く終了します。複数の反復が必要になる確率は、指数関数的に減衰します。 1億個の数値が出力される(fillfactorが10%)と、10億分の1の確率で9回を超える反復が必要になります。数値の50%が取得されたとしても、平均して2回の反復が必要であり、10億分の1の確率で30を超えるチェックが必要になります。または、数値の99%がすでに使用されている極端な場合でも、妥当である可能性があります。100回の反復を平均し、2062回の反復を必要とする10億分の1の変化があります。

5
Ants Aasma

標準の線形合同乱数ジェネレーターのシードシーケンスは、開始シード値からの数値の完全なセットが生成されるまで繰り返すことができません。次に、正確に繰り返す必要があります。

多くの場合、内部シードは大きくなります(48または64ビット)。ビットのセット全体がランダムではないため、生成される数値は小さくなります(通常32ビット)。シード値に従うと、それらは明確な非反復シーケンスを形成します。

問題は本質的に、「十分な」数を生成する良い種を見つけることです。シードを選択し、開始シードに戻るまで数値を生成できます。これがシーケンスの長さです。数百万または数十億の数になる可能性があります。

Knuthには、一意の数の非常に長いシーケンスを生成する適切なシードを選択するためのガイドラインがいくつかあります。

4
S.Lott

ランダムな間隔を1つずつ減らすだけで、1)を実行して、誤った乱数が多すぎるという問題に遭遇することはありません。

この方法が機能するためには、すでに与えられている数値(とにかく実行したい数値)を保存し、取得した数値の数も保存する必要があります。

10個の数値を収集した後、可能な乱数のプールが10減少することは明らかです。したがって、1〜1.000.000の間ではなく、1〜999.990の間の数を選択する必要があります。もちろん、この数は実際の数ではなくインデックスにすぎません(収集された10個の数が999.991、999.992、…でない限り)。すでに収集されているすべての数を省略して、今から1から数える必要があります。

もちろん、アルゴリズムは1から1.000.000まで数えるよりも賢いはずですが、方法を理解していただければ幸いです。

どちらかが当てはまる乱数を取得するまで、乱数を描くのは好きではありません。それはただ間違っていると感じています。

1
Debilski

私の解決策 https://github.com/glushchenko/python-unique-id 、1,000,000,000バリエーションのマトリックスを拡張して楽しんでください。

1
fluder

次のように、定義されたしきい値内の完全に乱数のリストを生成するには、次のようにします。

plist=list()
length_of_list=100
upbound=1000
lowbound=0
while len(pList)<(length_of_list):
     pList.append(rnd.randint(lowbound,upbound))
     pList=list(set(pList))
0
David moreno

これは暗号的に安全である必要がありますか、それとも推測が困難ですか?衝突はどれほど悪いですか?それは、暗号的に強力で衝突がゼロである必要がある場合、残念ながら不可能だからです。

0
Andrew McGregor

カジュアルな観察者が次の値を推測できないほど十分であれば、 linear congruential generator または単純な linear feedback shift register のようなものを使用できますさらに値が必要な場合に備えて、値を生成し、データベースに状態を保持します。これらの権利を使用する場合、値は宇宙の終わりまで繰り返されません。 乱数ジェネレーターのリスト でさらに多くのアイデアを見つけることができます。

次の値を推測することに真剣に関心を持つ人がいるかもしれないと思われる場合は、データベースシーケンスを使用して、生成した値をカウントし、暗号化アルゴリズムで暗号化するか、別の暗号学的に強力な完全機能を使用できます。ただし、生成した一連の連続した数値を取得できる場合は、暗号化アルゴリズムが簡単に破られないように注意する必要があります-単純な [〜#〜] rsa [〜#〜] 、たとえば、 Franklin-Reiter Related Message Attack のため、これを実行しません。

0

以下で使用するアプローチの説明を書き始めましたが、それを実装するだけの方が簡単で正確でした。このアプローチには、生成した数が多いほど速くなるという奇妙な動作があります。しかし、それは機能し、事前にすべての数値を生成する必要はありません。

単純な最適化として、最初にこのクラスで確率アルゴリズムを使用する(乱数を生成し、それが使用された数値のセットにない場合は、それをセットに追加して返す)ようにして、衝突率を追跡します。衝突率が悪化したら、ここで使用する決定論的アプローチに切り替えます。

import random

class NonRepeatingRandom(object):

    def __init__(self, maxvalue):
        self.maxvalue = maxvalue
        self.used = set()

    def next(self):
        if len(self.used) >= self.maxvalue:
            raise StopIteration
        r = random.randrange(0, self.maxvalue - len(self.used))
        result = 0
        for i in range(1, r+1):
            result += 1
            while result in self.used:
                 result += 1
        self.used.add(result)
        return result

    def __iter__(self):
        return self

    def __getitem__(self):
        raise NotImplemented

    def get_all(self):
        return [i for i in self]

>>> n = NonRepeatingRandom(20)
>>> n.get_all()
[12, 14, 13, 2, 20, 4, 15, 16, 19, 1, 8, 6, 7, 9, 5, 11, 10, 3, 18, 17]
0
Robert Rossney

少し遅い答えですが、これが提案されたところはどこにもありません。

id モジュールを使用して globally unique identifiers を作成しないのはなぜですか

0
Mew

私は問題自体を再考します...あなたは番号で連続した何かをしているようではありません...そしてあなたはそれらを持っている列にインデックスを持っています。 必要数値になるのですか?

シャハッシュを考えてみてください...実際には全部が必要なわけではありません。 gitまたは他のURL短縮サービスが行うことを実行し、ハッシュの最初の3/4/5文字を取得します。各文字が10ではなく36の可能な値を持つようになったとすると、999,999の組み合わせではなく、2,176,782,336の組み合わせになります(6桁)。それを組み合わせが存在するかどうかの純粋なチェック(純粋なインデックスクエリ)とタイムスタンプ+乱数などのシードを組み合わせると、ほとんどすべての状況で実行できます。

0
Sudhir Jonathan

私は同じ問題にぶつかり、これに到達する前に 別のタイトルの質問 を開きました。 My solution は、_itersampleと呼ばれる間隔_[0,maximal)_のインデックス(つまり、繰り返されない数値)のランダムサンプルジェネレーターです。以下に使用例をいくつか示します。

_import random
generator=itersample(maximal)
another_number=generator.next() # pick the next non-repeating random number
_

または

_import random
generator=itersample(maximal)
for random_number in generator:
    # do something with random_number
    if some_condition: # exit loop when needed
        break
_

itersampleは繰り返されないランダムな整数を生成します。ストレージの必要性は選択された数値に制限され、nの数値を選択するために必要な時間は(いくつかのテストで確認されているように)O(n log(n))でなければなりません。 maximalの敬意。

itersampleのコードは次のとおりです:

_import random
def itersample(c): # c = upper bound of generated integers
    sampled=[]
    def fsb(a,b): # free spaces before middle of interval a,b
        fsb.idx=a+(b+1-a)/2
        fsb.last=sampled[fsb.idx]-fsb.idx if len(sampled)>0 else 0
        return fsb.last
    while len(sampled)<c:
        sample_index=random.randrange(c-len(sampled))
        a,b=0,len(sampled)-1
        if fsb(a,a)>sample_index:
            yielding=sample_index
            sampled.insert(0,yielding)
            yield yielding
        Elif fsb(b,b)<sample_index+1:
            yielding=len(sampled)+sample_index
            sampled.insert(len(sampled),yielding)
            yield yielding
        else: # sample_index falls inside sampled list
            while a+1<b:
                if fsb(a,b)<sample_index+1:
                    a=fsb.idx
                else:
                    b=fsb.idx
            yielding=a+1+sample_index
            sampled.insert(a+1,yielding)
            yield yielding
_
0
mmj