วิธีการสุ่ม std :: vector?


99

ฉันกำลังมองหาวิธีทั่วไปที่ใช้ซ้ำได้ในการสลับไฟล์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);
}

ไม่ look up fisher-yates ....
Mitch Wheat

4
พยายามอย่าใช้rand()มี RNG API ที่ดีกว่าให้ใช้งาน (Boost.Random หรือ 0x <random>)
Cat Plus Plus

คำตอบ:


208

ตั้งแต่ 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
std::random_shuffleนอกจากนี้คุณยังสามารถเสียบเครื่องกำเนิดไฟฟ้าจำนวนสุ่มที่กำหนดเองเป็นอาร์กิวเมนต์ที่สามของ
Alexandre C.

20
+1 - โปรดทราบว่าสิ่งนี้อาจให้ผลลัพธ์ที่เหมือนกันทุกครั้งที่รันโปรแกรม คุณสามารถเพิ่มตัวสร้างตัวเลขสุ่มแบบกำหนดเอง (ซึ่งสามารถเริ่มต้นจากแหล่งภายนอก) เป็นอาร์กิวเมนต์เพิ่มเติมstd::random_shuffleหากนี่เป็นปัญหา
Mankarse

4
@ Gob00st: random_shuffleมันจะสร้างผลเดียวกันเช่นทุกโปรแกรมไม่ได้ทุกการเรียกร้องให้ พฤติกรรมนี้เป็นเรื่องปกติและตั้งใจ
user703016

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

4
@ ParkYoung-แบ้ขอบคุณฉันเพิ่งค้นพบ มันไม่สะดวกจริงๆเมื่อคำตอบ SO ไม่มีข้อมูลเนื่องจากอยู่ด้านบนของผลการค้นหาของ Google
Tomáš Zato - คืนสถานะ Monica

11

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 ปรับปรุงตัวอย่าง
Mehmet Fide

2
ทำไมการใช้นาฬิการะบบแปลก ๆ สำหรับเมล็ดพันธุ์แทนที่จะใช้เพียงแค่std::random_device?
Chuck Walbourn

6

นอกจากสิ่งที่ @Cicada พูดแล้วคุณควรจะเพาะเมล็ดก่อน

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

ความคิดเห็นของ Per @ FredLarson:

แหล่งที่มาของการสุ่มสำหรับ random_shuffle () เวอร์ชันนี้มีการกำหนดการใช้งานดังนั้นจึงไม่สามารถใช้ rand () ได้เลย จากนั้น srand () จะไม่มีผล

ดังนั้น YMMV


11
จริงๆแล้วแหล่งที่มาของการสุ่มสำหรับเวอร์ชันนี้random_shuffle()มีการกำหนดการนำไปใช้งานดังนั้นจึงอาจไม่ใช้rand()เลย แล้วsrand()จะไม่มีผล ฉันเคยเจอเรื่องนั้นมาก่อน
Fred Larson

@ เฟรด: ขอบคุณเฟรด ไม่ทราบว่า. ฉันเคยชินกับการใช้ srand มาตลอด

6
คุณควรจะลบคำตอบนี้เนื่องจากมันไม่ถูกต้องและที่แย่กว่านั้นคือดูเหมือนว่าถูกต้องและถูกต้องในการใช้งานบางอย่าง แต่ไม่ใช่ทั้งหมดทำให้คำแนะนำนี้อันตรายมาก
Thomas Bonini

2
ดังที่ @Fred อธิบายไว้ข้างต้นสิ่งที่random_shuffleใช้ในการสร้างตัวเลขสุ่มคือการกำหนดการนำไปใช้งาน ซึ่งหมายความว่าในการใช้งานของคุณจะใช้rand()(และด้วยเหตุนี้ srand () จึงใช้งานได้) แต่สำหรับฉันแล้วมันสามารถใช้สิ่งที่แตกต่างไปจากเดิมโดยสิ้นเชิงซึ่งหมายความว่าในการใช้งานของฉันแม้จะใช้ srand ทุกครั้งที่ฉันรันโปรแกรมฉันก็จะได้ผลลัพธ์เหมือนกัน
Thomas Bonini

2
@Code: อย่างที่เราคุยกันมันใช้ไม่ได้ในการใช้งานทั้งหมด ความจริงที่ว่าคุณสามารถจัดหาการสร้างหมายเลขของคุณเองไม่ได้กล่าวถึงในคำตอบของคุณและไม่เกี่ยวข้องกับการสนทนานี้ไม่ว่าในกรณีใด ๆ ฉันรู้สึกเหมือนอยู่ในแวดวง: S
Thomas Bonini

2

หากคุณใช้บูสต์คุณสามารถใช้คลาสนี้ได้ ( 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ล่ะ?
Chuck Walbourn

1

มันอาจจะง่ายกว่านี้สามารถหลีกเลี่ยงการเพาะเมล็ดได้ทั้งหมด:

#include <algorithm>
#include <random>

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

ซึ่งจะทำให้เกิดการสุ่มใหม่ทุกครั้งที่เรียกใช้โปรแกรม ฉันชอบแนวทางนี้ด้วยเนื่องจากความเรียบง่ายของโค้ด

สิ่งนี้ได้ผลเพราะสิ่งที่เราต้องการstd::shuffleคือ a UniformRandomBitGeneratorซึ่งstd::random_deviceตรงตามข้อกำหนด

หมายเหตุ: หากสับซ้ำ ๆ กันอาจเป็นการดีกว่าที่จะจัดเก็บrandom_deviceตัวแปรในเครื่อง:

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

2
สิ่งนี้เพิ่มอะไรที่ยังไม่ได้เป็นส่วนหนึ่งของคำตอบที่ยอมรับเมื่อ 8 ปีก่อน
ChrisMM

1
สิ่งที่คุณต้องทำคืออ่านคำตอบเพื่อดูว่า ... ไม่มีอะไรจะพูดอีกมากที่ยังไม่ได้อธิบายไว้อย่างชัดเจนข้างต้น
Apollys สนับสนุน Monica

1
คำตอบที่ยอมรับนั้นใช้การสุ่มแล้วและบอกว่าให้ใช้random_device...
ChrisMM

1
คำตอบเดิมที่ได้รับการยอมรับอาจมีรายละเอียดเชิงลึกมากกว่า อย่างไรก็ตามนี่เป็นคำตอบแบบจุดและช็อตแบบบรรทัดเดียวที่ฉันคาดหวังเมื่อ googling สำหรับคำถามง่ายๆเช่นนี้โดยไม่ต้องกังวลใจมากนัก +1
Ichthyo

2
นี้เป็นธรรม random_deviceได้รับการออกแบบให้เรียกเพียงครั้งเดียวเพื่อเพาะเมล็ด PRNG ไม่ให้ถูกเรียกซ้ำแล้วซ้ำอีก (ซึ่งอาจทำให้เอนโทรปีที่อยู่ข้างใต้หมดลงอย่างรวดเร็วและทำให้เปลี่ยนไปใช้รูปแบบการสร้างที่ไม่เหมาะสม)
LF

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.