【问题标题】:Random selection of dictionary words随机选择字典单词
【发布时间】:2012-03-04 12:46:36
【问题描述】:

我想从一个数组中随机选择一些单词来组成总共 36 个字母。

起初我尝试选择一个随机单词并在检查它不长于我们拥有的可用空间量后添加它。这效率不高,因为列表会填满,并且只剩下 2-3 个字母的单词的空白空间,而且要找到这么短的单词需要很长时间。

所以我决定只选择六个 6 个字母的单词,我这样做是通过生成一个随机数然后将其递增 1 直到我们找到一个 6 个字母的单词。它非常快,但单词并不是那么随机,我经常得到以同一个字母开头的单词,或者只有以 a、b、c 或 x、y、z 等字母开头的单词。

srand ( time(NULL) );
for(int i=0;i<6;i++)
{
    randNumb = rand()%dictionary.size();
    while(dictionary.at(randNumb).length() != 6)
    {
        randNumb++;
    }
    a << "/" << dictionary.at(randNumb) << "/";
}

我想选择不同长度的单词,但为了提高性能,我会只选择 6 个字母的单词,但我至少希望它们被随机选择。

【问题讨论】:

  • 请注意,使用模来限制随机数的间隔会引入偏差。

标签: c++ random numbers


【解决方案1】:

你应该得到一个新的随机数而不是增加索引。你这样做的方式,所有不符合你的条件的字符串都会“吸引”更多的随机数,并可能导致以下字符串被选中的概率更高。

【讨论】:

  • 这似乎没有帮助,而调试数字似乎在 0-40,000 范围内是随机的,但字典有 250,000 个单词。我猜这是因为新随机数之间的间隔很短。
  • @Arnas:你没有说是什么让你认为它“不起作用”。当你的字典本质上是非随机的(很可能只有 250k 个单词),那么输出的字符串当然也会非常非随机。
【解决方案2】:

rand() function 生成一个介于0 和RAND_MAX 之间的数字。

如果RAND_MAX 定义为32767,那么您将无法访问字典(数组?)中索引大于该值的元素。

如果您需要生成一个大于RAND_MAX 的随机数,那么考虑将n 调用rand() 的结果相加,这样n * RAND_MAX &gt;= dictionary.size()。然后保证这个结果的模数会给出一个落在整个字典范围内的索引。

【讨论】:

  • 添加随机数会改变分布。
  • 均匀分布之和的分布是正态分布,这是真的。但真正的问题是生成的索引在[0, RAND_MAX] 范围内而不是[0, dictionary.size()-1] 范围内,因此,并非所有字典元素都可以访问。因此,无论使用什么方法生成随机数,输出允许访问整个字典似乎很重要。
  • 最好是分割一个集合,而不是求和。正如 Björn 指出的那样,对随机数求和会改变分布。结果就像在骰子游戏中一样 - 中间的数字被抽得更频繁。
  • 您可以在之前的结果中添加新的随机数。因此,移位将变得随机,使索引统一。
  • 抱歉浪费了你们的时间,伙计们,我尝试按照 Alex 的建议进行操作并对结果感到满意,但是当我尝试在 android 上运行相同的操作时,应用程序会崩溃。经过一些测试,我发现在android中运行时 rand() 没有 RAND_MAX 限制,所以 rand()%(dictionary.size()-1);工作得很好。我正在使用 cocos2dx,所以我不知道引擎是否进行了此更改或 android ndk。
【解决方案3】:

即使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() 的大多数实现都非常简单,一开始可能无法提供良好的正态分布。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-18
    • 2016-04-14
    • 2020-04-06
    • 1970-01-01
    • 1970-01-01
    • 2019-04-24
    • 1970-01-01
    相关资源
    最近更新 更多