为什么这个随机字符串生成器表现如此糟糕?

3
我发现了这段 PHP 代码,可以生成随机字符串(包括字母、字母数字组合、纯数字和十六进制)。
<?php
function random($length = 8, $seeds = 'alpha') {
  // Possible seeds
  $seedings['alpha'] = 'abcdefghijklmnopqrstuvwqyz';
  $seedings['numeric'] = '0123456789';
  $seedings['alphanum'] = 'abcdefghijklmnopqrstuvwqyz0123456789';
  $seedings['hexidec'] = '0123456789abcdef';

  // Choose seed
  if (isset($seedings[$seeds])) {
    $seeds = $seedings[$seeds];
  }

  // Seed generator
  list($usec, $sec) = explode(' ', microtime());
  $seed = (float) $sec + ((float) $usec * 100000);
  mt_srand($seed);

  // Generate
  $str = '';
  $seeds_count = strlen($seeds);

  for ($i = 0; $length > $i; $i++) {
    $str .= $seeds{mt_rand(0, $seeds_count - 1)};
  }

  return $str;
}
?>

如果我使用默认参数运行此函数(生成仅包含字母的8个字符字符串),并生成100万个字符串,则我认为我的冲突率应该很低:
26^8 = 208,827,064,576
1,000,000 / 208,827,064,576 ~= 0.0004%

实际上,当我在我的机器上运行此代码时,90%的冲突率!只有10%的字符串是唯一的。
实际上,这个数字可疑地接近10%。生成多组1,000,000个随机字符串,我发现每组生成...
- 100,032个独特字符串 - 100,035个独特字符串 - 100,032个独特字符串 - 100,028个独特字符串 - 100,030个独特字符串 - 你懂的
那么问题出在哪里呢?显然与如何种子化mt_srand有关,或php如何实现mt_rand,或其他原因。
那么...
为什么这段代码不能生成有用的随机字符串?
有什么更好的方法吗?

1
代码审查或是数学方面的讨论区可能更合适。 - user557846
1
你尝试过不使用种子吗? - Don't Panic
1
@dagon 由于这不是要求审核,Code Review 不是一个好的选择。 - Kaz
1
@Dagon,这绝对不是Code Review的主题 - Mast
1
我可能完全错了,但是这个函数是否基于高分辨率时间戳生成字符串?如果是这样,答案可能是“时间戳的分辨率不够高”。这也方便地解释了为什么您的唯一字符串数量是总数的1/10。 - Kaz
显示剩余4条评论
1个回答

3

除非你知道自己在做什么,否则不要设置种子。来自手册:

注意:不需要使用srand()或mt_srand()来初始化随机数生成器,因为这是自动完成的。

以下代码可以让我获得几乎100%独特的字符串集合

<?php
  function random($length = 8, $charset = 'alpha'){
    $list = [
      'alpha' => 'abcdefghijklmnopqrstuvwqyz',
      'numeric' => '0123456789',
      'alphanum' => 'abcdefghijklmnopqrstuvwqyz0123456789',
      'hexidec' => '0123456789abcdef'
    ];

    if(!isset($list[$charset])){
      trigger_error("Invalid charset '$charset', allowed sets: '".implode(', ', array_keys($list))."'", E_USER_NOTICE);
      $charset = 'alpha';
    }

    $str   = '';
    $max   = strlen($list[$charset]) - 1;

    for ($i = 0; $length > $i; $i++) {
      $str .= $list[$charset][mt_rand(0, $max)];
    }

    return $str;
  }

  $loop = 1000000;

  for($i=0;$i<$loop;$i++){
    $arr[random()] = true;
  }

  echo $loop - count($arr), " dupes found in list.";
?>

你如何检查唯一性?我想尝试一下,但不确定一个好的方法。 - user557846
是的,我想到了,我担心的是那个有一百万条目的数组;-) - user557846
1
@Dagon,你会看到他的代码正在使用1000000次迭代来设置索引$arr[random()],如果该索引已经存在,则最终结果基本上会小于1000000。 - Scuzzy
1
谢谢!是的,使用我的种子方法结果非常糟糕,不使用种子要好得多。刚刚运行了没有mt_srand的代码,得到了1,000,000个中的999,998个唯一字符串。 - romellem

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