【问题标题】:What is the most efficient way to generate random strings in C++?在 C++ 中生成随机字符串的最有效方法是什么?
【发布时间】:2020-12-01 02:37:34
【问题描述】:

我需要有效地生成随机字符串。在下文中,您将看到我的第一次尝试。我用 gcc 和 -O3 优化级别编译了代码。生成 10^7 个长度为 64 的随机字符串需要 18.5 秒

#include <iostream>
#include <random>
#include <algorithm>

std::string chars {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()`~-_=+[{]{|;:'\",<.>/?"};
std::random_device rd;
std::mt19937 generator(rd());
  
std::string rand_str (int length) {
  std::string output (chars);
  std::shuffle(output.begin(), output.end(), generator);
  return output.substr(0, length);
}

int main() {
  std::string str;
  for (long i=0; i<10000000; ++i)
      str = rand_str (64);
}

我在 c++17 中检查了std::sample,它并不比上述方法快。另外,它不会改变字符的顺序,所以不是真正的随机。

编辑:std::shuffle 不是一个好的选择,因为它不允许重复。基于 cmets 我修改了代码。这次 10^7 个随机数需要 9 分钟

std::string rand_str (size_t length) {
  const size_t char_size = chars.size();
  std::uniform_int_distribution<> random_int (0, char_size - 1);
  std::string output;
  for (size_t i=0; i<length; ++i)
    output.push_back(chars[random_int(generator)]);
  return output;
}

问题

  • 在现代 C++ 中是否有更有效的方法来做到这一点?

感谢任何改进代码的建议。

【问题讨论】:

  • 首先,你不说随机字符串的要求。根据您的代码,要求是每个随机字符串不能有重复的字符。
  • std::mt19937 具有相对较好的 PRNG“质量”,但也不是超快。有更快的 PRNG,例如Xorshift
  • 另一个观察结果:1)您的代码没有可观察到的效果,因此编译器可能会将其完全优化为return 0; in main。 2)每次迭代都有分配,这是不必要的。
  • 如果您不想重复,则使用从0 到大小为chars 的随机数生成器,然后使用该随机生成器的数字进行chars[random_number] 访问可能会更快。
  • 生成随机数的速度比您预期的要慢得多。它很容易成为门控因素。

标签: c++ string random c++17


【解决方案1】:
#include <iostream>
#include <random>
#include <algorithm>
#include <chrono>

std::string chars {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()`~-_=+[{]{|;:'\",<.>/?"};
std::random_device rd;
std::mt19937 generator(rd());
  
std::string rand_str(int length) {
  std::string output;
  output.reserve(length);

  while(length>0)
  {
      auto randNumb = generator();
      while(randNumb > 93 && length--)
      {
        output.push_back(chars[randNumb%93]);
        randNumb/=93;
      }
  }
  return output;
}

int main() {
  auto startTP = std::chrono::system_clock::now();
  std::string rand_bytes;
  for (long i=0; i<10000000; ++i)
      rand_bytes = std::move(rand_str(64));
  auto endTP = std::chrono::system_clock::now();

  std::cout << "This took: " << std::chrono::duration_cast<std::chrono::microseconds>(endTP-startTP).count() << std::endl;
}

这在我的机器上大约需要 3 秒。诀窍是尽可能少地调用随机数生成器,并且只分配一次内存。

我正在做的是将 randNumber 从基数 10 转换为基数 93(字符的长度)。之后,我使用每个基数 93 位作为不同的随机数。这为每个生成的随机数提供了大约 5 个数字。

【讨论】:

  • @t.niese 这是不正确的。可能的字符串有 length^chars.size() 数量,并且字符串仅与生成器一样可预测
  • 这只是将一个 randNumb 分解为 base-93 数字,应该很好,前提是 generator.min() == 0 和 (log generator.max() base 93) >= length
  • 按照你写的方式,我认为这是对a的偏见。
  • @A.Hristov:您仍然低估了偏差,这在您的更正版本中很容易观察到,在选择频率上的差异约为 0.45%。 (它比原始版本好得多,后者接近 40%。)您上面的分析只考虑了从随机数中提取的四个以 93 为基数的最后一个数字。但第一位数字的偏差要大得多:几乎 2%。此外,如果第一个数字恰好是 0('a'),而剩下的恰好是 0(概率约为 2%),那么a 将被丢弃。所以 [b-L] 是最常见的。
  • @A.Hristov:3 次除以 93 后,有 5340 个可能的残基(其中一个的概率比其他的低,但这是一个相对较小的细节。)这 5340 个残基对应到 57 个 0-92 的完整范围,加上一个 0-38 的不完整范围。因此,值 0-38 产生 58 次,每 57 次产生 39-92 的值。 (实际上是 57.6,因为范围不完整,使值 38 处于中间值。)
猜你喜欢
  • 2011-09-23
  • 1970-01-01
  • 1970-01-01
  • 2012-12-29
  • 2012-03-17
  • 1970-01-01
  • 1970-01-01
  • 2013-08-21
  • 1970-01-01
相关资源
最近更新 更多