Làm thế nào để xáo trộn một vector std ::?


97

Tôi đang tìm kiếm một cách chung, có thể sử dụng lại để xáo trộn một std::vectortrong C ++. Đây là cách tôi hiện đang làm, nhưng tôi nghĩ nó không hiệu quả lắm vì nó cần một mảng trung gian và nó cần biết loại mục (DeckCard trong ví dụ này):

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

nope. nhìn lên Fisher-Yates ....
Mitch Wheat

3
Cố gắng không sử dụng rand(), có sẵn các API RNG tốt hơn (Boost.Random hoặc 0x <random>).
Cat Plus Plus

Câu trả lời:


200

Từ C ++ 11 trở đi, bạn nên:

#include <algorithm>
#include <random>

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

Live example on Coliru

Đảm bảo sử dụng lại cùng một phiên bản của rngnhiều lần gọi std::shufflenếu bạn định tạo các hoán vị khác nhau mọi lúc!

Hơn nữa, nếu bạn muốn chương trình của mình tạo ra các chuỗi xáo trộn khác nhau mỗi khi nó được chạy, bạn có thể khởi tạo hàm tạo của công cụ ngẫu nhiên với đầu ra là std::random_device:

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

Đối với C ++ 98, bạn có thể sử dụng:

#include <algorithm>

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

8
Bạn cũng có thể cắm một trình tạo số ngẫu nhiên tùy chỉnh làm đối số thứ ba của std::random_shuffle.
Alexandre C.

19
+1 - Lưu ý rằng điều này có thể tạo ra một kết quả giống nhau trong mỗi lần chạy chương trình. Bạn có thể thêm trình tạo số ngẫu nhiên tùy chỉnh (có thể được tạo từ nguồn bên ngoài) làm đối số bổ sung để std::random_shufflexem đây có phải là vấn đề hay không.
Mankarse

4
@ Gob00st: nó sẽ tạo ra cùng một kết quả cho mọi trường hợp của chương trình, không phải mọi lệnh gọi tới random_shuffle. Hành vi này là bình thường và có mục đích.
user703016

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

4
@ ParkYoung-Bae Cảm ơn, tôi vừa phát hiện ra . Thực sự bất tiện khi câu trả lời SO không chứa thông tin bao gồm vì chúng nằm trên đầu kết quả tìm kiếm của google.
Tomáš Zato - Phục hồi Monica

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

một ví dụ xấu được sao chép từ cplusplus.com/reference/algorithm/shuffle . Làm thế nào để bạn thực hiện một cuộc gọi xáo trộn khác?
magic173

@ miracle173 dụ được cải thiện
Mehmet Fide

2
Tại sao lại sử dụng đồng hồ hệ thống cho một hạt giống thay vì chỉ sử dụng std::random_device?
Chuck Walbourn

6

Ngoài những gì @Cicada đã nói, bạn có thể nên gieo hạt trước,

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

Theo bình luận của @ FredLarson:

nguồn ngẫu nhiên cho phiên bản này của random_shuffle () được xác định thực thi, vì vậy nó có thể không sử dụng rand (). Sau đó, srand () sẽ không có hiệu lực.

Vì vậy, YMMV.


10
Trên thực tế, nguồn ngẫu nhiên cho phiên bản random_shuffle()này là việc triển khai được xác định, vì vậy nó có thể hoàn toàn không sử dụng rand(). Sau đó srand()sẽ không có hiệu lực. Tôi đã gặp điều đó trước đây.
Fred Larson

@Fred: Cảm ơn Fred. Không biết rằng. Tôi đã quen với việc sử dụng srand mọi lúc.

6
Bạn có thể nên xóa câu trả lời này vì nó sai và - thậm chí tệ hơn - nó có vẻ đúng và thực sự là đúng trong một số triển khai, nhưng không phải tất cả, làm cho lời khuyên này rất nguy hiểm.
Thomas Bonini

2
Như @Fred đã giải thích ở trên, những gì random_shufflesử dụng để tạo số ngẫu nhiên được định nghĩa triển khai. Điều này có nghĩa là trên triển khai của bạn, nó sử dụng rand()(và do đó srand () hoạt động) nhưng đối với tôi, nó có thể sử dụng một cái gì đó hoàn toàn khác, có nghĩa là trong quá trình triển khai của tôi ngay cả với srand mỗi khi tôi chạy chương trình, tôi sẽ nhận được kết quả giống nhau.
Thomas Bonini

2
@Code: như chúng ta đã thảo luận, nó không hoạt động trong tất cả các triển khai. Việc bạn có thể cung cấp số điện thoại của riêng mình không được đề cập trong câu trả lời của bạn và không liên quan đến cuộc thảo luận này trong mọi trường hợp. Tôi cảm thấy như chúng tôi đang đi trong vòng tròn: S
Thomas Bonini

2

Nếu bạn đang sử dụng boost, bạn có thể sử dụng lớp này ( debug_modeđược đặt thành false, nếu bạn muốn việc ngẫu nhiên hóa có thể dự đoán được giữa quá trình thực thi, bạn phải đặt nó thành 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_));
    }
};

Hơn bạn có thể kiểm tra nó với mã này:

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

Tại sao bạn lại sử dụng thời gian để tạo hạt giống cho máy phát điện std::random_device?
Chuck Walbourn

1

Nó thậm chí có thể đơn giản hơn, có thể tránh hoàn toàn việc gieo hạt:

#include <algorithm>
#include <random>

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

Điều này sẽ tạo ra một lần trộn mới mỗi khi chương trình được chạy. Tôi cũng thích cách tiếp cận này do sự đơn giản của mã.

Điều này hoạt động bởi vì tất cả những gì chúng ta cần std::shufflelà một UniformRandomBitGenerator, có các yêu cầu std::random_deviceđáp ứng.

Lưu ý: nếu xáo trộn nhiều lần, có thể tốt hơn nên lưu trữ random_devicetrong một biến cục bộ:

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

2
Điều này bổ sung điều gì mà đã không phải là một phần của câu trả lời được chấp nhận từ 8 năm trước?
ChrisMM

1
Tất cả những gì bạn phải làm là đọc câu trả lời để tìm ra ... Không còn nhiều điều cần nói mà vẫn chưa được giải thích rõ ràng ở trên.
Apollys hỗ trợ Monica

1
Câu trả lời được chấp nhận đã sử dụng shuffle, và nói đến việc sử dụng random_device...
ChrisMM

1
Câu trả lời cũ được chấp nhận có thể sâu hơn. Tuy nhiên, đây chính xác là câu trả lời ngắm và bắn một dòng mà tôi mong đợi khi lập googling cho một câu hỏi đơn giản như vậy mà không cần nhiều lời khuyên. +1
Ichthyo

2
Điều này là sai lầm . random_deviceđược thiết kế để chỉ được gọi một lần để tạo hạt giống PRNG, không được gọi lặp đi lặp lại (điều này có thể làm cạn kiệt entropy bên dưới một cách nhanh chóng và khiến nó chuyển sang sơ đồ tạo tối ưu phụ)
LF

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.