PythonistaのためのC++記法入門: それC++ではどう書くの2の続き。今回はPythonでも頻繁に利用するtuple
やdict
などをC++ではどう書くか見ていく。
tuple / dict / set
tuple
C++では要素数によってpair
とtuple
があるが、Pythonでは要素数に関わらず一律tuple
として扱われる。Pythonのtuple
はイミュータブル(変更不可)であるために不変のデータの格納に向いているとよく言われる。
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でいうアンパックが可能(代入先の変数の事前定義が必要)。
// 宣言
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と同様に使える。
// 宣言
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だとこう書いた
# 定義
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++だとこう書く
// 宣言
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++だとこう書く
// 宣言
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) |
set
PythonもC++も同じ感覚で使える。
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<型> 変数名;
で宣言。
// 宣言
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++だとこう書く
// 宣言
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) |
-
key
が存在しない場合、map
にそのkey
-value
ペアが追加されてしまう。このときvalue
の値は型依存の初期値となる ↩ -
key
が存在しない場合、KeyError
例外が投げられる ↩ -
key
が存在しない場合out_of_range
例外を投げてくれる ↩ -
key
が存在しない場合None
を返す ↩ -
C++の
map
のmerge(dic2)
ではmerge
対象のdic2
に同一のkey
がすでに存在している場合でもそのvalue
は上書きされないが、Pythonのupdate(dic2)
では同一のkey
-value
ペアがdic2
に含まれていた場合、そのvalue
が上書きされてしまうことに注意。 ↩ -
C++でもPythonでも要素が存在しなくてもエラーにならない ↩
-
要素が存在しないと
KeyError
例外が投げられる ↩ -
削除される要素はランダム ↩
-
要素数を返す。すなわち要素が存在すれば1 / 存在しないなら0 ↩
-
要素が存在するか否かを
True
/False
で返す ↩ -
set
/unordered_set
でmerge
が使えるのはC++17以降のみ ↩