【问题标题】:"Unique 7 character string" - related issue“唯一 7 字符串” - 相关问题
【发布时间】:2012-12-12 03:20:28
【问题描述】:

我阅读了几个与我所要求的主题相似的主题,但似乎没有一个对我很有帮助。

我有一个表单,用户可以在其中生成存储在具有唯一约束的列中的代码。代码是长度为 7 个字符的字符串。用户可以输入一个数字,程序会生成那么多代码,并且可以重复此操作,直到达到最大代码数量。

我的问题是重复值。但不是在输入新条目时数据库中已经存在的值(我成功检查了这些值),但新组(比如 10000)代码中的一些条目(可能)是相同的。所以我的代码在同一个事务中生成两个(或更多)相同的代码,并且数据库中的唯一约束抱怨它。

我想在每次输入后检查数据库,但考虑到我们谈论的是 10000 或有时更多的条目,这非常耗时。

所以现在我认为唯一的选择是首先修改生成它们的代码,因为它似乎效率低下并生成双打。

问题的很大一部分是所需的代码长度,否则我会使用纯 'uniqid()' 或类似的东西,但由于我必须将其限制为 7 个字符,我想这会使情况变得更糟.另外,我必须从代​​码中的代码中排除一些字符[标记为'problem_characters']。

这是代码,我无法正确修改它以仅生成唯一值。

$problem_characters = array("0", "o", "O", "I", "1", 1);

$code = md5(uniqid(rand(), true));

$extId = strtoupper(str_replace($problem_characters,rand(2,9),substr($code, 0, 7)));

//insert $extId in the database

@Geo 好的,我尝试了您的解决方案并且它正在工作(当然),但后来我遇到了一个新问题 - 在您的“如果”的“其他”部分,我正在执行以下操作:

$extId = strtoupper(str_replace($problem_characters,rand(2,9),substr($code, 0, 7)));

while(true){     

      if((!in_array($extId, $allExternalIdsHandled)) && (!in_array($extId, $newEnteredValues))){
       break;
        }else{
 $extId = strtoupper(str_replace($problem_characters,rand(2,9),substr($code, 0, 7)));   }
               }
//insert the modified value in the DB here

所以,现在它进入了一个无限循环,并且它没有用“break”命令中断,即使它应该随着“random”调用的执行而改变,然后输入 if 并中断......

我没有看到这里的问题。谁能给我一些指导,好吗?

编辑:它有时会挂起,有时不会。我刚刚输入了 10000 个值并通过“else”路径修改了两个条目。我使用日志观察到这一点。

