PythonistaのためのC++記法入門: それC++ではどう書くの3 ~tuple / dict(map) / set~

[PythonistaのためのC++記法入門: それC++ではどう書くの2](https://qiita.com/nctp/items/bda68b81d124e4f47d9f)の続き。今回はPythonでも頻繁に利用する`tuple`や`dict`などをC++ではどう書くか見ていく。

# tuple / dict / set

## tuple

C++では要素数によって`pair`と`tuple`があるが、Pythonでは要素数に関わらず一律`tuple`として扱われる。Pythonの`tuple`はイミュータブル（変更不可）であるために不変のデータの格納に向いているとよく言われる。

### Pythonだとこう書いた

```python
# 要素数2のtuple
p = ("math", 4)  # ()はなくても可

# 要素へのアクセス
subject = p[0]
score = p[1]

# アンパックの基本
subject, score = p  # 代入する変数の数と要素数が一致していないといけない

# 要素数3のtuple
score = ("Bob", "math", 87)

# 特殊なアンパック - 値を捨てる
name, subject, _ = score  # 末尾の要素の87は（実質）捨てられる

# 特殊なアンパック - 値をまとめる
name, *misc = score  # miscはリストとなりscoreの第一要素以外がまとめて格納される
print(misc)  # ['math', 87] が出力される
```

### C++だとこう書く

前述の通りC++では組の要素数によって`pair`と`tuple`に分かれる。

## pair

2要素からなるデータ組。`pair<型1, 型2> 変数名(値1, 値2)`などで宣言する。
要素には`変数名.first`と`変数名.second`でアクセスできる。
既存の2つの値1と値2から`make_pair(値1, 値2)`を使って新たなpairを作ることもできる。
`tie(値1, 値2)`でPythonでいうアンパックが可能（代入先の変数の事前定義が必要）。

```cpp
// 宣言
pair<string, int> p("math", 4);

// 値へのアクセス
cout << p.first << p.second << endl;

// make_pairによるpairの生成
pair<string, int> result;  // 代入する場合は事前に代入先の変数宣言が必要
string person = "john";
int score = 120;
result = make_pair(person, score);

// tieの用法
pair<string, int> log("Dave", 3);
string name;
int count;
tie(name, count) = log;
cout << name << ": " << count << endl;
// Dave: 3 と出力される

// ignoreを使って値を捨てる。pythonで言うと_への代入
tie(name, ignore) = log;
```

## tuple

3要素以上からなるデータ組。使い方などはpairと同様。`tuple<型1, 型2, 型3, ...> 変数名(値1, 値2, 値3, ...)`などで宣言する。`make_pair`と同様に利用できる`make_tuple`も用意されている。
値のアクセスには`get<N>(変数名)`とする。このときインデックスには変数は使えない。
`tie`などもpairと同様に使える。

```cpp
// 宣言
tuple<string, string, int> score("Bob", "math", 87);

// 値へのアクセス
cout << get<0>(score) << " gets " << get<2>(score) << " pts in " << get<1>(score) << endl;
// "Bob gets 87 pts on math"と出力される

// make_tupleによるtupleの生成
string person = "john";
string subject = "English";
int point = 120;
tuple<string, string, int> result;  // 代入する場合は事前に代入先の変数宣言が必要
result = make_tuple(person, subject, point);
```

`pair`は後述の`map`に含まれる全`key`: `value`の組をまとめて処理するときに有用（Pythonでいうところの`for key, value in dictionary.items()`のようなことができる）。

#### まとめ

|  |  C++ | Python |
| --- | --- | --- |
| 宣言 / 定義 | tuple\<要素1の型, 要素2の型, 要素3の型, ...\> tpl(要素1, 要素2, 要素3, ...); | tpl = (要素1, 要素2, 要素3, ...) |
| 任意要素へのアクセス | auto elem = get\<i\>(tpl); | elem = tpl[i] |
| アンパック | tie(elem1, elem2, ...) = tpl; | elem1, elem2, ... = tpl |
| 既存オブジェクトからのtupleの生成 | make_tuple(要素1, 要素2, 要素3, ...); | tuple([要素1, 要素2, 要素3, ...]) |

## map

Pythonでいうところの`dict`である。`map`関数とは異なる。

### Pythonだとこう書いた

```python
# 定義
scores = {}  # 空のdict
scores = {"John": 85, "Bob": 98}  # 要素を指定して初期化

# 値の追加
scores["Chris"] = 67# このときscores = {'John': 87, 'Bob': 98, 'Chris': 67}

# 値の上書き
scores["John"] = 87  # Johnに対応するvalueが85から87に上書きされる

# 値へのアクセス
scores["John"]  # 87
scores.get("John")  # 87
scores["Joh"]  # KeyError: 'Joh'

# 値の削除
scores.pop("Chris")  # valueである67が出力される。 
# _ = scores.pop("Chris")  # 削除するvalueを出力させない場合
# del scores["Chris"]  # delを利用することも可
scores # {'John': 87, 'Bob': 98} が出力される

# 要素数チェック
s = len(scores)

# すべてのkey: valueを表示
for k, v in scores.items():
    print(f"{k} => {v}")
# John => 87
# Bob => 98
# Chris => 67 と出力される

# 全key: valueの削除
scores.clear()
```

Pythonの場合、通常の`dict`では存在しないkeyにアクセスしようとしてもエラーが返ってくるだけで値の追加は行われない（`collections.defaultdict`を用いることで後述のC++の`map`に近い挙動を実現することもできる）。
keyの登録順に関しては、Python3.7以降では登録順に並ぶが、必ず登録順を保持したいときは明示的に`collections.OrderedDict`を用いたほうが安全。

### C++だとこう書く

```cpp
// 宣言
map<string, int> scores;  // まだ空のmapだがkey: valueのデータ型のみ既定

// 値の追加
scores["John"] = 85;
scores["Bob"] = 98;

// map<string, int> scores = {{"John", 85}, {"Bob", 98}, }; として同じmapを作成することも可

scores["Chris"] = 67;

// 値の上書き
scores["John"] = 87;  // Johnに対応するvalueが85から87に上書きされる

// 値へのアクセス
scores["John"];
scores.at("John");
scores["Joh"];  // このkeyは存在しないが、scoresに新たに追加されてしまう
cout << scores["Joh"] << endl;  // 0 が出力される（valueのデータ型依存の初期値）

// 値の削除
scores.erase("Chris");  // eraseされた"Chris"は返り値とならない

// emplaceを用いた値の追加
scores.emplace("Chris", 67);

// keyの所属判定として要素数をカウントするcountが使える
if (scores.count("Eric")) {
  // 処理;
}
// 存在するkeyに対してcountを使うと1が返される
cout << scores.count("John") << endl;  // 1 が出力される

// 要素数チェック
int s = scores.size();

// すべてのkey: valueを表示
for (pair<string, int> p: scores) {
  string key = p.first;
  int value = p.second;
  cout << key << " => " << value << endl;
}
// Bob => 98
// Chris => 67
// Joh => 0
// John => 87 と出力される（keyが昇順に返される！）

// autoを用いた型推論で楽に書ける
for (auto p: scores) {
  auto k = p.first;
  auto v = p.second;
  cout << k << " => " << v << endl;
}

// 全key: valueの削除
scores.clear();
```

ほとんど同じ感覚で利用することができるが、C++では`map`に登録したkeyの順番が昇順になる（Pythonでは順番はソートされない）。
C++では`変数["key"]`で存在しないkeyにアクセスしようとすると新たにそのkeyが登録されてしまう。そのため`変数.at("key")`を使うほうが安全。
Pythonライクな値の追加`変数[key] = value;`以外にも`変数.emplace(key, value);`という方法で要素を追加できる。

## unordered_map

`key`の順番が保持されない`map`。`key`として`pair`や`tuple`などの**ハッシュ関数が定義されていない型を用いることができない**制約がある。

内部的にはハッシュテーブルで実装されており、Pythonの`dict`は実際はこちらに近い。

### C++だとこう書く

```cpp
// 宣言
unordered_map<string, int> u_scores;

// 値の追加はmapと同様
u_scores["John"] = 85;
u_scores["Bob"] = 98;
u_scores.emplace("Chris", 67);

// 値の上書き
u_scores["John"] = 87;  // Johnに対応するvalueが85から87に上書きされる

// 値へのアクセス
cout << u_scores["John"] << endl; // 87 が出力される
cout << u_scores.at("John") << endl; // 87 が出力される
u_scores["Joh"];  // このkeyは存在しないが、scoresに新たに追加されてしまう
cout << u_scores["Joh"] << endl;  // 0 が出力される（valueのデータ型依存の初期値）

// 値の削除
u_scores.erase("Chris");  // eraseされた"Chris"は返り値とならない

// 存在するkeyに対してcountを使うと1が返される
cout << u_scores.count("John") << endl;  // 1 が出力される

// 要素数チェック
cout <<  u_scores.size() << endl; // 3 が出力される

// すべてのkey: valueを表示
for (auto p: u_scores) {
  auto k = p.first;
  auto v = p.second;
  cout << k << " => " << v << endl;
}
// Joh => 0
// Bob => 98
// John => 87 が出力される（keyの順番はランダム）

// 全key: valueの削除
u_scores.clear();
```

#### まとめ

|  |  C++ | Python |
| --- | --- | --- |
| 宣言 / 定義 | (unordered_)map\<key型, valueの型\> dic; | dic = dict() |
| key / valueを追加(1) | dic[key] = value; | dic[key] = value |
| key / valueを追加(2) | dic.insert(make_pair(key, value)); | 同上 |
| keyに対応するvalueを取得(1) | auto v = dic[key]; [^1] | v = dic[key] [^2]|
| keyに対応するvalueを取得(2) | auto v = dic.at(key); [^3] | v = dic.get(key) [^4] |
| key / valueを削除(1) | dic.erase(key); | _ = dic.pop(key) |
| key / valueを削除(2) | 同上 | del dic[key] |
| keyの所属判定 | dic.count(key); | key in dic |
| key / valueペア数を取得 | dic.size(); | len(dic) |
| 他のmap/dictと合成[^5] | dic.merge(dic2); | dic.update(dic2) |

[^1]: `key`が存在しない場合、`map`にその`key`-`value`ペアが追加されてしまう。このとき`value`の値は型依存の初期値となる
[^2]: `key`が存在しない場合、`KeyError`例外が投げられる
[^3]: `key`が存在しない場合`out_of_range`例外を投げてくれる
[^4]: `key`が存在しない場合`None`を返す
[^5]:  C++の`map`の`merge(dic2)`では`merge`対象の`dic2`に同一の`key`がすでに存在している場合でもその`value`は*上書きされない*が、Pythonの`update(dic2)`では同一の`key`-`value`ペアが`dic2`に含まれていた場合、その`value`が*上書きされてしまう*ことに注意。

## set

PythonもC++も同じ感覚で使える。

### Pythonではこう書いた

```python
# 定義
S = set()  # 空の集合{}が作られる

# 値の追加
S.add(12)  # {12}
S.add(3)  # {3, 12}
S.add(900)  # {3, 12, 900}
S.add(10000)  # {3, 12, 900, 10000}


# 値の削除
S.pop()  # 900 が出力される。Sは{3, 12, 900}に更新
S.discard(3)  #  3は出力されない。Sは{12, 900}に更新
S.discard(300)  #  300はSに含まれていないため何も起こらない

# S.remove(3)  #  discardとは異なり、存在しない要素をremoveしようとするとKeyError
S.remove(12)  # Sは{900}に更新

# 値の所属判定
900 in S  # True

# 要素の再追加
S.update({3, 12})  # Sは{3, 12, 900}に更新

# 全要素を表示
for elem in S:
    print(elem)
# 3
# 900
# 12 が出力される（keyの順番はランダム）

# 要素数チェック
len(S)  # 3 が出力される

# setのクリア
S.clear()  # 要素がすべて削除され空のsetとなる
```

### C++ではこう書く

`set<型> 変数名;`で宣言。

```cpp
// 宣言
set<int> S;

// 値の追加
S.insert(12);  // {12}
S.insert(3);  // {3, 12}
S.insert(900);  // {3, 12, 900}
S.insert(10000);  // {3, 12, 900, 10000}


// 値の削除
S.erase(900);  // eraseされた900は返り値とならない。Sは{3, 12, 10000}に更新

// 値の所属判定
if (S.count(12)) {
  cout << "12 exists" << endl;
}  // 12 exists が出力される

// empty()で空であるかチェック可能
if (S.empty()) {
  cout << "S is empty." << endl;
}
else {
  cout << "S is not empty." << endl;
}

// 要素数チェック
cout << S.size() << endl;  // 3 が出力される

// setに含まれる全要素を出力
for (auto elem: S){
    cout << elem << endl;
}
// 3
// 12
// 10000 が出力される（keyが昇順に返される！）

// setのクリア
S.clear();
```

## unordered_set

`unordered_map`同様、順番が保持されない`unordered_set`がある。こちらもハッシュテーブルで実装されており、場合によっては通常の`set`よりも高速なアクセスが可能。

Pythonの`set`もこちらに近い。

### C++だとこう書く

```cpp
// 宣言
unordered_set<int> u_S;

// 値の追加
u_S.insert(12);  // {12}
u_S.insert(3);  // {3, 12}
u_S.insert(900);  // {3, 12, 900}
u_S.insert(10000);  // {3, 12, 900, 10000}


// 値の削除
u_S.erase(900);  // eraseされた900は返り値とならない。u_Sは{3, 12, 10000}に更新

// 値の所属判定
if (u_S.count(12)) {
  cout << "12 exists" << endl;
}  // 12 exists が出力される

// empty()で空であるかチェック可能
if (u_S.empty()) {
  cout << "u_S is empty." << endl;
}
else {
  cout << "u_S is not empty." << endl;
}

// 要素数チェック
cout << u_S.size() << endl;  // 3 が出力される

// setに含まれる全要素を出力
for (auto elem: u_S){
    cout << elem << endl;
}
// 3
// 10000
// 12 が出力される（keyの順番はランダム）

// setのクリア
u_S.clear();
```

#### まとめ

|  |  C++ | Python |
| --- | --- | --- |
| 宣言 / 定義 | (unordered_)set\<要素の型\> S; | S = set() |
| 要素を追加(1) | S.insert(要素); | S.add(要素) |
| 要素を削除(1) [^6] | S.erase(要素); | S.discard(要素) |
| 要素を削除(2) | 同上 | S.remove(要素) [^7] |
| 要素を削除(3) | 同上 | _ = S.pop() [^8] |
| 要素の所属判定 | S.count(要素); [^9] | 要素 in S [^10] |
| 全要素を削除 | S.clear(); | S.clear() |
| 要素数を取得 | S.size(); | len(S) |
| 他のsetと合成[^11] | S.merge(S2); | S.update(S2) |

[^6]: C++でもPythonでも要素が存在しなくてもエラーにならない
[^7]: 要素が存在しないと`KeyError`例外が投げられる
[^8]: 削除される要素はランダム
[^9]: 要素数を返す。すなわち要素が存在すれば1 / 存在しないなら0
[^10]: 要素が存在するか否かを`True` / `False`で返す
[^11]: `set`/ `unordered_set`で`merge`が使えるのはC++17以降のみ