簡體   English   中英

在Python中生成非重復隨機數

[英]Generating non-repeating random numbers in Python

好吧,這是一個比它聽起來更棘手的問題,所以我轉向堆棧溢出,因為我想不出一個好的答案。 這就是我想要的:我需要Python以隨機順序生成一個簡單的0到1,000,000,000的數字列表,用於序列號(使用隨機數,這樣你就無法分辨已經分配了多少或做了時間攻擊很容易,即猜測下一個會出現的問題)。 這些數字與鏈接到它們的信息一起存儲在數據庫表(索引)中。 生成它們的程序不會永遠運行,因此它不能依賴於內部狀態。

沒什么大不了的? 只需生成一個數字列表,將它們推入一個數組並使用Python“random.shuffle(big_number_array)”,我們就完成了。 問題是我想避免必須存儲一個數字列表(從而讀取文件,彈出一個頂部,保存文件並關閉它)。 我寧願在飛行中生成它們。 問題是我能想到的解決方案有問題:

1)生成一個隨機數,然后檢查它是否已被使用。 如果已經使用它生成一個新的數字,檢查,根據需要重復,直到找到一個未使用的數字。 這里的問題是,在獲得未使用的數字之前,我可能會感到不幸並生成大量使用過的數字。 可能的解決方法:使用一個非常大的數字池來減少這種情況的可能性(但最后我得到了愚蠢的長數字)。

2)生成一個隨機數,然后檢查它是否已被使用。 如果已經使用了從數字中添加或減去一個並再次檢查,請繼續重復,直到我點擊未使用的數字。 問題是這不再是一個隨機數,因為我引入了偏差(最終我會得到一堆數字,你可以預測下一個數字有更大的成功機會)。

3)生成一個隨機數,然后檢查它是否已被使用。 如果它已被使用添加或減去另一個隨機生成的隨機數並再次檢查,問題是我們回到簡單生成隨機數並檢查解決方案1。

4)將其取出並生成隨機列表並保存,讓守護程序將它們放入隊列中,以便有可用的數字(並避免不斷打開和關閉文件,而是將其批處理)。

5)生成更大的隨機數並散列它們(即使用MD5)來獲得更小的數值,我們應該很少得到沖突,但最終我的數字大於所需的數字。

6)在隨機數(即unix時間戳)之前添加或附加基於時間的信息以減少碰撞的可能性,同樣我得到的數字比我需要的數字更多。

任何人都有任何聰明的想法,可以減少“碰撞”的機會(即產生一個已經采取的隨機數),但也可以讓我保持這個數字“小”(即少於十億(或十億)你的歐洲人=))。

答案以及為什么我接受它:

因此,我將簡單地選擇1,並希望它不是一個問題,但是如果是的話,我會選擇生成所有數字並存儲它們的確定性解決方案,以便有一個獲得新的隨機數的保證,我可以使用“小”數字(即9位數而不是MD5 /等)。

這是一個很好的問題,我已經考慮了一段時間(使用與Sjoerd類似的解決方案),但最后,我認為這是:

使用你的觀點1)並停止擔心。

假設真正的隨機性,之前已經選擇隨機數的概率是先前選擇的數的計數除以池的大小,即最大數。

如果你說你只需要十億個數字,即9個數字:給自己多3個數字,所以你有12位數的序列號(這是三組四個數字 - 漂亮可讀)。

即使你以前接近選擇了十億個數字,你的新數字已經被采用的概率仍然只有0.1%。

執行第1步並再次繪制。 您仍然可以檢查“無限”循環,比如不要嘗試超過1000次左右,然后回退到添加1(或其他)。

在使用后備版之前,你將贏得彩票。

您可以使用格式保留加密來加密計數器。 你的計數器從0開始向上,加密使用你選擇的一個鍵將它變成一個看似隨機的值,你想要的基數和寬度。

分組密碼通常具有固定的塊大小,例如64或128位。 但格式保留加密允許您采用像AES這樣的標准密碼,並制作一個小寬度的密碼,無論你想要什么基數和寬度(例如基數10,問題參數的寬度9),算法仍然是密碼學上健壯。

保證永遠不會發生沖突(因為加密算法會創建1:1映射)。 它也是可逆的(雙向映射),因此您可以獲取結果數字並返回到您開始時的計數器值。

AES-FFX是一種提出的標准方法。

我已經嘗試了一些AES-FFX的基本Python代碼 - 請參閱此處的Python代碼 (但請注意,它並不完全符合AES-FFX規范)。 它可以例如將計數器加密為隨機查看的7位十進制數。 例如:

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的數字。

使用一些模塊化的算術和素數,您可以創建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

如果它們不必是隨機的,但不是明顯線性的(1,2,3,4 ......),那么這是一個簡單的算法:

選擇兩個素數。 其中一個將是您可以生成的最大數字,因此它應該是大約十億。 另一個應該相當大。

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位數的素數來證明它,它只需要更多的工作(或更多的內存)。

這意味着,只要給出一些序列號,就有可能弄清楚你的價值是多少 - 但只有九位數,不管怎么說,你都不太可能會找到不合適的數字。

如果你不需要加密安全的東西,但只是“充分混淆”......

伽羅瓦菲爾茲

您可以嘗試在Galois Fields中操作,例如GF(2) 32 ,將簡單的遞增計數器x映射到看似隨機的序列號y

x = counter_value
y = some_galois_function(x)
  • 乘以常數
    • 逆是乘以常數的倒數
  • 提升到一個力量x n
  • 倒數x -1
    • 提高到n次冪的特殊情況
    • 這是它自己的逆
  • 原始元素的指數x
    • 請注意,這沒有一個容易計算的逆(離散對數)
    • 確保a是一個原始元素 ,也就是生成器

