1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-08-11

PythonistaのためのC++記法入門: それC++ではどう書くの2の続き。今回はPythonでも頻繁に利用するtupledictなどをC++ではどう書くか見ていく。

tuple / dict / set

tuple

C++では要素数によってpairtupleがあるが、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++では組の要素数によってpairtupleに分かれる。

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の順番が保持されないmapkeyとしてpairtupleなどのハッシュ関数が定義されていない型を用いることができない制約がある。

内部的にはハッシュテーブルで実装されており、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)
  1. keyが存在しない場合、mapにそのkey-valueペアが追加されてしまう。このときvalueの値は型依存の初期値となる

  2. keyが存在しない場合、KeyError例外が投げられる

  3. keyが存在しない場合out_of_range例外を投げてくれる

  4. keyが存在しない場合Noneを返す

  5. C++のmapmerge(dic2)ではmerge対象のdic2に同一のkeyがすでに存在している場合でもそのvalue上書きされないが、Pythonのupdate(dic2)では同一のkey-valueペアがdic2に含まれていた場合、そのvalue上書きされてしまうことに注意。

  6. C++でもPythonでも要素が存在しなくてもエラーにならない

  7. 要素が存在しないとKeyError例外が投げられる

  8. 削除される要素はランダム

  9. 要素数を返す。すなわち要素が存在すれば1 / 存在しないなら0

  10. 要素が存在するか否かをTrue / Falseで返す

  11. set/ unordered_setmergeが使えるのはC++17以降のみ

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?