C++入門|連想配列とmap

番号ではなく名前で探す。mapを使えば、戦士名や技名から必要なデータをすぐに取り出せます。

STLには、vectorやlistのほかにも、データを便利に管理するためのクラスがいくつも用意されています。
その中でも、今回学ぶmapは、名前や文字列などを手がかりにして値を取り出せる、とても実用的なクラスです。

vectorでは、0番目、1番目、2番目のように、番号を使って要素へアクセスしました。
listでは、イテレータを使って、先頭から順番に要素をたどりました。

mapでは、番号ではなくキーを使って値へアクセスします。

たとえば、修行場にいる戦士の修行得点を管理したいとします。
vectorで管理する場合、0番目が誰、1番目が誰、2番目が誰なのかを別に覚えておく必要があります。

しかしmapを使えば、戦士名そのものをキーにできます。
リクという名前を指定すればリクの得点を取り出し、ソラという名前を指定すればソラの得点を取り出せます。

ドラゴンボール風にたとえるなら、mapは神殿の修行記録帳です。
戦士名を調べると、その戦士の修行得点や戦闘力がすぐに分かるような仕組みです。

このように、キーと値を組み合わせて管理する配列のような仕組みを、連想配列といいます。

用語意味ドラゴンボール風のイメージ
mapキーと値を組み合わせて管理するSTLのクラス修行記録帳
キー値を探すための手がかり戦士名、技名、流派名
キーに対応して保存されるデータ修行得点、戦闘力、技の威力
連想配列番号ではなくキーで値を取り出す配列のような仕組み名前で探せるデータ帳

mapとは何か

mapは、キーと値を1組にして管理するSTLのクラスです。

配列やvectorでは、要素へアクセスするときに番号を使います。

scores[0]
scores[1]
scores[2]

一方、mapでは、番号ではなくキーを使います。

trainingScore["リク"]
trainingScore["ソラ"]
trainingScore["レン"]

この書き方を見ると、どの戦士の得点を扱っているのかがすぐに分かります。

たとえば、trainingScore["リク"] は、リクというキーに対応する値を表します。
trainingScore["ソラ"] は、ソラというキーに対応する値を表します。

このように、mapでは意味のある名前を使ってデータへアクセスできます。

辞書を思い浮かべると、mapの考え方はとても分かりやすくなります。
辞書では、調べたい単語を探すと、その意味が分かります。

mapでも同じように、キーを指定すると、そのキーに対応する値が分かります。

辞書の考え方mapの考え方
単語を調べるキーを指定する
意味が分かる値を取り出す
単語と意味が対応するキーと値が対応する

ドラゴンボール風に考えると、戦士名を調べると修行得点が分かる記録帳です。
リクという戦士名を調べれば100点、ソラという戦士名を調べれば80点、レンという戦士名を調べれば76点、というように対応します。

図:連想配列とmapの基本イメージ

この図が示していること

この図では、mapがキーと値を対応付けて管理する仕組みを表しています。

リク、ソラ、レンという戦士名がキーです。
100点、80点、76点という修行得点が値です。

mapでは、番号ではなくキーを使って値を取り出します。
そのため、誰のデータを扱っているのかが分かりやすくなります。

vectorとmapの違い

vectorとmapは、どちらも複数のデータを管理できます。
しかし、データの取り出し方が大きく違います。

vectorは、番号で要素にアクセスします。

vector<int> scores;

scores.push_back(100);
scores.push_back(80);
scores.push_back(76);

cout << scores[0] << endl;

この場合、scores[0] は最初の要素を表します。
ただし、0番目が誰の点数なのかは、コードだけでは分かりにくいことがあります。

一方、mapではキーを使います。

map<string, int> trainingScore;

trainingScore["リク"] = 100;
trainingScore["ソラ"] = 80;
trainingScore["レン"] = 76;

cout << trainingScore["リク"] << endl;

この場合、trainingScore["リク"] はリクの修行得点を表します。
コードを読んだだけで、どの戦士の点数なのかが分かります。

比較項目vectormap
アクセス方法番号でアクセスするキーでアクセスする
scores[0]trainingScore["リク"]
向いている場面順番が重要なデータ名前と値を対応させたいデータ
イメージ番号付きの修行ボード名前で探せる修行記録帳

vectorは、順番に並んだデータを扱うときに便利です。
mapは、名前やIDなどのキーから値を取り出したいときに便利です。

mapを使う準備

mapを使うには、map用のヘッダーをインクルードします。

#include <map>

また、今回のサンプルではstring型も使うため、string用のヘッダーもインクルードします。

#include <string>

画面に表示するためにiostreamも使います。

#include <iostream>

今回のサンプルでは、標準名前空間を使えるようにします。

using namespace std;

mapの基本的な宣言は、次の形です。

