生成唯一的6位数字代码。

8
我正在使用以下字符生成一个6位数的代码。这些代码将被用于印在贴纸上。
它们将以10k或更少的批次生成(在打印之前),我不预计总共会超过1-2百万个(可能会少得多)。
在生成代码的批次后,我将检查已有代码的MySQL数据库,以确保不存在重复。
// exclude problem chars: B8G6I1l0OQDS5Z2

$characters = 'ACEFHJKMNPRTUVWXY4937';

$string = '';

for ($i = 0; $i < 6; $i++) {
    $string .= $characters[rand(0, strlen($characters) - 1)];
}   

return $string;
  1. 这种生成代码的方法可靠吗?
  2. 可能的排列组合有多少种?(从21个字符的池中生成6位代码)。抱歉,数学不是我的强项。

这里有一些关于不同方法的好帖子:https://dev59.com/1HI-5IYBdhLWcg3wc3-w - user557846
如果您不需要超过800万个代码,并且短小明了的代码是首要考虑因素,我认为您有一个很好的方法来实现它。 - SEngstrom
6个回答

14

21^6 = 85766121 种可能性。

使用数据库并存储已使用的值是不好的。如果你想模拟随机性,可以使用以下方法:

将其缩小为19个可能的数字,并利用素数 p 是奇数时阶数为 p^k 的群总是循环群的事实。

采用阶数为7^19的生成元(与7^19互质的生成元),例如我选择了13^11,你可以选择任何不可被7整除的数字。

这样做是有效的:

$previous = 0;

function generator($previous)
{

  $generator = pow(13,11);
  $modulus = pow(7,19); //int might be too small
  $possibleChars = "ACEFHJKMNPRTUVWXY49";

  $previous = ($previous + $generator) % $modulus;
  $output='';
  $temp = $previous;

  for($i = 0; $i < 6; $i++) {
    $output += $possibleChars[$temp % 19];
    $temp = $temp / 19;
  }

  return $output;
}
它将循环遍历所有可能的值,如果不仔细观察看起来有点随机。一个更安全的选择是乘法群,但我已经忘了我的数学了 :(
它会循环遍历所有可能的值,并且如果不深入研究的话看起来可能有些随机。一个更为安全的替代方案是使用乘法群,但我已经忘记了相关的数学内容 :(

1
生成的代码需要被存储,因为它们与邮戳(产品)相关联,这些邮戳将有与之相关联的信息。如果我生成了10k个代码,然后一个月后又生成了另外10k个代码,我需要以某种方式检查这个新的10k批次中是否有重复的代码。 - Quad6
2
@Jean-BernardPellerin 已将您的代码转换为 PHP 格式,希望您不介意,请检查一下是否一切正常。 - CSᵠ
这个每次都会是唯一的吗?还是我需要手动检查一下?而且我不想只限制在21个字符,任何长度都可以。 - keen
如何使用这个答案。 - PHP_USER1
1
6^19还是19^6?6不是奇素数,所以我假设你指的是后者。在这种情况下,你选择的生成器与它不互质(19^11)- 11^19看起来没问题。我正在寻找5个字符代码,并得出了这个答案;我使用23^5(从字母表中删除I、O和Q)作为模数,而19^11作为生成器,这对我来说似乎有效。 - FreeBird
显示剩余2条评论

7
  • 有很多可能的组合,可以有或没有重复,所以您的逻辑就足够了。
  • 由于您正在使用rand,所以碰撞会经常发生,请参见str_shuffle和randomness
  • rand更改为mt_rand
  • 在检查时,请使用像memcachedredis这样的快速存储,而不是MySQL。

总可能性

21 ^ 6 = 85,766,121

85,766,121 应该没问题,要将数据库添加到这一代中,请尝试:

示例:

$prifix = "stamp.";

$cache = new Memcache();
$cache->addserver("127.0.0.1");

$stamp = myRand(6);
while($cache->get($prifix . $stamp)) {
    $stamp = myRand(6);
}
echo $stamp;

使用的函数

function myRand($no, $str = "", $chr = 'ACEFHJKMNPRTUVWXY4937') {
    $length = strlen($chr);
    while($no --) {
        $str .= $chr{mt_rand(0, $length- 1)};
    }
    return $str;
}

谢谢。8600万足够了。我会使用您建议的mt_rand。我不确定如何使用memcached来检查新生成批次中的代码是否已经存在于MySQL中(包含所有先前生成的代码批次)。为了打印目的,我想一次生成大约10,000个代码。 - Quad6

3
如巴巴所说,动态生成字符串会导致大量冲突。越接近8000万已生成的字符串,获取可用字符串就会变得更加困难。
另一个解决方案是一次性生成所有可能的组合,并将每个组合存储到数据库中,使用一个布尔字段标记行/令牌是否已被使用。
然后从中获取一个可用的字符串。
SELECT * FROM tokens WHERE tokenIsUsed = 0 ORDER BY RAND() LIMIT 0,1

然后将其标记为已使用

UPDATE tokens SET tokenIsUsed = 1 WHERE token = ...

谢谢,我本来以为存储那80万个组合会对查询等方面产生相当大的性能影响。这段代码不仅仅是作为日志存储,还会被查找。 - Quad6
2
我认为你可以将这种方法与@Jean-Bernard的循环生成相结合,使你的查询速度更快-以某种随机顺序填充数据库中的所有组合,每次需要新值时,你可以一次性获取它们。无论如何都值得一试。 - Jerry

3

1
你把底数和指数搞混了,还弄乱了数字的分隔方式,结果是大约8600万。 - Jean-Bernard Pellerin

0

我曾经遇到过同样的问题,后来我找到了一个非常出色的开源解决方案:

http://www.hashids.org/php/

你可以拿来使用,同时查看它的源代码也是值得的,以便了解底层发生了什么。


-2

或者...你可以将用户名+日期时间编码为md5并保存到数据库,这肯定会生成一个唯一的代码;)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接