簡體   English   中英

生成一百萬個唯一的隨機 12 位數字

[英]Generate a million unique random 12 digit numbers

我需要為刮刮卡應用程序生成近一百萬(100 批 10000 個數字)唯一和隨機的 12 位代碼。 此過程將重復進行,並且每次都需要生成相同數量的代碼。

此外,生成的代碼需要輸入到數據庫中,以便稍后當消費者在我的網站上輸入時進行驗證。 我正在使用 PHP 和 Mysql 來做到這一點。 這些是我正在遵循的步驟

  1. 獲取有關批次數量和每批次代碼的管理員輸入

  2. 使用 for 循環使用mt_rand(100000000000,999999999999)生成代碼

  3. 每次生成數字時檢查以查看數據庫中是否存在重復項,如果不添加到結果變量中,則重新生成。

  4. 如果唯一,則將生成的數字保存在數據庫中

  5. 在所需數量的代碼上重復 b、c 和 d

  6. 將代碼輸出到 csv 中的管理員

使用的代碼(刪除了大部分注釋以使其不那么冗長,因為我之前已經解釋了這些步驟):

$totalLabels = $numBatch*$numLabelsPerBatch;
// file name for download
$fileName = $customerName."_scratchcodes_" . date('Ymdhs') . ".csv";
$flag = false;
$generatedCodeInfo = array();
// headers for download
header("Content-Disposition: attachment; filename=\"$fileName\"");
header("Content-Type: application/vnd.ms-excel");
$codeObject = new Codes();
//get new batch number 
$batchNumber = $codeObject->getLastBatchNumber() + 1;
$random = array();
for ($i = 0; $i < $totalLabels; $i++) {
    do{
        $random[$i] = mt_rand(100000000000,999999999999); //need to optimize this to reduce collisions given the databse will be grow
    }while(isCodeNotUnique($random[$i],$db));
    $codeObject = new Codes();
    $codeObject->UID = $random[$i];
    $codeObject->customerName = $customerName;
    $codeObject->batchNumber = $batchNumber;
    $generatedCodeInfo[$i] = $codeObject->addCode();

    //change batch number for next batch
    if($i == ($numLabelsPerBatch-1)){$batchNumber++;}


    //$generatedCodeInfo[i] = array("UID" => 10001,"OID"=>$random[$i]);
    if(!$flag) {
        // display column names as first row
        echo implode("\t", array_keys($generatedCodeInfo[$i])) . "\n";
        $flag = true;
    }
    // filter data
    array_walk($generatedCodeInfo[$i], 'filterData');
    echo implode("\t", array_values($generatedCodeInfo[$i])) . "\n";


}


function filterData(&$str)
{
    $str = preg_replace("/\t/", "\\t", $str);
    $str = preg_replace("/\r?\n/", "\\n", $str);
    if(strstr($str, '"')) $str = '"' . str_replace('"', '""', $str) . '"';
}

function isCodeNotUnique($random){
    $codeObject = new Codes();
    $codeObject->UID = $random;
    if(!empty($codeObject->getCodeByUID())){
        return true;
    }
    return false;
}

現在這需要很長時間才能執行,我認為這不是最佳選擇。

  1. 如何優化以快速生成唯一的隨機數?

  2. 如果數字是在 mysql 或其他方式而不是 php 中生成的,它會更快,如果是這樣,我該怎么做?

  3. 當數據庫開始增長時,步驟 b 中的重復檢查將非常耗時,那么我該如何避免呢?

  4. mysql 的行數有限制嗎?

注意:在應用程序的整個生命周期內,所有批次的編號都必須是唯一的。

1)根據批次數將您的數字范圍划分為更小的范圍。 例如,如果您的范圍是 0 - 1000 並且您有 10 個批次,那么有 0 - 99 的批次、接下來的 100 - 199 等。當您為批次生成數字時,僅從批次范圍生成隨機數。 這樣您就知道一個批次中只能有重復的數字。

不要單獨插入每個號碼存入數據庫,但它們存儲在陣列中。 當您生成一個新的隨機數時,請使用in_array()函數檢查數組,而不是數據庫。 當批處理完成時,然后使用單個insert語句插入批處理的內容:

insert into yourtable (bignumber) values (1), (2), ..., (n)

查看MySQL的max_allowed_packet設置,看是否能夠一次性接收到完整的sql語句。

實施回退計划,以防在插入期間仍然發現重復值(錯誤處理和數字重新生成)。

2) MySQL 在程序方面不是很好,所以我會堅持使用外部語言,比如 php。

3) 在包含隨機數的字段上添加唯一索引。 如果您嘗試插入重復記錄,MySQL 將阻止它並拋出錯誤。 這真的很快。

4) 根據實際使用的表引擎(innodb、myisam 等)、其配置和操作系統,可能會對表的大小施加某些限制。 有關更詳細的答案,請參閱此處的 MySQL 數據庫表問題中的最大記錄數以獲取更詳細的答案(檢查最受好評的答案,而不是接受的答案)。

您可以執行以下操作:

$random = getExistingCodes(); // Get what you already have (from the DB).  
$random = array_flip($random); //Make them into keys
$existingCount = count($random); //The codes you already have 

