簡體   English   中英

生成隨機數的算法

[英]Algorithm for generating a random number

我希望生成一個隨機數並將其發送到數據庫中特定 user_id 的表中。 問題是,同一個數字不能使用兩次。 有一百萬種方法可以做到這一點,但我希望非常熱衷於算法的人有一個巧妙的方法來解決這個問題的優雅解決方案,因為滿足以下標准:

1) 對數據庫的查詢次數最少。 2) 對內存中的數據結構進行最少的爬行。

本質上,這個想法是做以下事情

1) 創建一個從 0 到 9999999 的隨機數
2)檢查數據庫看號碼是否存在
或者
2) 查詢數據庫中的所有數字
3)查看返回的結果是否匹配來自數據庫的任何內容
4)如果匹配,重復步驟1,如果不匹配,問題解決。

謝謝。

沒有你的算法不可擴展。 我之前做的是串行發送數字(每次+1),然后通過XOR操作傳遞它們來混雜位,從而給我一個看似隨機的數字。 當然它們並不是隨機的,但它們看起來對用戶來說是如此。


[編輯]補充資料

這個算法的邏輯是這樣的,你使用一個已知的序列來生成唯一的數字,然后你確定地操縱它們,所以它們不再看起來是連續的。 一般的解決方案是使用某種形式的加密,在我的例子中是一個XOR觸發器,因為它的速度盡可能快,並且它可以保證數字永遠不會碰撞。

但是,您可以使用其他形式的加密,如果您想要更加隨機的數字,超速(假設您不需要一次生成多個ID)。 現在,選擇加密算法的重點是“數字絕不會碰撞的保證”。 並且證明加密算法是否能夠實現該保證的方法是檢查加密的原始數量和結果是否具有相同的比特數,並且該算法是可逆的(雙射)。

[感謝Adam LissCesarB對解決方案的研究]

你為什么不使用GUID? 大多數語言應該有內置的方法來執行此操作。 它保證是唯一的(具有非常合理的界限)。

想要一個過頂的解決方案?

我認為隨機性不是加密質量,而是足以阻止用戶通過user_id猜測用戶的壽命。

在開發期間,以字符串形式生成所有1000萬個數字的列表。

(可選)執行一些簡單的轉換,例如向中間添加常量字符串。 (這是為了防止結果過於可預測。)

將它們傳遞給生成Perfect Hash函數的工具,例如gperf

生成的代碼可用於在運行時將用戶的id快速編碼為唯一的哈希值,保證不與任何其他哈希值沖突。

嘗試使用mysql中的語句SELECT CAST(RAND()* 1000000 AS INT)

假設:

  • 唯一性需要隨機性,而不是安全性
  • 您的user_id是32位
  • 您的限制9999999只是一個例子

您可以做一些簡單的操作,將隨機數作為64位整數,其中高32位包含時間戳(在行插入時),低32位包含user_id。 即使對於具有相同用戶的多行,這也是唯一的,前提是您在時間戳上使用適當的分辨率,具體取決於您為同一用戶添加新行的頻率。 結合隨機列上的唯一約束並捕獲邏輯中的任何此類錯誤,然后重試。

我想你會發現你真的不想這樣做。 隨着數據庫中的數字增加,您可能會在“確保未采用此數字”循環中花費太多時間。

就個人而言,我已經幸運地選擇了哈希作為替代方案,但是為了找到更好的解決方案,我真的需要知道你為什么要這樣做。

我的經驗只是在PHP中使用RNG。 我發現使用一定數量的數字(我使用的是int,所以我的最大值為4G)。 我進行了一些測試,發現平均而言,在500,000次迭代中,我得到了120個單個重復項。 在運行循環多次之后,我從來沒有一式三份。 我的“解決方案”是然后插入並檢查它是否失敗,然后生成一個新的ID然后再去。

我的建議是做同樣的事情,看看你的碰撞率是多少,看看它是否適合你的情況。

這不是最佳的,所以如果有人有建議我也在尋找:)

編輯:我被限制為5位數ID([a-zA-z0-9] {5,5}),id越長(組合越多,碰撞次數越少)。 例如,電子郵件的md5幾乎不會發生沖突。

問題是,如果您生成隨機數,則很有可能無限制地生成重復數據。

然而:

<?php
//Lets assume we already have a connection to the db
$sql = "SELECT randField FROM tableName";
$result = mysql_query($sql);
$array = array();
while($row = mysql_fetch_assoc($result))
 {
   $array[] = $row['randField'];
 }
while(True)
 {
   $rand = rand(0, 999999);
   if(!in_array($rand))
     {
       //This number is not in the db so use it!
       break;
     }
 }
?>

雖然這也會做你想要的,但這是一個糟糕的想法,因為這不會長時間擴展,最終你的陣列會變得很大,生成一個不在你的數據庫中的隨機數將需要很長時間。

設計一個長期不重復的偽隨機數發生器很容易; 例如, 這個用於你想要它的同一件事。

順便說一句,為什么不按順序發出用戶ID呢?

我喜歡Oddthinking的想法,但不是選擇世界上最強大的哈希函數,你可以簡單地:

  • 生成前1000萬個數字的MD5(表示為字符串,+一些鹽)
  • 離線檢查重復,即在投入生產之前(我猜不會有任何重復)
  • 將重復項存儲在某個位置的數組中
  • 應用程序啟動時,加載陣列
  • 如果要插入ID,請選擇下一個數字,計算其MD5,檢查它是否在數組中,以及是否不將其用作數據庫中的ID。 否則,請選擇下一個號碼

MD5很快,並且檢查字符串是否屬於數組將避免使用SELECT。

