【问题标题】:How to shuffle a std::vector?如何洗牌 std::vector?
【发布时间】:2011-10-19 01:53:36
【问题描述】:

我正在寻找一种通用的、可重用的方法来在 C++ 中对 std::vector 进行洗牌。这是我目前的做法,但我认为它不是很有效,因为它需要一个中间数组并且它需要知道项目类型(本例中为 DeckCard):

srand(time(NULL));

cards_.clear();

while (temp.size() > 0) {
    int idx = rand() % temp.size();
    DeckCard* card = temp[idx];
    cards_.push_back(card);
    temp.erase(temp.begin() + idx);
}

【问题讨论】:

标签: c++ shuffle stdvector


【解决方案1】:

从 C++11 开始,您应该更喜欢:

#include <algorithm>
#include <random>

auto rng = std::default_random_engine {};
std::shuffle(std::begin(cards_), std::end(cards_), rng);

Live example on Coliru

如果您打算每次都生成不同的排列,请确保在多次调用 std::shuffle 时重复使用相同的 rng 实例!

此外,如果您希望程序在每次运行时创建不同的随机播放序列,您可以使用std::random_device 的输出为随机引擎的构造函数提供种子:

auto rd = std::random_device {}; 
auto rng = std::default_random_engine { rd() };
std::shuffle(std::begin(cards_), std::end(cards_), rng);

对于 C++98,您可以使用:

#include <algorithm>

std::random_shuffle(cards_.begin(), cards_.end());

【讨论】:

  • 您还可以插入自定义随机数生成器作为std::random_shuffle 的第三个参数。
  • +1 - 请注意,这可能会在每次运行程序时产生相同的结果。如果这是一个问题,您可以添加自定义随机数生成器(可以从外部来源播种)作为附加参数到 std::random_shuffle
  • @Gob00st:它将为程序的每个实例生成相同的结果,而不是每次调用random_shuffle。这种行为是正常的和有意的。
  • @ParkYoung-Bae 谢谢I just found out。当 SO 答案不包含包含信息时,真的很不方便,因为它们位于谷歌搜索结果的顶部。
  • 我认为您应该将如何播种此内容添加到您的答案中。很多人(就是我)来这里是因为他们在 Google 上搜索了“c++, shuffle vector”。
【解决方案2】:

根据您必须遵循的标准 (C++11/C++14/C++17),此“cppreference”页面提供了很好的示例:https://en.cppreference.com/w/cpp/algorithm/random_shuffle

