map


概要

mapはSTLコンテナの一つで連想配列のテンプレートクラスです。

連想配列

連想配列とはjavascriptやphp、perlなどで使われている特殊な配列です。
通常の配列との違いは数値以外の値(文字列)をインデックスに使用できることです。

// 連想配列 (javascriptコードなので、注意してください)
var condition = new Object();
condition['朝'] = '悪い';
condition['昼'] = 'やや悪い';
condition['晩'] = '若干悪い';

console.log("朝 => %s", condition['朝']);
console.log("昼 => %s", condition['昼']);
console.log("晩 => %s", condition['晩']);

出力結果:
	朝 => 悪い
	昼 => やや悪い
	晩 => 若干悪い

通常の配列は先頭のアドレスとインデックス番号のオフセットで要素を管理していますが、
連想配列は要素とその要素に紐付いた値を1つのペアとして扱っており、
紐付いた値を利用して要素を管理しています。
この要素に紐付いた値のことをキーと呼んでいます。

宣言

まず、mapを使用するには<map>をインクルードする必要があります。

#include <map>

次に宣言ですが、mapはテンプレートクラスなので、変数宣言時に<>を使用しますが、
vetorやlistとは違い、キーと要素の2つの型を指定しなければいけません。

// 書式例
std::map<キーの型, 要素の型> 変数名;

// 具体例
// キー:string、要素:string
std::map<std::string, std::string> str_list;

これでmapを使用することができます。

参照と代入

参照と代入の方法は要素の指定がキーになるだけで通常の配列と変わりません。
ただ、代入をした場合、指定したキーが存在しない場合だと、
新しい要素の追加として扱われます。

std::map<std::string, int> list;
list["rank_01"] = 1000; // キー:rank01に1000を保存
printf("キー => %s\n要素 => %d", "rank_01", list["rank_01"]);

実行結果
	キー => rank_01
	要素 => 1000

iterator、範囲for使用時

mapをiteratorや範囲forで要素を取得して値を参照する場合、
vectorやlistとはアクセス方法が異なります。

std::map<std::string, int> map;
map["key01"] = 100;
map["key02"] = 200;

printf("範囲for\n");
for (std::pair<const std::string, int>& data : map)
{
	printf("key => %s\n", data.first.c_str());
	printf("value => %d\n", data.second);
}

printf("\niterator\n");
for (auto itr = map.begin(); itr != map.end(); itr++)
{
	printf("key => %s\n", itr->first.c_str());
	printf("value => %d\n", itr->second);
}

実行結果:
	範囲for
	key => key01
	value => 100
	key => key02
	value => 200

	iterator
	key => key01
	value => 100
	key => key02
	value => 200

上のコードのようにmapを範囲forやiteratorで取得した要素の参照は
「firstでキー情報」「secondで値情報」の参照となります。

要素追加

要素の追加は代入の項目で説明している「直接キー指定して値を代入する方法」と、
「insert関数を使用する方法」があります。
直接キー指定して値を代入する方法は既に説明しているので省略します。

insert関数

insert関数は戻り値と引数が異なるパターンが数多く用意されていますので、
今回はそのうちの3種類を紹介します。

insertその①
内容
マップに指定したキーと要素を追加する
戻り値の型 説明
pair<iterator, bool> 追加結果のpair
 firstがmapのiterator
 secondが追加結果
引数の型 説明
map<キーの型, 要素の型>::value_type(キー, 値) 追加するキーと値(value_typeを指定する)
std::map<int, int>map; // 挿入前のサイズ出力 printf("map size => %d\n", map.size()); // 挿入 map.insert(std::map<int, int>::value_type(10, 100)); // 挿入内容出力 printf("map[10] => %d\n", map[10]); // 挿入後のサイズ出力 printf("map size => %d\n", map.size()); 出力結果: map size => 0 map[10] => 100 map size => 1
insertその②
内容
マップに指定したキーと要素を追加する
戻り値の型 説明
pair<iterator, bool> 追加結果のpair
 firstがmapのiterator
 secondが追加結果
引数の型 説明
pair<キーの型, 要素の型>(キー, 値) 追加するキーと値(pairで指定する)
std::map<std::string, int>map; // 挿入前のサイズ出力 printf("map size => %d\n", map.size()); // 挿入 map.insert(std::pair<std::string, int>("キー", 30)); // 挿入内容出力 printf("map[\"キー\"] => %d\n", map["キー"]); // 挿入後のサイズ出力 printf("map size => %d\n", map.size()); 出力結果: map size => 0 map["キー"] => 30 map size => 1
insertその③
内容
マップに指定したキーと要素を追加する
戻り値の型 説明
pair<iterator, bool> 追加結果のpair
 firstがmapのiterator
 secondが追加結果
