HOWTO · C++

C++ でのシャッフルベクター

この記事では、C++ でベクトルをシャッフルする方法を紹介します。

このページの内容

C++ の vector の要素をその場でランダムに並べ替えるには、vector の begin() と end() の反復子、および乱数エンジンを指定して std::shuffle を呼び出します。このアルゴリズムは指定した範囲内の要素を並べ替え、新しい vector は作成しません。疑似乱数の選択結果を再現したい場合はエンジンに固定シードを使い、実行ごとに通常は異なる順序にしたい場合は適切なソースからエンジンにシードを設定します。

std::shuffle で vector をシャッフルする

std::shuffle は <algorithm> ヘッダーで宣言されています。最初の2つの引数は半開区間の反復子範囲を指定します。最初の反復子は含める最初の要素を指し、2つ目の反復子は最後の要素の1つ後を指します。そのため、vector では values.begin() と values.end() を使うと全体が対象になります。3つ目の引数は UniformRandomBitGenerator で、一般には std::mt19937 などのエンジンを渡します。エンジンは並べ替え先を選ぶための乱数ビットを供給するもので、シャッフル済みの vector を返すコールバックではありません。

次の完全な例では固定シードを使うため、プログラムを実行するたびにエンジンは同じ状態から始まります。ただし、std::shuffle が特定の並び順を保証するわけではありません。結果は標準ライブラリの実装によって異なることがあるため、下の出力は保証された順序ではなく一例として扱ってください。

次の例を実行します。

#include <algorithm>
#include <cstddef>
#include <iostream>
#include <random>
#include <vector>

int main() {
    std::vector<int> values{12, 32, 43, 53, 23, 65, 84};
    std::mt19937 engine(42);

    std::shuffle(values.begin(), values.end(), engine);

    for (std::size_t i = 0; i < values.size(); ++i) {
        if (i != 0) {
            std::cout << ' ';
        }
        std::cout << values[i];
    }
    std::cout << '\n';
}

入力 vector は 12 32 43 53 23 65 84 です。提供された正常実行では、プログラムは次の順序を出力しました。

23 12 43 53 84 65 32

固定シードを使うと、同じ実装と実行環境では疑似乱数エンジンの列を再現できます。ただし、アルゴリズムが選ぶ順序は標準ライブラリ全体で共通の列として規定されていないため、すべての実装でシャッフル結果が同一になるとは限りません。実行ごとに変化させたい場合は、エンジンを一度シードしてからシャッフルに使い回してください。要素ごと、または呼び出しごとに新しいエンジンを作る方法は避けます。シードのソースには std::random_device がよく使われますが、その動作は実装と環境によって異なります。非決定性や暗号学的な乱数が保証されるとは説明できません。

標準のイテレーター関連ユーティリティを使い、範囲を std::begin(values) と std::end(values) で表すこともできます。この形式は、異なる種類のコンテナーや組み込み配列を受け取る汎用ヘルパーを書くときに便利です。vector 専用の呼び出しでは、メンバー反復子を使うと直接的で分かりやすくなります。どちらの形式でも、両端の反復子は同じコンテナーから取得し、並べ替えたい要素を範囲に含めてください。この処理は vector の順序をその場で変えるため、後続のコードが読み取ったり反復したりすると、シャッフル後の並びが使われます。

std::random_shuffle を使う旧式コードへの対応

std::random_shuffle は古い標準ライブラリのアルゴリズムで、既存の C++ コードに残っていることがあります。利用できるかどうかは、プロジェクトが選択している言語標準と使用するライブラリ実装によって異なります。そのため、保守時には名前が使えると決めつけず、これらの設定を確認してください。古いオーバーロードは実装定義の乱数ソースを使う場合があり、生成器を受け取るオーバーロードの要件も std::shuffle に渡すエンジンの要件とは異なります。

新しいコードや更新するコードでは、明示的な乱数エンジンを渡して std::shuffle を使うのがおすすめです。これにより乱数のソースが明確になり、シードで再現性を制御でき、古いインターフェースへの依存も避けられます。コードベースに std::random_shuffle があるからといって、すべてのコンパイラーモードで利用できるとは限りません。また、古い環境では必ず std::shuffle が使えないとも限りません。互換性はプロジェクトの標準ライブラリと言語標準の設定で決まります。旧式コードを残す必要がある場合は、そのプロジェクト内で互換性の方針を決め、実際のビルドに使うコンパイラー設定で確認してください。