HOWTO · C++
C++ で HashMap を使用する
このチュートリアルでは、C++ の `std::map` と `std::unordered_map` について説明し、ハッシュマップを適切に使う方法を紹介します。また、それぞれの主な違いと使い分けも解説します。
このページの内容
C++ でハッシュマップを使うには std::unordered_map を使用します。std::map は、キーを順序付けて保持する関連するキーと値のコンテナです。どちらも連想コンテナであり、各キーは一意で、対応する値を識別します。異なるキーが同じ値を持つことはできます。キーによる検索が必要な場合はどちらも使えます。順序どおりの走査が必要かどうかで選びましょう。
標準規格は std::map の具体的な内部実装を定めていません。外から見える順序はキーの比較方法に従い、既定の比較関数では昇順になります。一方、std::unordered_map はハッシュ関数とバケットを使い、反復順序は規定されていません。ある実行でたまたま見えた順序に依存しないでください。
C++ のハッシュマップの仕組み
<map> または <unordered_map> をインクルードし、キーと値の型を宣言します。たとえば std::map<int, std::string> のように指定します。insert はキーがまだ存在しない場合にのみキーと値の組を追加します。operator[] による代入では、必要に応じて値初期化された値を挿入してから代入します。int などのスカラー型では、値初期化によって代入前にゼロになります。既存のキーに代入すると、その値が更新され、重複キーは作られません。値を使う前にキーの有無を確認したい場合は find を使います。at でもキーを検索できますが、キーが見つからない場合は std::out_of_range を報告します。
提供された実行記録では、元の例がどちらも正常に実行されています。以下の std::map の出力は記録された出力です。後半の非順序コンテナの例には走査ループが追加されているため、変更後の例について特定の出力順序は示していません。
順序付きキーには std::map を使う
キーを比較関数の順序で走査したい場合や、指定した値以上の最初のキーを見つけるなどの順序付き操作が必要な場合は、std::map を使います。検索、挿入、削除にかかる時間は、要素数を n とすると対数時間の O(log n) です。この予測しやすい計算量と順序付き走査は、レポート、範囲検索、ソート済み一覧に役立ちます。標準規格が保証するのは順序の動作であり、特定の木構造による実装ではありません。
次の例を実行します。
このセクションのコードと表示されるラベルや出力は英語のままです。
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<int, std::string> players;
players.emplace(2, "Lin Dan");
players.emplace(1, "Chen Long");
const auto found = players.find(1);
if (found != players.end()) {
std::cout << found->first << ": " << found->second << '\n';
}
for (const auto& entry : players) {
std::cout << entry.first << ": " << entry.second << '\n';
}
}
1: Chen Long
1: Chen Long
2: Lin Dan
検索結果としてキー 1 が出力されます。ループは比較関数の順序でキーを走査するため、挿入順に関係なく 1 が 2 より先になります。
ハッシュベースの検索には std::unordered_map を使う
キー検索を優先し、ソート済みの走査が不要な場合は std::unordered_map を選びます。検索、挿入、削除は平均 O(1) ですが、多数のキーが衝突する場合など、最悪時には O(n) かかることがあります。再ハッシュによって反復順序が変わり、イテレーターが無効になる場合もあります。このコンテナにはハッシュと等価性の要件があります。等価なキーからは同じハッシュ値が生成されなければなりません。
次の例を実行します。
このセクションのコードと表示されるラベルや出力は英語のままです。
#include <iostream>
#include <string>
#include <unordered_map>
int main() {
std::unordered_map<std::string, int> scores;
scores["Ada"] = 8;
scores["Lin"] = 11;
const auto found = scores.find("Ada");
if (found != scores.end()) {
std::cout << found->first << ": " << found->second << '\n';
}
for (const auto& entry : scores) {
std::cout << entry.first << ": " << entry.second << '\n';
}
std::cout << "Players: " << scores.size() << '\n';
}
範囲ベースのループは scores 内のすべてのエントリーを走査します。エントリーの順序は任意であり、標準規格もこの例も特定の順序を保証しません。表示順が必要な場合は、キーを別のリストに集めてソートしてください。
std::map と std::unordered_map の選び方
| 要件 | std::map |
std::unordered_map |
|---|---|---|
| 走査 | キーは比較関数の順序に従う | 反復順序は保証されない |
| 検索、挿入、削除 | O(log n) |
平均 O(1)、最悪時 O(n) |
| 適した用途 | ソート済みの出力、順序付き範囲、または予測しやすい対数時間の操作が必要 | キーによる検索が必要で、順序付き走査は不要 |
| キーの要件 | 比較関数による狭義弱順序 | 相互に整合するハッシュ関数と等価比較 |
単語の出現頻度を数える場合や、順序が関係しないキャッシュでは、std::unordered_map が自然な選択になることがよくあります。ソート済みの出力やキーによる範囲検索には std::map を使います。非順序コンテナの検索性能が用途に合っていても、表示順を整えたい場合は、反復順序を前提にせず、出力前にキーを集めてソートしてください。選択では平均速度だけでなく必要な動作も考慮します。性能が重要なアプリケーションでは、実際の用途を代表するデータでベンチマークを行ってください。
どちらのコンテナもキーと値を扱えますが、反復と性能に関する保証は異なります。キーを順序どおりに出力する走査には std::map を、順序が重要でないハッシュベースの検索には std::unordered_map を使います。順序付き走査の別の例については、C++ の中順走査を参照してください。