即使RAND_MAX 大于dictionary.size(),使用% 运算符选择索引也会导致非均匀分布。模数会导致选择较早的单词比后面的单词更频繁(除非RAND_MAX + 1 是dictionary.size() 的整数倍)。
考虑一个简单的例子:假设你的字典有 10 个单词,RAND_MAX 是 14。当rand() 返回一个从 0 到 9 的值时,直接选择对应的单词。但是当rand() 是10 到14 时,将选择前五个单词中的一个。所以前五个词被选中的几率是后五个词的两倍。
将 [0..RAND_MAX] 映射到 [0..dictionary.size()) 的更好方法是使用除法:
assert(RAND_MAX + 1 >= dictionary.size());
randNumb = rand() * dictionary.size() / (RAND_MAX + 1);
但是你必须小心整数溢出。如果RAND_MAX * dictionary.size() 比您可以用整数表示的大,您将需要使用更大的数据类型。一些系统有一个类似MulDiv 的函数就是为了这个目的。如果你没有MulDiv 之类的东西,你可以转换为浮点类型,然后将结果截断回整数:
double temp = static_cast<double>(rand()) * dictionary.size() / (RAND_MAX + 1);
randNumb = static_cast<int>(temp);
这仍然是一个不完美的分布,但“热门”词现在将均匀分布在字典中,而不是一开始就聚集在一起。
RAND_MAX + 1 越接近dictionary.size() 的整数倍,您的情况就越好。如果您不能确定它是否接近整数倍,那么您希望 RAND_MAX 相对于dictionary.size() 尽可能大。
由于您对RAND_MAX 没有太多控制权,您可以考虑调整dictionary.size()。例如,如果您只想要六个字母的单词,那么为什么不将其他所有单词都从字典中剔除呢?
std::vector<std::string> six_letter_words;
std::copy_if(dictionary.begin(), dictionary.end(),
std::back_inserter(six_letter_words),
[](const std::string &word){ return word.size() == 6; });
通过缩减集,我们可以使用更通用的算法来选择单词:
typedef std::vector<std::string> WordList;
// Returns true with the given probability, which should be 0.0 to 1.0.
bool Probably(double probability) {
return (static_cast<double>(std::rand()) / RAND_MAX) < probability;
}
// Selects n words from the dictionary using a normal distribution and
// copies them to target.
template <typename OutputIt>
OutputIt Select(int n, const WordList &dictionary, OutputIt target) {
double count = static_cast<double>(n);
for (std::size_t i = 0; count > 0.0 && i < dictionary.size(); ++i) {
if (Probably(count / (dictionary.size() - i))) {
*target++ = dictionary[i];
count -= 1.0;
}
}
return target;
}
这个想法是逐步遍历字典中的每个单词,并以您需要选择的单词数除以剩余要选择的单词数的概率来选择它。这很好用,即使RAND_MAX 相对较小。但总的来说,它比尝试随机选择索引要多得多。另请注意,这种技术永远不会多次选择同一个词,而索引映射技术可以。
你像这样打电话给Select:
// Select six words from six_letter_words using a normal distribution.
WordList selected;
Select(6, six_letter_words, std::back_inserter(selected));
另请注意,rand() 的大多数实现都非常简单,一开始可能无法提供良好的正态分布。