map<キーの型, 値の型> 変数名;

たとえば、戦士名を文字列、修行得点を整数で管理するなら、次のように書きます。

map<string, int> trainingScore;

この宣言では、キーの型がstring、値の型がintになります。

つまり、文字列のキーを使って、整数の値を保存するmapを作っています。

宣言の部分意味
mapmapクラスを使う
stringキーの型
int値の型
trainingScoremapの変数名

mapを使ったサンプルプログラム

ここでは、戦士名と修行得点を対応付けるプログラムを作ります。

リク、ソラ、レンという3人の戦士に対して、それぞれ修行得点を登録します。
そのあと、戦士名をキーとして得点を取り出して表示します。

プロジェクト/ファイル名: Chap6_08/training_score_map.cpp

#include <iostream>
#include <string>
#include <map>

using namespace std;

int main(int argc, char** argv) {
    // 戦士名と修行得点を対応付けて管理するmap
    map<string, int> trainingScore;

    // 戦士名をキーにして修行得点を登録する
    trainingScore["リク"] = 100;
    trainingScore["ソラ"] = 80;
    trainingScore["レン"] = 76;

    // キーを指定して修行得点を取り出す
    cout << "リクの修行得点は" << trainingScore["リク"] << "点です。" << endl;
    cout << "ソラの修行得点は" << trainingScore["ソラ"] << "点です。" << endl;
    cout << "レンの修行得点は" << trainingScore["レン"] << "点です。" << endl;

    // プログラムが正常に終わったことを示す
    return 0;
}

実行結果

リクの修行得点は100点です。
ソラの修行得点は80点です。
レンの修行得点は76点です。

このプログラムでは、trainingScoreというmapを使っています。

map<string, int> trainingScore;

この1行で、戦士名と修行得点を対応付ける記録帳が作られます。

キーはstring型なので、リク、ソラ、レンのような文字列を指定できます。
値はint型なので、100、80、76のような整数を保存できます。

mapへ値を登録する

mapへ値を登録するときは、キーを指定して値を代入します。

trainingScore["リク"] = 100;
trainingScore["ソラ"] = 80;
trainingScore["レン"] = 76;

この3行によって、trainingScoreの中に次の対応が作られます。

キー
リク100
ソラ80
レン76

キーは、値を探すための名前です。
値は、キーに対応して保存されるデータです。

リクというキーには100が対応します。
ソラというキーには80が対応します。
レンというキーには76が対応します。

ドラゴンボール風に言えば、神殿の修行記録帳に、戦士名と得点を書き込んでいる状態です。

図:mapの宣言と値の登録

この図が示していること

この図では、mapの宣言と値の登録の流れを表しています。

map<string, int> trainingScore と宣言することで、文字列をキーにして整数を保存できるmapを作ります。
そのあと、trainingScore[リク] = 100 のように、キーと値の対応を登録します。

mapでは、キーと値を1組として管理することが大切です。

mapから値を取り出す

mapから値を取り出すときも、キーを指定します。

trainingScore["リク"]

この書き方で、リクに対応する値を取り出せます。
今回のサンプルでは、リクには100が登録されているため、trainingScore["リク"] の値は100になります。

表示するときは、次のように書けます。

cout << "リクの修行得点は" << trainingScore["リク"] << "点です。" << endl;

この1行によって、リクの修行得点は100点です。と表示されます。

同じように、ソラやレンの値も取り出せます。

cout << "ソラの修行得点は" << trainingScore["ソラ"] << "点です。" << endl;
cout << "レンの修行得点は" << trainingScore["レン"] << "点です。" << endl;

mapでは、キーを使うことで、目的の値へ直接アクセスできます。

配列のように0番目、1番目という番号を意識する必要がないため、データの意味が読み取りやすくなります。

mapの仕組みをイメージする

mapは、キーと値の対応表です。

今回のtrainingScoreは、次のような状態になっています。

trainingScore
リク → 100
ソラ → 80
レン → 76

この対応表があるため、キーを指定すると値を取り出せます。

リクを指定すれば100が取り出せます。
ソラを指定すれば80が取り出せます。
レンを指定すれば76が取り出せます。

この考え方は、実際のプログラムでもとてもよく使われます。
たとえば、技名から消費気力を調べたり、アイテム名から所持数を調べたり、流派名から修行回数を調べたりできます。

管理したい情報キー
戦士の修行得点戦士名得点
技の消費気力技名消費気力
アイテムの所持数アイテム名個数
流派ごとの修行回数流派名回数

mapは、何かの名前と、それに対応するデータを結び付けたい場面に向いています。

mapで同じキーに代入した場合

mapでは、同じキーにもう一度値を代入すると、値が上書きされます。

たとえば、次のように書いたとします。

trainingScore["リク"] = 100;
trainingScore["リク"] = 120;