其中許多操作都有反轉,這意味着,根據序列號,您可以計算從中派生的原始計數器值。

至於為Galois Field尋找Python的庫...好問題。 如果你不需要速度(你不需要速度),那么你可以自己動手。 我沒試過這些:

GF中的矩陣乘法(2)

在GF(2)中選擇合適的32×32可逆矩陣,並將32位輸入計數器乘以它。 這在概念上與LFSR有關,如S.Lott的回答中所述

CRC

相關的可能性是使用CRC計算。 基於GF(2)中的不可約多項式的長除的余數。 Python代碼隨時可用於CRC( crcmodpycrc ),盡管您可能希望選擇與通常使用的不同的不可約多項式,以用於您的目的。 我對理論有點模糊,但我認為32位CRC應該為每個可能的4字節輸入組合生成一個唯一值。 檢查一下。 通過將輸出反饋到輸入中並檢查它是否產生一個長度為2 32 -1的完整循環(零只是映射到零),可以很容易地通過實驗檢查這一點。 您可能需要擺脫CRC算法中的任何初始/最終XOR,以使此檢查起作用。

我認為你高估了方法1)的問題。 除非您有硬實時要求,否則只需通過隨機選擇即可快速終止。 需要多次迭代的概率呈指數衰減。 輸出100M數字(10%fillfactor),您將有十億次機會需要超過9次迭代。 即使有50%的數字被采用,你平均需要2次迭代,並且有十分之一的機會需要超過30次檢查。 或者甚至是99%的數字已經被采用的極端情況可能仍然是合理的 - 你將平均100次迭代並且需要2062次迭代的十億次更改

標准線性同余隨機數發生器的種子序列不能重復,直到生成了起始種子值的全部數字。 然后它必須精確重復。

內部種子通常很大(48或64位)。 生成的數字較小(通常為32位),因為整個位組不是隨機的。 如果您遵循種子值,它們將形成獨特的非重復序列。

問題基本上是找到一個產生“足夠”數字的好種子。 您可以選擇一個種子,並生成數字,直到您回到起始種子。 這是序列的長度。 它可能是數百萬或數十億的數字。

Knuth中有一些指導方針可以選擇合適的種子,這些種子會產生很長的獨特數字序列。

我的解決方案https://github.com/glushchenko/python-unique-id ,我認為你應該擴展矩陣1000,000,000變化,並享受樂趣。

如果你每次只減少一個隨機間隔,你可以運行1)而不會遇到太多錯誤隨機數的問題。

要使此方法起作用,您需要保存已經給出的數字(無論如何要保存),並保存所采用的數字。

很明顯,在收集了10個數字后,您的可能隨機數池將減少10個。因此,您不能選擇介於1和1.000.000之間但介於1和999.990之間的數字。 當然這個數字不是實數而只是一個指數(除非收集的10個數字是999.991,999.992,......); 你現在必須從1中省略所有已收集的數字。

當然,你的算法應該比從1到1.000.000計算更聰明,但我希望你理解這個方法。

我不喜歡繪制隨機數字,直到我得到一個適合的任何一個。 這只是感覺不對。

有點遲到的答案,但我沒有看到任何地方的建議。

為什么不使用uuid模塊創建全局唯一標識符

我重新考慮問題本身......你似乎沒有按照數字順序做任何事情......而且你的列上有一個索引。 他們真的需要成為數字嗎?

考慮一下sha hash ...你實際上並不需要整個事情。 做git或其他url縮短服務做什么,並采取散列的前3/4/5字符。 鑒於每個角色現在有36個可能的值而不是10個,你有2,176,782,336個組合而不是999,999個組合(六個數字)。 將其與快速檢查組合是否存在(純索引查詢)和種子如時間戳+隨機數相結合,幾乎可以用於任何情況。

你需要這個加密安全還是難以猜測? 碰撞有多糟糕? 因為如果它需要加密強大並且沒有碰撞,那么遺憾的是,這是不可能的。

我開始嘗試編寫下面使用的方法的解釋,但只是實現它更容易,更准確。 這種方法有一種奇怪的行為,即你生成的數字越多,它就越快。 但它有效,並且它不需要您提前生成所有數字。

作為一個簡單的優化,您可以輕松地使這個類使用概率算法(生成一個隨機數,如果它不在使用的數字集中,將它添加到集合並返回它),首先跟蹤碰撞率,一旦碰撞率變差,切換到此處使用的確定性方法。

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]

要在定義的閾值內生成完全隨機數的列表,如下所示:

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,maximal)的索引(即非重復數字)的隨機樣本生成器,稱為itersample 以下是一些用法示例:

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))的regardelss 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

如果對於你來說,一個隨意的觀察者無法猜測下一個值就足夠了,你可以使用像線性同余生成器甚至簡單的線性反饋移位寄存器來生成值並在數據庫中保持狀態以備不時之需更多價值觀。 如果使用這些權限,則值將不會重復,直到Universe結束。 您將在隨機數生成器列表中找到更多想法。

如果您認為可能有人對猜測下一個值感興趣,您可以使用數據庫序列來計算您生成的值,並使用加密算法或其他加密強完美函數對其進行加密。 但是,如果你能掌握一系列你生成的連續數字,你需要注意加密算法是不容易破解的 - 例如,由於Franklin-Reiter相關消息攻擊 ,一個簡單的RSA不會這樣做。

您聲明將數字存儲在數據庫中。

那么在那里存儲所有數字會不會更容易,並向數據庫詢問隨機未使用的數字? 大多數數據庫都支持此類請求。

例子

MySQL的:

SELECT column FROM table
ORDER BY RAND()
LIMIT 1

PostgreSQL的:

SELECT column FROM table
ORDER BY RANDOM()
LIMIT 1

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM