Come mescolare uno std :: vector?


97

Sto cercando un modo generico e riutilizzabile per mescolare un std::vectorin C ++. Questo è il modo in cui lo faccio attualmente, ma penso che non sia molto efficiente perché ha bisogno di un array intermedio e deve conoscere il tipo di elemento (DeckCard in questo esempio):

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

no. cerca i pescatori ...
Mitch Wheat

3
Cerca di non usarlo rand(), sono disponibili API RNG migliori (Boost.Random o 0x <random>).
Cat Plus Plus

Risposte:


202

Da C ++ 11 in poi, dovresti preferire:

#include <algorithm>
#include <random>

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

Live example on Coliru

Assicurati di riutilizzare la stessa istanza di rngpiù chiamate a std::shufflese intendi generare permutazioni diverse ogni volta!

Inoltre, se vuoi che il tuo programma crei diverse sequenze di mescolamenti ogni volta che viene eseguito, puoi seminare il costruttore del motore casuale con l'output di std::random_device:

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

Per C ++ 98 puoi usare:

#include <algorithm>

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

8
Puoi anche collegare un generatore di numeri casuali personalizzato come terzo argomento di std::random_shuffle.
Alexandre C.

19
+1 - Nota che questo può produrre un risultato identico ad ogni esecuzione del programma. È possibile aggiungere un generatore di numeri casuali personalizzato (che può essere inizializzato da una fonte esterna) come argomento aggiuntivo std::random_shufflese questo è un problema.
Mankarse

4
@ Gob00st: genererà lo stesso risultato per ogni istanza del programma, non per ogni chiamata a random_shuffle. Questo comportamento è normale e intenzionale.
user703016

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

4
@ ParkYoung-Bae Grazie, l' ho appena scoperto . È davvero scomodo quando le risposte SO non contengono informazioni di inclusione perché sono in cima ai risultati di ricerca di Google.
Tomáš Zato - Ripristina Monica il

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;
}

un cattivo esempio copiato da cplusplus.com/reference/algorithm/shuffle . Come si effettua un'altra chiamata di shuffle?
miracle173

@ miracle173 esempio migliorato
Mehmet Fide

2
Perché l'uso strano dell'orologio di sistema per un seme invece di usarlo std::random_device?
Chuck Walbourn

6

Oltre a ciò che ha detto @Cicada, dovresti probabilmente seminare prima,

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

Per il commento di @ FredLarson:

la fonte di casualità per questa versione di random_shuffle () è definita dall'implementazione, quindi potrebbe non usare affatto rand (). Quindi srand () non avrebbe alcun effetto.

Quindi YMMV.


11
In realtà, la fonte della casualità per questa versione di random_shuffle()è definita dall'implementazione, quindi potrebbe non essere utilizzata rand()affatto. Allora non srand()avrebbe alcun effetto. Ci sono già capitato.
Fred Larson

@Fred: Grazie Fred. Non lo sapevo. Sono stato abituato a usare srand tutto il tempo.

6
Probabilmente dovresti cancellare questa risposta in quanto è sbagliata e - peggio ancora - sembra corretta e in effetti è corretta in alcune implementazioni, ma non tutte, rendendo questo consiglio molto pericoloso.
Thomas Bonini

2
Come @Fred ha spiegato sopra, ciò che random_shuffleutilizza per generare un numero casuale è definito dall'implementazione. Ciò significa che sulla tua implementazione usa rand()(e quindi srand () funziona) ma sulla mia può usare qualcosa di totalmente diverso, il che significa che sulla mia implementazione anche con srand ogni volta che eseguo il programma otterrò gli stessi risultati.
Thomas Bonini

2
@Code: come abbiamo discusso, non funziona in tutte le implementazioni. Il fatto che tu possa fornire la tua generazione di numeri non è menzionato nella tua risposta e in ogni caso non è correlato a questa discussione. Sento che stiamo andando in tondo: S
Thomas Bonini

2

Se stai usando boost puoi usare questa classe ( debug_modeè impostata su false, se vuoi che la randomizzazione possa essere prevedibile tra le esecuzioni devi impostarla su 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_));
    }
};

Quindi puoi testarlo con questo codice:

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

Perché stai usando il tempo per seminare il generatore invece di std::random_device?
Chuck Walbourn

1

Può essere ancora più semplice, la semina può essere completamente evitata:

#include <algorithm>
#include <random>

// Given some container `container`...
std::shuffle(container.begin(), container.end(), std::random_device());

Questo produrrà un nuovo shuffle ogni volta che il programma viene eseguito. Mi piace anche questo approccio per la semplicità del codice.

Funziona perché tutto ciò di cui abbiamo bisogno std::shuffleè un UniformRandomBitGenerator, i cui requisiti std::random_devicesoddisfano.

Nota: se si mescola ripetutamente, potrebbe essere meglio memorizzare random_devicein una variabile locale:

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

2
Cosa aggiunge questo che non faceva già parte della risposta accettata di 8 anni fa?
ChrisMM

1
Tutto quello che devi fare è leggere la risposta per scoprirlo ... Non c'è molto altro da dire che non sia già stato spiegato molto chiaramente sopra.
Apollys supporta Monica

1
La risposta accettata utilizza già la riproduzione casuale e dice di usare random_device...
ChrisMM

1
La vecchia risposta accettata potrebbe essere più approfondita. Tuttavia, questa è esattamente la risposta punta e inquadra su una riga che mi aspetterei quando cercavo su Google una domanda così semplice senza troppi indugi. +1
Ichthyo

2
Questo è sbagliato . random_deviceè progettato per essere chiamato una sola volta per seminare PRNG, per non essere chiamato più e più volte (il che potrebbe esaurire rapidamente l'entropia sottostante e farla passare a uno schema di generazione subottimale)
LF

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.