この場合、リクに対応する値は、最終的に120になります。

最初に100を登録していますが、そのあと同じキーで120を代入しているため、値が更新されます。

これは、修行得点を更新する場面では便利です。
リクが追加修行を行い、得点が100点から120点へ上がった、というように考えると自然です。

処理リクの値
trainingScore["リク"] = 100100
trainingScore["リク"] = 120120

同じキーは、同じ記録欄を表します。
そのため、同じキーに値を入れ直すと、その記録欄の内容が変わります。

mapの主なメンバ関数

mapには、データを管理するための便利なメンバ関数があります。

メンバ関数役割
clearすべての要素を消去する
emptymapが空かどうかを調べる
erase指定したキーの要素を削除する
size要素数を取得する
find指定したキーを探す

clearを使うと、mapの中身をすべて消去できます。
emptyを使うと、mapが空かどうかを確認できます。
eraseを使うと、指定したキーの要素を削除できます。
sizeを使うと、mapに登録されている要素数を取得できます。
findを使うと、指定したキーがmapの中にあるかを探せます。

たとえば、trainingScoreの要素数を調べたい場合は、sizeを使います。

cout << trainingScore.size() << endl;

ソラの記録を削除したい場合は、eraseを使います。

trainingScore.erase("ソラ");

すべての記録を消したい場合は、clearを使います。

trainingScore.clear();

mapが空かどうかを調べたい場合は、emptyを使います。

if (trainingScore.empty()) {
    cout << "修行記録は空です。" << endl;
}

findでキーを探す

mapでは、キーを指定して値を取り出せます。
ただし、指定したキーが本当に存在するかを確認したい場合もあります。

そのようなときに使えるのがfindです。

findは、指定したキーを探し、見つかった場合はその位置を表すイテレータを返します。
見つからなかった場合は、endを返します。

map<string, int>::iterator itr;

itr = trainingScore.find("レン");

if (itr != trainingScore.end()) {
    cout << "レンの修行得点は" << itr->second << "点です。" << endl;
}
else {
    cout << "レンの修行記録は見つかりませんでした。" << endl;
}

mapのイテレータでは、キーをfirst、値をsecondで扱います。

itr->first
itr->second

itr->first はキーを表します。
itr->second は値を表します。

今回の場合、レンが見つかれば、itr->first はレン、itr->second は76になります。

findを使うと、キーが存在するかどうかを確認してから値を扱えるため、より安全にmapを使えます。

図:mapの主な操作

この図が示していること

この図では、mapでよく使う主な操作を表しています。

sizeで要素数を確認し、findでキーを探し、eraseで指定キーの要素を削除できます。
emptyで空かどうかを調べ、clearですべての要素を消去できます。

mapは、キーと値の対応を登録するだけでなく、登録済みのデータを調べたり削除したりする機能も持っています。

vector、list、mapの使い分け

ここまで学んできたSTLのコンテナには、それぞれ得意なことがあります。

vectorは、番号で要素へアクセスしたいときに向いています。
listは、先頭や途中への挿入、要素の削除など、隊列を組み替えたいときに向いています。
mapは、キーから値を探したいときに向いています。

コンテナ得意なことドラゴンボール風のイメージ
vector番号で要素を管理する番号付きの修行データボード
list並びの変更や削除を行う戦士の隊列
mapキーから値を探す名前で探せる修行記録帳

たとえば、入力された順番に戦闘力を保存するならvectorが分かりやすいです。
戦士の並びを途中で入れ替えるならlistが考えやすいです。
戦士名から修行得点を取り出すならmapが向いています。

STLでは、どのコンテナを使うかによって、プログラムの考え方が変わります。
大切なのは、データをあとでどのように使いたいかを考えて選ぶことです。

連想配列としてのmapで身につけたい感覚

mapは、番号ではなくキーで値を取り出すためのSTLコンテナです。

vectorのように0番目、1番目、2番目という番号で管理するのではなく、リク、ソラ、レンのような名前で値を探せます。

今回のサンプルでは、戦士名をキーにして、修行得点を値として保存しました。

リク → 100
ソラ → 80
レン → 76

このような対応関係を作れることが、mapの大きな特徴です。

ドラゴンボール風に言えば、mapは神殿の修行記録帳です。
戦士名を調べると、その戦士の得点がすぐに分かります。
技名を調べると、その技の威力や消費気力が分かるようにもできます。

mapを使えるようになると、名前と値を結び付けるデータ管理がとても書きやすくなります。

戦士名と点数、技名と威力、アイテム名と所持数、流派名と修行回数のように、何かと何かを対応させたいときはmapが役立ちます。

C++でデータを扱うとき、順番で管理するだけではなく、意味のあるキーで管理できるようになると、プログラムの表現力が大きく広がります。