How to shuffle a std::vector?
I am looking for a generic, reusable way to shuffle a std::vector
in C++. This is how I currently do it, but I think it's not very efficient because it needs an intermediate array and it needs to know the item type (DeckCard in this example):
srand(time(NULL));
cards_.clear();
while (temp.size() > 0) {
int idx = rand() % temp.size();
DeckCard* card = temp[idx];
cards_.push_back(开发者_StackOverflowcard);
temp.erase(temp.begin() + idx);
}
From C++11 onwards, you should prefer:
#include <algorithm>
#include <random>
auto rng = std::default_random_engine {};
std::shuffle(std::begin(cards_), std::end(cards_), rng);
Live example on Coliru
Make sure to reuse the same instance of rng
throughout multiple calls to std::shuffle
if you intend to generate different permutations every time!
Moreover, if you want your program to create different sequences of shuffles each time it is run, you can seed the constructor of the random engine with the output of std::random_device
:
auto rd = std::random_device {};
auto rng = std::default_random_engine { rd() };
std::shuffle(std::begin(cards_), std::end(cards_), rng);
For C++98 you may use:
#include <algorithm>
std::random_shuffle(cards_.begin(), cards_.end());
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;
}
In addition to what @Cicada said, you should probably seed first,
srand(unsigned(time(NULL)));
std::random_shuffle(cards_.begin(), cards_.end());
Per @FredLarson's comment:
the source of randomness for this version of random_shuffle() is implementation defined, so it may not use rand() at all. Then srand() would have no effect.
So YMMV.
It can be even simpler, seeding can be avoided entirely:
#include <algorithm>
#include <random>
// Given some container `container`...
std::shuffle(container.begin(), container.end(), std::random_device());
This will produce a new shuffle each time the program is run. I also like this approach due to the simplicity of the code.
This works because all we need for std::shuffle
is a UniformRandomBitGenerator
, whose requirements std::random_device
meets.
Note: if shuffling repeatedly, it may be better to store the random_device
in a local variable:
std::random_device rd;
std::shuffle(container.begin(), container.end(), rd);
If you are using boost you could use this class (debug_mode
is set to false
, if you want that the randomizing could be predictable beetween execution you have to set it to 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_));
}
};
Than you can test it with this code:
#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;
}
Depending of the standard you have to follow (C++11/C++14/C++17) this "cppreference" page provides pretty good examples: https://en.cppreference.com/w/cpp/algorithm/random_shuffle.
精彩评论