引数の型 説明
make_pair(キー, 値) 追加するキーと値(make_pairでpairを作成する)
std::map<std::string, std::string>map; // 挿入前のサイズ出力 printf("map count => %d\n", map.size()); // 挿入 map.insert(std::make_pair("キー", "値")); // 挿入内容出力 printf("map_[\"キー\"] => %s\n", map["キー"].c_str()); // 挿入後のサイズ出力 printf("map count => %d\n", map.size()); 出力結果: map size => 0 map["キー"] => 値 map size => 1 引数は異なりますが、全て追加するキーと値を指定することで、 要素が挿入される結果は変わりません。

要素の順番

mapの要素はキーの値を元にして自動的に昇順でソートされます。

std::map<std::string, std::string>map;

map.insert(std::make_pair("b", "い"));
map.insert(std::make_pair("e", "お"));
map.insert(std::make_pair("a", "あ"));
map.insert(std::make_pair("d", "え"));
map.insert(std::make_pair("c", "う"));

for (auto itr = map.begin(); itr != map.end(); itr++)
{
	printf("key = %s, ", itr->first.c_str());
	printf("val = %s\n", itr->second.c_str());
}

出力結果:
	key = a, val = あ
	key = b, val = い
	key = c, val = う
	key = d, val = え
	key = e, val = お

上のコードではキーを追加した順番は「b => e => a => d => c」ですが、
beginから要素を出力した結果は「a => b => c => d => e」になっています。
このようにmapは追加や削除が行われて要素の数が変更されたら
自動的にソートがかかるようになっています。

要素削除

要素の削除はerase関数を使用して行います。
説明する方法は「キー指定」「イテレータ指定」「イテレータ指定(範囲)」です。

キー指定

キー指定は引数にキーを指定することで指定した要素が削除できます。

eraseその①
内容
指定されたキーをマップから削除する
戻り値の型 説明
size_t 削除後のmapの要素数
引数の型 説明
キー(templateなので型は不定) 削除予定のキー
std::map<std::string, std::string>map; map.insert(std::make_pair("a", "あ")); map.insert(std::make_pair("b", "い")); map.insert(std::make_pair("c", "う")); map.insert(std::make_pair("d", "え")); map.insert(std::make_pair("e", "お")); // キー:aを指定 map.erase("a"); for (auto itr = map.begin(); itr != map.end(); itr++) { printf("key = %s, ", itr->first.c_str()); printf("val = %s\n", itr->second.c_str()); } 出力結果: key = b, val = い key = c, val = う key = d, val = え key = e, val = お 上のコードでmapから「キー:a」を削除しており、 その結果がきちんと反映されていることが分かります。

イテレータ指定

イテレータ指定は引数に削除したい要素のイテレータを指定することで、
指定した要素が削除されます。