【讨论】:

    【解决方案3】:

    还可以更简单,完全避免播种:

    #include <algorithm>
    #include <random>
    
    // Given some container `container`...
    std::shuffle(container.begin(), container.end(), std::random_device());
    

    这将在程序每次运行时产生一个新的随机播放。由于代码简单,我也喜欢这种方法。

    这是可行的,因为我们需要 std::shuffle 是一个 UniformRandomBitGenerator,它的要求 std::random_device 满足。

    注意:如果反复洗牌,最好将random_device存储在局部变量中:

    std::random_device rd;
    std::shuffle(container.begin(), container.end(), rd);
    

    【讨论】:

    • 这增加了 8 年前尚未被接受的答案的一部分?
    • 您所要做的就是阅读答案以找出答案...没有太多要说的,上面还没有很清楚地解释过。
    • 旧接受的答案可能更深入。然而,这正是我在毫不费力地搜索这样一个简单的问题时所期望的单行傻瓜式答案。 +1
    • 这是错误的random_device 被设计为只被调用一次来播种 PRNG,而不是被一遍又一遍地调用(这可能会很快耗尽底层熵并导致它切换到次优生成方案)
    • 这可能是一个需要补充的重要警告,但它远非你如此戏剧性地指责的“错误”。
    【解决方案4】:

    http://www.cplusplus.com/reference/algorithm/shuffle/

    // shuffle algorithm example
    #include <iostream>     // std::cout
    #include <algorithm>    // std::shuffle
    #include <vector>       // std::vector
    #include <random>       // std::default_random_engine
    #include <chrono>       // std::chrono::system_clock
    
    int main () 
    {
        // obtain a time-based seed:
        unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
        std::default_random_engine e(seed);
    
        while(true)
        {
          std::vector<int> foo{1,2,3,4,5};
    
          std::shuffle(foo.begin(), foo.end(), e);
    
          std::cout << "shuffled elements:";
          for (int& x: foo) std::cout << ' ' << x;
          std::cout << '\n';
        }
    
        return 0;
    }
    

    【讨论】:

    • cplusplus.com/reference/algorithm/shuffle 复制的一个坏例子。你如何再次调用 shuffle?
    • @miracle173 示例改进
    • 为什么不使用std::random_device 而是将系统时钟奇怪地用于种子?
    【解决方案5】:

    如果你使用boost,你可以使用这个类(debug_mode 设置为false,如果你希望随机化在执行之间是可预测的,你必须将它设置为true):

    #include <iostream>
    #include <ctime>
    #include <boost/random/mersenne_twister.hpp>
    #include <boost/random/uniform_int.hpp>
    #include <boost/random/uniform_int_distribution.hpp>
    #include <boost/random/variate_generator.hpp>
    #include <algorithm> // std::random_shuffle
    
    using namespace std;
    using namespace boost;
    
    class Randomizer {
    private:
        static const bool debug_mode = false;
        random::mt19937 rng_;
    
        // The private constructor so that the user can not directly instantiate
        Randomizer() {
            if(debug_mode==true){
                this->rng_ = random::mt19937();
            }else{
                this->rng_ = random::mt19937(current_time_nanoseconds());
            }
        };
    
        int current_time_nanoseconds(){
            struct timespec tm;
            clock_gettime(CLOCK_REALTIME, &tm);
            return tm.tv_nsec;
        }
    
        // C++ 03
        // ========
        // Dont forget to declare these two. You want to make sure they
        // are unacceptable otherwise you may accidentally get copies of
        // your singleton appearing.
        Randomizer(Randomizer const&);     // Don't Implement
        void operator=(Randomizer const&); // Don't implement
    
    public:
        static Randomizer& get_instance(){
            // The only instance of the class is created at the first call get_instance ()
            // and will be destroyed only when the program exits
            static Randomizer instance;
            return instance;
        }
    
        template<typename RandomAccessIterator>
        void random_shuffle(RandomAccessIterator first, RandomAccessIterator last){
            boost::variate_generator<boost::mt19937&, boost::uniform_int<> > random_number_shuffler(rng_, boost::uniform_int<>());
            std::random_shuffle(first, last, random_number_shuffler);
        }
    
        int rand(unsigned int floor, unsigned int ceil){
            random::uniform_int_distribution<> rand_ = random::uniform_int_distribution<> (floor,ceil);
            return (rand_(rng_));
        }
    };
    

    您可以使用以下代码对其进行测试:

    #include "Randomizer.h"
    #include <iostream>
    using namespace std;
    
    int main (int argc, char* argv[]) {
        vector<int> v;
        v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);
        v.push_back(6);v.push_back(7);v.push_back(8);v.push_back(9);v.push_back(10);
    
        Randomizer::get_instance().random_shuffle(v.begin(), v.end());
        for(unsigned int i=0; i<v.size(); i++){
            cout << v[i] << ", ";
        }
        return 0;
    }
    

    【讨论】:

    • 你为什么用时间而不是std::random_device来播种生成器?
    【解决方案6】:

    除了@Cicada 说的,你应该先播种,

    srand(unsigned(time(NULL)));
    std::random_shuffle(cards_.begin(), cards_.end());
    

    根据@FredLarson 的评论:

    这个版本的 random_shuffle() 的随机性来源是 实现定义,所以它可能根本不使用 rand() 。然后 srand() 不会有任何影响。

    所以YMMV。

    【讨论】:

    • 其实这个版本的random_shuffle()的随机性来源是实现定义的,所以它可能根本不使用rand()。然后srand() 将无效。我以前也遇到过。
    • @Fred:谢谢 Fred。不知道。我一直习惯使用 srand。
    • 您可能应该删除此答案,因为它是错误的,而且 - 更糟糕的是 - 它看起来是正确的,并且在某些实现中确实是正确的,但不是全部,这使得这个建议非常危险。
    • 正如@Fred 上面解释的那样,random_shuffle 用来生成随机数的方法是由实现定义的。这意味着在您的实现中它使用rand()(因此 srand() 有效)但在我的实现中它可以使用完全不同的东西,这意味着在我的实现中,即使每次运行程序时使用 srand 我都会得到相同的结果。
    • @Code:就像我们讨论的那样,它并不适用于所有实现。您的答案中没有提到您可以提供自己的号码生成这一事实,并且无论如何都与此讨论无关。我觉得我们在绕圈子:S
    猜你喜欢
    • 2016-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-31
    相关资源
    最近更新 更多