do {
    $random[mt_rand(100000000000,999999999999)] = 1;
} while ((count($random)-$existingCount) < $totalLabels);

$random = array_keys($random);

當您生成重復的數字時,它只會覆蓋該鍵,而不會增加計數。

要插入,您可以啟動一個事務並根據需要執行任意數量的插入。 MySQL 將嘗試優化單個事務中的所有操作。

這是一個生成 100 萬個無重復偽隨機數的查詢:

select cast(  (@n := (13*@n + 97) % 899999999981)+1e11 as char(12)) as num
from   (select @n := floor(rand() * 9e11) ) init,
       (select 1 union select 2) m01,
       (select 1 union select 2) m02,
       (select 1 union select 2) m03,
       (select 1 union select 2) m04,
       (select 1 union select 2) m05,
       (select 1 union select 2) m06,
       (select 1 union select 2) m07,
       (select 1 union select 2) m08,
       (select 1 union select 2) m09,
       (select 1 union select 2) m10,
       (select 1 union select 2) m11,
       (select 1 union select 2) m12,
       (select 1 union select 2) m13,
       (select 1 union select 2) m14,
       (select 1 union select 2) m15,
       (select 1 union select 2) m16,
       (select 1 union select 2) m17,
       (select 1 union select 2) m18,
       (select 1 union select 2) m19,
       (select 1 union select 2) m20
limit 1000000;

工作原理

它首先生成一個隨機整數值n ,其中0 <= n < 900000000000 這個數字將具有生成序列的種子的功能:

@n := floor(rand() * 9e11)

通過與內聯記錄對的多個 (20) 連接,該單個記錄乘以 2 20 個副本,僅略高於 100 萬。

然后開始選擇,隨着記錄的取回,@n變量的值按照這個增量公式修改:

@n := (13*@n + 97) % 899999999981

這個公式是一個線性同余生成器 這三個常數需要遵守一些規則來最大化(不重復的)周期,但是當 899999999981 是素數時最容易,它是。 在這種情況下,我們有一個周期為 899999999981,這意味着第一個 899999999981 生成的數字將是唯一的(我們需要的更少)。 這個數實際上是900000000000以下的最大素數。

作為最后一步,將100000000000添加到號碼中以確保號碼始終為12位,因此排除小於100000000000的號碼。由於選擇了899999999981,因此將永遠不會生成20個號碼,即999998199之間的號碼999999999999(含)。

由於這會生成 2 20條記錄,因此limit子句將確保將其截斷為一百萬條記錄。

castchar(12)是可選的,但可能需要可視化 12 位數字,而無需將它們以科學計數法呈現在屏幕上。 如果您將使用它來插入記錄,並且目標數據類型是數字,那么您當然會忽略此轉換。

CREATE TABLE x (v BIGINT(12) ZEROFILL NOT NULL PRIMARY KEY);

INSERT IGNORE INTO x (v) VALUES
    (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()),
    (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()),
    (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()),
    (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()),
    (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND());

這樣做 INSERT 1e6/15 次。

檢查COUNT(*)以查看您是否有一百萬。 這樣做直到表為一百萬行:

INSERT IGNORE INTO x (v) VALUES
    (FLOOR(1e12*RAND());

注意事項:

  • ZEROFILL假設您希望顯示具有前導零。
  • IGNORE是因為會有一些重復。 這避免了每次插入后的昂貴檢查。
  • “批量插入”比一次一行快。 (一次做 100 次是最佳選擇,但我很懶。)
  • 潛在問題:雖然我認為RAND()的值模式不會重復,比如 2^16 或 2^32 值,但我不知道事實。 如果你不能達到一百萬,那么隨機數生成器就壞了; 您應該切換到 PHP 的 rand 或其他東西。
  • 當心線性結果隨機數生成器。 他們可能很容易被黑客入侵。 (我假設刮刮卡后面有一些“錢”。)

不要計划mt_rand()在小范圍內是唯一的

<?php
// Does mt_rand() repeat?

TryMT(100);
TryMT(100);
TryMT(1000);
TryMT(10000);
TryMT(1e6);
TryMT(1e8);
TryMT(1e10);
TryMT(1e12);
TryMT(1e14);

function TryMT($max) {
    $h = [];
    for ($j = 0; $j<$max; $j++) {
        $v = mt_rand(1, $max);
        if (isset($h[$v])) {
            echo "Dup after $j iterations (limit=$max)<br>\n";

            return;
        }
        $h[$v] = 1;
    }
}

示例輸出:

Dup after 7 iterations (limit=100)<br>
Dup after 13 iterations (limit=100)<br>
Dup after 29 iterations (limit=1000)<br>
Dup after 253 iterations (limit=10000)<br>
Dup after 245 iterations (limit=1000000)<br>
Dup after 3407 iterations (limit=100000000)<br>
Dup after 29667 iterations (limit=10000000000)<br>
Dup after 82046 iterations (limit=1000000000000)<br>
Dup after 42603 iterations (limit=1.0E+14)<br>

mt_rand()是一個“好”的隨機數,因為它確實有重復。

暫無
暫無

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

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