eraseその②
内容
指定されたmapのiterator要素を削除する
戻り値の型 説明
mapのiterator 削除後が完了したmapの次の要素のiterator
引数の型 説明
mapのiterator 削除する要素のiterator
std::map<std::string, std::string>map; map.insert(std::make_pair("a", "あ")); map.insert(std::make_pair("b", "い")); map.insert(std::make_pair("c", "う")); map.insert(std::make_pair("d", "え")); map.insert(std::make_pair("e", "お")); for (auto itr = map.begin(); itr != map.end(); itr++) { if (strcmp(itr->second.c_str(), "え") == 0) { // えの要素を削除 map.erase(itr); break; } } for (auto itr = map.begin(); itr != map.end(); itr++) { printf("key = %s, ", itr->first.c_str()); printf("val = %s\n", itr->second.c_str()); } 出力結果: key = a, val = あ key = b, val = い key = c, val = う key = e, val = お

イテレータ(範囲指定)

引数に削除したい範囲を指定することで、その範囲の要素を削除できます。

eraseその③
内容
指定されたmapのiterator要素から
iterator要素までの範囲を削除する
戻り値の型 説明
mapのiterator 削除後が完了したmapの次の要素のiterator
引数の型 説明
mapのiterator 削除する要素のiterator(範囲の先頭)
mapのiterator 削除する要素のiterator(範囲の末尾)
std::map<std::string, std::string>map; map.insert(std::make_pair("a", "あ")); map.insert(std::make_pair("b", "い")); map.insert(std::make_pair("c", "う")); map.insert(std::make_pair("d", "え")); map.insert(std::make_pair("e", "お")); for (auto itr = map.begin(); itr != map.end(); itr++) { if (strcmp(itr->second.c_str(), "う") == 0) { // "う"~最後の要素までを削除 map.erase(itr, map.end()); break; } } for (auto itr = map.begin(); itr != map.end(); itr++) { printf("key = %s, ", itr->first.c_str()); printf("val = %s\n", itr->second.c_str()); } 出力結果: key = a, val = あ key = b, val = い eraseは要素削除関数ですが、引数の内容を変えることで様々な削除が可能です。

全要素削除

mapの要素を全て削除したい場合は全てをeraseするのではなく、
「clear関数」を使用すれば全て削除されます。

clear
内容
mapの要素を全て削除する
戻り値の型 説明
map 要素が削除されたmap
std::map<std::string, std::string>map; map.insert(std::make_pair("a", "あ")); map.insert(std::make_pair("b", "い")); map.insert(std::make_pair("c", "う")); map.insert(std::make_pair("d", "え")); map.insert(std::make_pair("e", "お")); map.clear(); printf("mapの要素数 => %d\n", map.size()); 実行結果 mapの要素数 => 0 clearを使ったことで、insertされていた要素の全てが削除されて 要素の数が0になりました。

unordered_map

unordered_mapというもう一つのmapがあります。
この二つのmapは同じやり方で参照、挿入、削除が行えますが、
通常のmapは探索木、unordered_mapはハッシュで管理されています。

違い

この管理の違いで二つのmapにどのような差異が生まれるかというと
mapは探索木によるソートが行われ、unordered_mapはソートが行われません。

// ソートの証明
std::map<std::string, std::string>map;

map.insert(std::make_pair("b", "い"));
map.insert(std::make_pair("a", "あ"));
map.insert(std::make_pair("e", "お"));
map.insert(std::make_pair("d", "え"));
map.insert(std::make_pair("c", "う"));

printf("mapの出力\n");
for (std::pair<const std::string, std::string>& data : map)
{
	printf("key => %s", data.first.c_str());
	printf(" value => %s\n", data.second.c_str());
}

std::unordered_map<std::string, std::string>unordered_map;
unordered_map.insert(std::make_pair("c", "う"));
unordered_map.insert(std::make_pair("b", "い"));
unordered_map.insert(std::make_pair("e", "お"));
unordered_map.insert(std::make_pair("d", "え"));
unordered_map.insert(std::make_pair("a", "あ"));

printf("\nunordered_mapの出力\n");
for (std::pair<const std::string, std::string>& data : unordered_map)
{
	printf("key => %s", data.first.c_str());
	printf(" value => %s\n", data.second.c_str());
}

実行結果:
	mapの出力
	key => a value => あ
	key => b value => い
	key => c value => う
	key => d value => え
	key => e value => お

	unordered_mapの出力
	key => c value => う
	key => b value => い
	key => e value => お
	key => d value => え
	key => a value => あ

上記のようにmapはソートがされており、nunordered_mapはソートされていません。
これにより、二つのmapの「速度」に違いが発生します。
ソートが行われないunordered_mapの方がmapよりも高速に動きます// 速度検証
#include <stdio.h>
#include <string>
#include <map>
#include <unordered_map>
#include <time.h>
#include <windows.h>

int main()
{
	srand((unsigned)time(nullptr));
	LARGE_INTEGER f;
	if (!QueryPerformanceFrequency(&f))
	{
		return 0;
	}

	LARGE_INTEGER s, e;

	std::map<int, int>map;

	QueryPerformanceCounter(&s);
	for (int i = 0; i < 100000; i++)
	{
		map.insert(std::make_pair(rand(), rand()));
	}
	QueryPerformanceCounter(&e);

	double t = (double)(e.QuadPart - s.QuadPart) / f.QuadPart;
	printf("time = %fsec\n", t);

	std::unordered_map<int, int>unordered_map;

	QueryPerformanceCounter(&s);
	for (int i = 0; i < 100000; i++)
	{
		unordered_map.insert(std::make_pair(rand(), rand()));
	}
	QueryPerformanceCounter(&e);

	t = (double)(e.QuadPart - s.QuadPart) / f.QuadPart;
	printf("time = %fsec\n", t);

	return 0;
}

実行結果:
	map time = 0.484407sec
	unordered_map time = 0.226817sec

上のコードの検証の結果、unordered_mapの方が通常のmapよりも
倍近い速さで動作しました。
このことから、mapを使用する時は速度を求めるのか、自動ソートを求めるのかで
mapとunordered_mapを使い分けてください。