我以前寫過一篇關於此的文章 它采用與Robert Gould的答案相同的方法,但另外還展示了如何使用xor折疊將分組密碼縮短到合適的長度,然后如何在不是2的冪的范圍內生成排列,同時仍然保留獨特性。

如果你真的想從0到9 999 999獲得“隨機”數字,那么解決方案是進行一次“隨機化”,然后將結果存儲到磁盤中。

獲得你想要的結果並不難,但我認為它更像是“用數字制作一個長列表”,而不是“得到一個隨機數”。

$array = range(0, 9999999);
$numbers = shuffle($array);

您還需要一個指向$ numbers中當前位置的指針(將其存儲在數據庫中); 從0開始並在每次需要新號碼時遞增。 (或者你可以使用array_shift()或array_pop(),如果你不喜歡使用指針。)

適當的PRNG(偽隨機數發生器)算法將具有循環時間,在此期間它將永遠不會處於相同的狀態。 如果您將PRNG的整個狀態暴露在從其中檢索到的數字中,您將獲得一個在發生器周期內保證唯一的數字。

執行此操作的簡單PRNG稱為“ 線性同余 ”PRNG,它迭代公式:

X(i) = AX(i-1)|M

使用正確的一對因子,您可以從具有32位累加器的簡單PRNG獲得2 ^ 30(大約10億)的周期。 請注意,您需要一個64位長的臨時變量來保存計算的中間“AX”部分。 大多數(如果不是全部)C編譯器都支持這種數據類型。 您還應該能夠在大多數SQL方言上使用數字數據類型。

使用正確的A和M值,我們可以得到具有良好統計和幾何屬性的隨機數生成器。 Fishman和Moore寫了一篇關於此的着名論文。

對於M = 2 ^ 31 - 1,我們可以使用下面的A值來獲得具有良好長周期的PRNG(2 ^ 30 IIRC)。

A的良好價值:

742,938,285  
950,706,376  
1,226,874,159  
62,089,911  
1,343,714,438   

請注意,這種類型的生成器(根據定義)不是加密安全的。 如果您知道從中生成的最后一個數字,您可以預測它接下來會做什么。 不幸的是,我認為您無法同時獲得加密安全性和保證不可重復性。 對於PRNG是加密安全的(例如Blum Blum Shub ),它不能在生成的數字中暴露足夠的狀態以允許預測序列中的下一個數字。 因此,內部狀態比生成的數字寬,並且(為了具有良好的安全性),周期將比可以生成的可能值的數量長。 這意味着曝光的數字在此期間不會是唯一的。

出於類似的原因,長期發電機也是如此,例如Mersenne Twister。

有幾種方法可以用這種方法構建一個數字為0000000到9999999的數組,然后在這個數組中隨機挑選這些數字,並將選取的數字值與最高值Max交換,然后通過減少最大值1並選擇此陣列的另一個隨機成員,直到新的最大值

每次將Max減少一個

例如(基本):(右邊是應該在實際程序中刪除的注釋)Rndfunc是對你正在使用的任何隨機數生成器函數的調用

dim array(0 to 9999999) as integer
for x% = 1 to 9999999
array(x%)=x%
next x%
maxPlus = 10000000
max =9999999
pickedrandom =int(Rndfunc*maxPlus)  picks a random indext of the array based on    
                                   how many numbers are left
maxplus = maxplus-1
swap array(pickedrandom) , array(max) swap this array value to the current end of the
                                     array 
max = max -1                   decrement the pointer of the max array value so it 
                              points to the next lowest place..

然后對你想要選擇的每個數字繼續這樣做,但是你需要選擇使用非常大的數組

另一種方法如下:生成一個數字並將其存儲到一個可以動態增長的數組中然后選擇一個新數字並將其與數組中從第一個元素到最后一個元素的中間值進行比較它將是第一個被選中的數字,如果它匹配選擇另一個隨機數,根據大小排序數組,如果沒有匹配,那么根據天氣它大於或小於你與你比較它的數字上升或下降列表的一半距離,每次它不匹配,並且大於或小於你比較的距離。

每次將它減半,直到達到間隙大小為1,然后檢查一次並停止,因為沒有匹配,然后將數字添加到列表中,列表按升序重新洗牌,依此類推,直到你是完成挑選隨機數...希望這有助於..

PHP已經有了這個功能, uniqid 它生成一個標准的uuid,如果你必須從其他地方訪問數據,這是很好的。 不要重新發明輪子。

我可能沒有抓住你的觀點,但是auto_increments呢?

如果你想確保隨機號碼不重復,你需要一個不重復的隨機數發生器(如描述在這里 )。

基本思想是以下公式seed * seed & p將為任何輸入x such that 2x < p產生非重復隨機數, x such that 2x < pp - x * x % p產生所有其他隨機數以及非重復,但是只有當p = 3 mod 4 所以你基本上只需要一個單一的原始數字,盡可能接近9999999 通過這種方式,可以將工作量減少到單個讀取字段,但缺點是生成過大的ID或生成過少的ID。

這個算法不能很好地進行排列,所以我建議將它與XOR或加法或其他方法結合使用,以改變確切的值,而不會破壞種子與其生成值之間的1對1關系。

對於隨機生成:(在 PHP 中)

代碼:

它完美地創建了隨機數。

 <?PHP /*set the bigger range if there is huge demand*/ $n=range(111111,999999); shuffle($n); //shuffle those for ($x=0; $x< 1; $x++) //selects unique random number. { echo $n[$x].' '; } echo "\\n" ?>

結果:

第一次:

一

第二次:

二

第三次:

三

暫無
暫無

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

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