はじめに
C++で、カードゲームの山札を混ぜるように、vector などのコンテナの要素の並び順をランダムにしたい場合があります。
C++の標準ライブラリ <algorithm> には、このための std::shuffle という関数が用意されています。shuffle は、指定された範囲の要素を、第三引数で与えられた乱数生成器を使ってランダムに並べ替えます。
この記事では、<random> ライブラリの高品質な乱数エンジン「メルセンヌ・ツイスター (std::mt19937)」と shuffle を組み合わせて、コンテナの要素を効果的にシャッフルする方法を解説します。
【前提】C++11とは?
C++11(シーピープラスいちいち)は、2011年に正式化されたC++言語のメジャーアップデート版です。この記事で紹介する <random> ライブラリや shuffle アルゴリズムはC++11で導入されたため、利用するにはC++11以降に対応したコンパイラが必要です。
shuffle を使ったサンプルコード
このコードは、vector に格納された 1 から 8 までの数値を、shuffle を使ってランダムに並べ替えます。
完成コード
#include <iostream>
#include <vector>
#include <random> // mt19937, random_device
#include <algorithm> // shuffle
using namespace std;
int main() {
vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8};
// 1. 乱数生成器を準備
// random_deviceで、実行ごとに異なるシード値を生成する
random_device rd;
// メルセンヌ・ツイスター法に基づく高品質な乱数エンジン
mt19937 generator(rd());
cout << "--- シャッフル前 ---" << endl;
for (int n : numbers) {
cout << n << " ";
}
cout << endl;
// 2. shuffleアルゴリズムを実行
shuffle(numbers.begin(), numbers.end(), generator);
cout << "\n--- シャッフル後 ---" << endl;
for (int n : numbers) {
cout << n << " ";
}
cout << endl;
return 0;
}
実行結果(毎回異なります)
--- シャッフル前 ---
1 2 3 4 5 6 7 8
--- シャッフル後 ---
5 1 8 3 7 2 6 4
コードの解説
1. 乱数生成器の準備
高品質で再現性のない(毎回結果が異なる)シャッフルを行うには、適切な乱数生成器の準備が重要です。
random_device rd;: OSが提供する非決定論的な乱数(ハードウェアノイズなど)を生成します。これは、乱数生成の「種(シード)」として使うのに最適です。mt19937 generator(rd());: メルセンヌ・ツイスターという、非常に高品質で周期の長い擬似乱数生成アルゴリズムです。random_deviceから得たシード値で初期化することで、プログラムを実行するたびに異なる乱数列を生成します。
2. shuffle(numbers.begin(), numbers.end(), generator);
これが、実際にシャッフルを行う核心部分です。
- 第1, 2引数: シャッフルしたい要素の範囲をイテレータで指定します。
- 第3引数: 乱数生成器を渡します。
shuffleは、この生成器が出力する乱数に基づいて、要素をランダムに入れ替えます。
古い rand() を使うべきではない理由
古いC言語スタイルの rand() 関数は、乱数の質が低く、予測可能であるため、現代のC++プログラミング、特に shuffle のようなアルゴリズムでの使用は推奨されません。常に <random> ライブラリの高品質なエンジンを使うようにしましょう。
まとめ
今回は、C++の std::shuffle と <random> ライブラリを使って、コンテナの要素を効果的にシャッフルする方法を解説しました。
<random>ヘッダーから、random_deviceとmt19937を使って高品質な乱数生成器を準備する。<algorithm>ヘッダーのshuffle関数に、範囲と乱数生成器を渡す。
この手順で、統計的に偏りの少ない、信頼性の高いシャッフルを簡単に行うことができます。
