【问题标题】:How to fetch 'n' random values out of an array c++如何从数组c ++中获取“n”个随机值
【发布时间】:2015-07-02 20:21:27
【问题描述】:

我想从数组中取出n 随机值。 我可以在 Python 中使用x=random.sample(listName, numberOfValuesNeeded)

[code]
#include <iostream>
#include <ctime>
#include <cstdlib>
#include <algorithm>
#include <iterator>

using namespace std;

int main()
{
    const int MAX_Name=100;

    srand((unsigned)time( NULL));

    // Names
    string PsDisp[MAX_Name]={"Alberto", "Bruno", "Carlo", "Dario", "Elio", "Francesco", "Giovanni", "Luca", "Marco", "Nicola", "Oreste", "Pietro",   "Rino", "Sandro", "Tonino", "Valerio", "Vittorio"};


    for(int x=1; x<=6; x++)
    {
        random_shuffle(begin(PsDisp), end(PsDisp));

        cout << x << ": " << endl; // snomething missing before endl
    }

    return 0;
}

我想在屏幕上打印:

1:随机名称1
2:随机名称3 ...
6:随机名称6

【问题讨论】:

  • 注意std::random_shuffle不推荐,因为它的随机性可能很差。 std::shuffle 带有适当的 PRNG 更好。
  • 1-6可以包含重复吗?如果没有,只需排序一次并打印前 6 个。访问元素的方法是 dispNames[x]。并使您的循环条件从 0 开始:for (int x=06; x&lt;6; ++x) ....
  • 首先,这是一种非常低效(并且具有不必要的破坏性)的方法——就像在 Python 中使用 random.shuffle(n); return n[0] 而不是 return random.choice(n)
  • 另外,random.sample 为您提供无替换的样本,但您的算法为您提供替换的样本。如果您希望您的代码无需替换即可工作,您只需随机播放一次并从结果中选择前 6 个值。 (但同样,不要使用 shuffle 进行采样……)

标签: python c++ arrays random


【解决方案1】:

Python 的 random.sample 返回样本没有替换。这意味着您不能两次获得相同的元素。

您的算法显然不会做同样的事情。如果您在获得每个值后重新洗牌,那就是替换这些值。但是有一个简单的方法可以解决这个问题:只需在循环外随机播放一次,而不是每次都通过循环。

这意味着循环非常简单:您只想打印打乱后的 dispNames 中的前 6 个数字。所以:

random_shuffle(begin(dispNames), end(dispNames));
for (int x=1; x<=6; x++) {
    cout << x << ": " << dispNames[x-1] << endl;
}

一些旁注:

  • random_shuffle 已弃用。 shuffle 默认 PRNG 也是如此。这是有充分理由的;即使您不关心加密随机性或任何花哨的东西,您仍然希望使用特定的 PRNG 和新版本的 shuffle,只是为了获得统一的分布。
  • 0 计数到&lt;6 比从1 计数到&lt;=6 更正常。这意味着您不需要x-1
  • 使用迭代器代替索引会更好;那么即使您将 dispNames 替换为其他一些可随机访问的类型而不是数组,相同的代码也可以工作。

从 17 个样本中挑选 6 个样本,这很好。 (好吧,只要破坏性地修改数组是可以接受的,Python 的 random.sample 不会这样做……但如果这是一个问题,用复制洗牌替换它并不难,或者只是一个副本加上一个破坏性随机播放...)

但是,如果您的值变得太大,则需要考虑效率。洗牌本身需要 O(N) 时间,因为它必须洗牌 N 个值中的大部分。但是从 N 中挑选 K 值不应该花那么长时间。 (如果你切换到复制随机播放,你也会添加 O(N) 空间,这也不是必需的。)

我认为 C++ 标准库中没有等价物,但没关系;值得学习如何自己实现它,这样你就会明白它在做什么。有几种不同的选择:您可以在置换生成器(C++ 在 stdlib 中确实有)之上构建它,或者在一个普通线性同余生成器之上构建大于列表长度的下一个素数(抛出值太大),或者部分 Fisher-Yates 洗牌而不是完整洗牌,或者可能是我没有想到的其他事情。 (有些在特定情况下非常简单和快速,但不是一般情况下——例如,如果 K 很小,仅选择替换并跟踪以前的选择会很好,但如果 K 是相当大的一部分,那就太可怕了N.)

当然,您也可以read the CPython source,这是了解如何编写与 Python 完全相同的 C++ 代码的好方法。

【讨论】:

  • 这解决了对代码进行一些更改的问题:DELETE this line const int MAX_Name=100;在字符串 PsDisp[17]= 中更改此行字符串 PsDisp[MAX_Name]= ,然后使用您的解决方案。使用字符串 PsDisp[MAX_Name]= 时,程序通常会为名称打印空行
【解决方案2】:

如果您不关心重复,您可以生成 6 个随机索引并打印相应的值,如下所示:

for(int x=1; x<=6; x++)
{
    cout << x << ": " << dispNames[rand() % MAX_Name] << endl;
}

【讨论】:

  • - 一百万用于rand()(并在上面使用%)。 C++ has proper facilities for PRNGs for a while now.See STL's presentation.
  • @BoBTFish 谢谢,多年来我一直没有理由在自己的工作中使用随机生成。前提仍然成立,不是吗?只需将 rand 替换为您选择的 PRNG?
  • 差不多,只要这具有 OP 之后的行为(从问题中不清楚)。
  • 仅仅替换 rand 并没有帮助;使用% 甚至会毫无理由地将完全均匀的分布变成非均匀分布。
  • 啊,好吧,看来我得先去读一读,然后再在程序中实现任何随机性
【解决方案3】:

您可以使用一种称为Reservoir Sampling 的技术。它只在输入上迭代一次,因此需要 O(n)。这是您的示例代码,其中包含有效的 C++11 水库采样:

#include <iostream>
#include <string>
#include <vector>
#include <random>
#include <iterator>
#include <algorithm>
#include <cassert>


template<class InputIt, class Generator>
std::vector<typename std::iterator_traits<InputIt>::value_type> sample(InputIt first, InputIt last, size_t n, Generator& engine) {
    assert(distance(first, last) >= n);
    InputIt nth = next(first, n);
    std::vector<typename std::iterator_traits<InputIt>::value_type> result{first, nth};
    size_t k=n+1;
    for (InputIt it = nth; it != last; ++it, ++k) {
        size_t r = std::uniform_int_distribution<size_t>{0, k}(engine);
        if (r<n)
            result[r] = *it;
    }
    return result;
}


using namespace std;

int main()
{

    string xs[] = {"Alberto", "Bruno", "Carlo", "Dario", "Elio", "Francesco", "Giovanni", "Luca", "Marco", "Nicola", "Oreste", "Pietro",   "Rino", "Sandro", "Tonino", "Valerio", "Vittorio"};

    mt19937 engine{random_device{}()};
    vector<string> ys = sample(begin(xs), end(xs), 5, engine);

    for (const string& s : ys)
        cout << s << endl;

    return 0;
}

【讨论】:

    猜你喜欢
    • 2012-02-16
    • 2017-12-29
    • 2010-12-11
    • 2019-03-09
    • 2012-12-27
    • 2022-11-01
    • 1970-01-01
    相关资源
    最近更新 更多