std :: vectorをシャッフルする方法は?


97

std::vectorC ++でをシャッフルするための汎用的で再利用可能な方法を探しています。これは私が現在行っている方法ですが、中間配列が必要であり、アイテムタイプ(この例では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);
}

いいえ。fisher-yatesを調べてください。...–
Mitch Wheat、

3
を使用しないようにしてください。rand()より良いRNG APIが利用可能です(Boost.Randomまたは0x <random>)。
Cat Plus Plus

回答:


201

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

毎回異なる順列を生成rngするstd::shuffle場合は、複数の呼び出し全体で同じインスタンスを必ず再利用してください!

さらに、プログラムを実行するたびに異なるシーケンスのシャッフルを作成する場合は、ランダムエンジンのコンストラクターに次の出力をシードできます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());

8
カスタム乱数ジェネレータをの3番目の引数としてプラグインすることもできますstd::random_shuffle
Alexandre C.

19
+1-これにより、プログラムを実行するたびに同じ結果が生成される場合があります。std::random_shuffleこれが問題である場合は、追加の引数としてカスタム乱数ジェネレータ(外部ソースからシードできる)を追加できます。
マンカルス2011

4
@ Gob00st:へのすべての呼び出しではなく、プログラムのすべてのインスタンスに対して同じ結果を生成しrandom_shuffleます。この動作は正常であり、意図されています。
user703016

3
TomášZato@#include <algorithm>
user703016

4
@ ParkYoung-Baeありがとう、私はちょうど見つけた。Googleの検索結果の上部にあるため、SOの回答にインクルード情報が含まれていないと、本当に不便です。
トマーシュZato -復活モニカ

10

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からコピーした悪い例。どのようにしてシャッフルをもう一度呼び出しますか?
miracle173 2018

@ miracle173の例を改善
Mehmet Fide

2
シードを使用するだけでなく、システムクロックを奇妙に使用するのはなぜstd::random_deviceですか。
チャックウォルボーン

6

@Cicadaが言ったことに加えて、おそらく最初にシードする必要があります。

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

@FredLarsonのコメントによると:

このバージョンのrandom_shuffle()のランダム性のソースは実装定義であるため、rand()をまったく使用しない場合があります。その場合、srand()は効果がありません。

だからYMMV。


10
実際、このバージョンののランダム性のソースrandom_shuffle()は実装定義であるため、まったく使用さrand()れない可能性があります。その後srand()、効果はありません。以前に遭遇したことがあります。
Fred Larson

@フレッド:ありがとうフレッド。それを知りませんでした。私はいつもsrandを使うことに慣れています。

6
この回答は誤りであり、さらに悪いことに、一部の実装では正しいように見え、実際には正しいため、おそらくこの回答を削除する必要があります。このアドバイスは非常に危険です。
Thomas Bonini、2011

2
@Fredが上記で説明したように、random_shuffle乱数の生成に使用されるのは実装定義です。これは、実装で使用するrand()(したがってsrand()が機能する)が、私の場合はまったく異なるものを使用できることを意味します。つまり、私の実装では、srandを使用しても、プログラムを実行するたびに同じ結果が得られます。
Thomas Bonini、2011

2
@コード:先ほど説明したように、すべての実装で機能するわけではありません。独自の番号生成を提供できるという事実は、回答では言及されておらず、いかなる場合でもこの議論とは無関係です。まるでサークルに入っているような気がします:S
トーマスボニーニ

2

使用している場合は、ブーストを(あなたは、このクラスを使用することができますdebug_modeに設定されているfalseあなたはランダム化は、あなたがそれを設定する必要があり、予測可能beetween実行することができることをしたい場合、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なぜジェネレーターのシードに時間を使用しているのですか?
チャックウォルボーン

1

それはもっと簡単にすることができ、シーディングは完全に回避できます:

#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);

2
8年前に承認された回答の一部ではなかった、これは何を追加しますか?
ChrisMM

1
あなたがしなければならないすべては見つけるために答えを読むことです...まだ上で非常に明確に説明されていないことについて言うべきことはこれ以上ありません。
Apollysは

1
承認された回答はすでにシャッフルを使用しており、使用するように言っていますrandom_device...
ChrisMM

1
古い受け入れられた答えはより詳細かもしれません。しかし、これはまさしくそのような単純な質問をググググして苦労せずにググるときに私が期待するであろう単一行のポイントアンドショットの答えです。+1
Ichthyo

2
これは間違いです。PRNGをシードするために1回random_deviceだけ呼び出されるように設計されており、何度も呼び出されないようになっています(基礎となるエントロピーをすぐに使い果たし、最適ではない生成スキームに切り替わる可能性があります)
LF

0

従う必要がある標準(C ++ 11 / C ++ 14 / C ++ 17)に応じて、この「cppreference」ページにはかなり良い例が提供されています。https://en.cppreference.com/w/cpp/algorithm/ random_shuffle

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.