Як перетасувати std :: vector?


97

Я шукаю загальний, багаторазовий спосіб перетасувати a std::vectorв C ++. Ось як я зараз це роблю, але я думаю, що це не надто ефективно, оскільки йому потрібен проміжний масив і йому потрібно знати тип елемента (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(), доступні кращі API RNG (Boost.Random або 0x <random>).
Cat Plus Plus

Відповіді:


201

Починаючи з С ++ 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
Ви також можете підключити власний генератор випадкових чисел як третій аргумент std::random_shuffle.
Alexandre C.

19
+1 - Зверніть увагу, що це може давати однаковий результат при кожному запуску програми. Ви можете додати власний генератор випадкових чисел (який можна засіяти із зовнішнього джерела) як додатковий аргумент, std::random_shuffleякщо це проблема.
Манкарс

4
@ Gob00st: він буде генерувати однаковий результат для кожного екземпляра програми, а не кожного виклику random_shuffle. Така поведінка є нормальною та передбачуваною.
user703016

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

4
@ ParkYoung-Bae Дякую, я щойно дізнався . Це справді незручно, коли відповіді SO не містять інформацію про включення, оскільки вони знаходяться у верхній частині результатів пошуку Google.
Томаш Зато - відновити Моніку

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

Приклад @ miracle173 покращено
Мехмет Фіде

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: Дякую, Фред. Не знав цього. Я звик весь час користуватися srand.

6
Ймовірно, вам слід видалити цю відповідь, оскільки вона є неправильною і - що ще гірше - вона видається правильною і дійсно правильною в деяких реалізаціях, але не у всіх, що робить цю пораду дуже небезпечною.
Томас Боніні

2
Як @Fred пояснював вище, для random_shuffleвикористання випадкових чисел визначається реалізація. Це означає, що для вашої реалізації вона використовує rand()(і, отже, srand () працює), але для моєї вона може використовувати щось зовсім інше, це означає, що при моїй реалізації навіть при srand кожен раз, коли я запускаю програму, я отримуватиму однакові результати.
Thomas Bonini

2
@Code: як ми вже обговорювали, він працює не у всіх реалізаціях. Той факт, що ви можете створити власний номер, не згадується у вашій відповіді і не пов'язаний з цим обговоренням у будь-якому випадку. Я відчуваю, ніби ми йдемо колами: S
Thomas Bonini

2

Якщо ви використовуєте 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?
Чак Уолборн,

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
Ічтіо,

2
Це неправильно . random_deviceпризначений для виклику лише один раз для засівання PRNG, а не для виклику знову і знову (що може швидко вичерпати основну ентропію та призвести до її переходу на неоптимальну схему генерації)
LF

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.