繁体   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