【问题讨论】:

    标签: php string unique


    【解决方案1】:

    已经有libraries为你辛苦工作,让你选择生成字符串时使用的“字母”和字符串的长度。

    您的“相同条目”问题称为collision,这是无法避免的。

    编辑因此,类似于 Geo 的建议,我使用 PHP 创建一个 n 唯一条目列表。不同之处在于 SQL 插入可能会失败,所以我有 2 层迭代以确保我们填充所需的总数:

    <?php
    
    require('hashids.php'); // I'm using the library I suggested
    
    $hashids = new hashids('some salt', 7); // use the default alphabet, feel free to pass the 3rd parameter with the alphabet you want to use
    
    $generationTries = 0;
    
    $hashesInDBCount = 0; // get from your database
    $desiredHashesCount = 50; // use a parameter
    $totalDesiredHashes = $hashesInDBCount + $desiredHashesCount;
    do
    {
        // when coming back in the loop, only generate what's still required
        $desiredHashesCount = $totalDesiredHashes - $hashesInDBCount; 
        $generatedHashesCount = 0;
        $generatedHashes = array();
    
        while($generatedHashesCount < $desiredHashesCount)
        {
            $hash = $hashids->encrypt($generationTries++);
            if(!in_array($hash, $generatedHashes))
            {
                array_push($generatedHashes, $hash);
                ++$generatedHashesCount;
            }
        }
    
        // insert $generatedHashes in your Database
    
        $hashesInDBCount = 50; // again, query your database as you might come through this loop more than once, 
                               // I'm hardcoding the value to have a working example
    }
    while($hashesInDBCount < $totalDesiredHashes);
    
    echo "Generated " . count($generatedHashes) . " hashes in " . $generationTries . " tries\n";
    var_dump($generatedHashes);
    

    这给了我一个类似的输出:

    Generated 50 hashes in 50 tries
    array(50) {
      [0]=>
      string(7) "eAcgAcx"
      [1]=>
      string(7) "Exidai8"
      [2]=>
      string(7) "ExTbqT8"
      [3]=>
      string(7) "4Acz8cB"
      [4]=>
      string(7) "LRipxir"
      [5]=>
      string(7) "zATe5Tx"
      ...
    }
    

    添加随机盐每次都会给你随机值

    【讨论】:

    • 不,值不重要,但格式很重要(7 个字符,有问题的字符省略)。我提供的代码在 for() 循环中运行。非常抱歉,但我没有完全理解您的解决方案。你认为我应该在 for() 循环中创建一个 while() 循环吗?你认为我应该将目标计数设置为在 for() 之外还是在里面?我真的没有得到解决方案,对不起。您能进一步解释一下吗?
    • 好的,让我重新表述一下我的答案,希望它符合您的要求
    • 我尝试用我的手机编辑,但代码有格式问题,我回家后会更新,抱歉耽搁了
    • 我唯一没有做的就是添加您的自定义字母,但它应该很容易更改!如果您需要我这样做,请告诉我
    【解决方案2】:

    首先 - 您使用 md5 来生成字符串代码,因为 md5 是十六进制编码的字符串,您正在严重减少可能组合的数量,生成具有 30 个可能字符的随机字符串给您 210 亿 (10^9)可能性而不是使用十六进制字符的 2.68 亿(10^6)

    另外一件事 - 你永远无法创建真正唯一的值(guid-s 是机器唯一的),生成相同值两次的概率会随着字符串越短而增加。

    我可以使用三种不同的方法(我假设您至少有 30 个没有问题的字符) 您可以创建唯一的非随机值。假设你有两个计数器生成请求计数和请求计数器。因此,如果用户 1 要求提供 100 个代码,则诸如 user_request_counter-code_counter : '00-00-00_00-00-00-01' to '00-00-01_00-00-03-00' 之类的代码肯定是唯一的(并且是实际上 7 - 每组最多 30 位的两位数字可以用单个字符表示(就像十六进制用 16 个字符表示一样 - 你可以选择任何你喜欢的基数)这将允许你创建 30^4 (810,000) 个代码到 30^ 3 (27,000) 个用户。这样你就不需要使用昂贵的随机调用,也不需要担心重复代码。你甚至可以通过随机化优惠券计数器并随机分配每个代码生成请求来使其有点随机而不是不是计数器,但每个用户的代码仍然具有相同的前缀

    我曾经使用的第二种方法是简单地用随机代码填充数据库,然后将它们分配给用户,这很有用,因为您只需要偶尔执行一次,并且可以生成新的代码离线完成(使用 db 转储)然后推送到服务器,这样您就可以在 php 代码的代码生成上获得出色的性能 o(1),在 db 服务器端获得 o(1),因为不需要像将 php 生成的代码插入数据库时​​一样,更新表索引数千次。

    如果您唯一的问题是您在 php 生成的值中得到重复项,则第三种方法是将它们放入数组中,然后检查这些值是否是新的。由于 php 数组是作为哈希表实现的,因此您将获得相当不错的性能)。

    如果您选择在 PHP 中随机生成代码 - 您将始终面临两个问题 - 第一个 - 无法保证代码不会存在于数据库中,因此您将始终需要处理双键问题第二个是因为您需要生成许多代码 - 将它们插入到 db 在 sql server 端将非常昂贵,并且由于您有更多代码 - 会显着减慢脚本

    【讨论】:

    • 好的,关于你答案的 md5() 部分 - 你认为我应该像这样删除它 - $code = uniqid(rand(), true); 还是你认为我应该这样做 - $code = str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
    • 在这里查看第二个答案stackoverflow.com/questions/853813/…function randString($length, $charset='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') { $str = ''; $count = strlen($charset); while ($length--) { $str .= $charset[mt_rand(0, $count-1)]; } return $str; }
    【解决方案3】:
    <?php
    
    $problem_characters = array('0', 'o', 'O', 'I', '1', 1);
    $length = 10000;
    $i = 0;
    $hashes = array();
    while ($i < $length) {
        $code = md5(uniqid(rand(), TRUE));
        $extId = strtoupper(str_replace($problem_characters, rand(2, 9), substr($code, 0, 7)));
        if ( ! in_array($extId, $hashes)) {
            $hashes[] = $extId;
            $i++;
            // insert $extId in the database
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-12
      • 2022-12-20
      • 2020-07-03
      • 2020-07-25
      • 1970-01-01
      • 2023-03-15
      相关资源
      最近更新 更多