PythonistaのためのC++記法入門: それC++ではどう書くの1の続き。今回は配列。
配列
Pythonにおける配列として代表的なのはlist
であり、C++でそれに近い挙動をするのはarray
やlist
およびvector
である。ここでは主にvector
を例に挙げてPythonのlist
との用法を比較していく。
vector
vector
は末尾への要素追加とシーケンス内要素へのランダムアクセスが$O(1)$と高速に行える一方、任意位置への要素の挿入は$O(N)$と要素数に応じた時間がかかる。こういった特性、およびarray
と異なり動的配列をサポートしているなど、Pythonのlist
はこちらに近い。
1次元配列
Pythonではこう書いた
data = list() # 空の配列
data = [0, 0, 0] # [0, 0, 0] で初期化
data = [128, 128, 128] # [128, 128, 128] で初期化
data = [1, 2, 3] # [1, 2, 3] で初期化
val = data[0] # 0番目の要素がvalに代入される
s = len(data) # 配列の要素数が出力される
data.append(10) # 末尾に10が追加される
data.pop() # 末尾である10が削除されて出力される
C++ではこう書く
vector<int> data; // 空の配列、ただし要素はintと既定
vector<int> data(3); // 要素数は3だが中身は未初期化値
vector<int> data(3, 128); // 要素数3かつそれぞれ128で初期化
vector<int> data = {1, 2, 3}; // 3つの要素{1, 2, 3}からなる配列となる
vector<int> data = {1, 2, 3}; // 3つの要素{1, 2, 3}からなる配列を作成
cout << data.at(0) << " " << data.at(1) << " " << data.at(2) << endl;
// "1 2 3"が出力される
int val;
val = data.at(0); // 0番目の要素がint型の変数valに代入される
val = data[0]; // 変数名[i]でもi番目の要素にアクセスできる
// 要素の変更
data.at(2) = 300; // data[2] = 300;でも同様
cout << data.at(0) << " " << data.at(1) << " " << data.at(2) << endl;
// "1 2 300"が出力される
data.push_back(10); // 末尾に10が追加される
cout << data.at(0) << " " << data.at(1) << " " << data.at(2) << " " << data.at(3) << endl;
// "1 2 300 10" が出力される
data.pop_back(); // 末尾である10が削除される
// 削除されたか確認する
int s = data.size(); // 配列の要素数をint型の変数sに代入
cout << s << endl; // 3 が出力される
for (int i = 0; i < s; i++){
cout << data.at(i) << " ";
}
cout << endl;
// "1 2 300"が出力される
以下のような書き方もある。
int data[3]; // int型の3要素の配列が作られる。要素は未初期化。int data[3] = {1, 2, 3};などとすれば初期化は可能。
// data[N]で各要素にアクセスが可能
cout << data[0] << " " << data[1] << " " << data[2] << endl;
// "0 4196272 0" と出力される(未初期化値)
// data[N]で各要素の値のセットが可能
data[1] = 120;
cout << data[0] << " " << data[1] << " " << data[2] << endl;
// 今回は"0 120 0" と出力される
多次元配列
配列の配列、すなわちC++ではvector
を要素に持つvector
であり、Pythonではlist
のlist
である。
C++ではこう書く
// 縦3×横4の配列
vector<vector<int>> data(3, vector<int>(4));
// 要素数が未定の場合
vector<vector<int>> data;
// 配列の配列であるdataに3要素からなる配列を追加
vector<int> tmp = {1, 2, 3};
data.push_back(tmp); // data = [[1, 2, 3]]
// 異なる要素数の配列を追加できる
vector<int> tmp2 = {10, 20, 30, 40};
data.push_back(tmp2); // data = [[1, 2, 3], [10, 20, 30, 40]]
まとめ
C++ | Python | |
---|---|---|
宣言 / 定義 | vector<型> vec; | vec = list() |
要素を追加 | vec.push_back(要素); | vec.append(要素) |
先頭要素へのアクセス | vec.front(); | vec[0] |
末尾要素へのアクセス | vec.back(); | vec[-1] |
任意要素へのアクセス(1) 1 | vec.at(i); | vec[i] |
任意要素へのアクセス(2) | vec[i]; 2 | 同上 |
要素を任意位置に挿入 3 | vec.insert(位置, 要素); | vec.insert(位置, 要素) |
末尾要素を削除 | vec.pop_back(); | _ = vec.pop() 4 |
任意要素を削除 | vec.erase(位置); 5 | vec.remove(要素) |
要素数を取得 | vec.size(); | len(vec) |
配列が空か確認 | vec.empty(); | len(vec) == 0 |
全要素を削除 | vec.clear(); | vec.clear() |
array
固定長シーケンスを扱う配列。固定長の配列であるため、宣言時に要素数も指定する必要がある。要素の追加はできないが既存の要素の変更はできる。
Pythonでは(あまり使わないかもしれないが)array
に近いか。
C++ではこう書く
array<型, 要素数> 変数名;
で宣言。この場合、各要素として型に応じたランダムな値が格納される。
array<型, 要素数> 変数名 = {要素1, 要素2, 要素3, ...};
と各要素を初期化した上で宣言することも可能。
変数名.at(i)
もしくは変数名[i]
でi
番目の要素にアクセスできる。またこのときその要素への代入も可能。vector
と同様、at(i)
は境界チェックを行うので前者のほうが安全。
まとめ
C++ | Python | |
---|---|---|
宣言 / 定義 | array<型, 要素数> arr = {要素1, 要素2, ...}; | arr = array.array(型, [要素1, 要素2, ...]) |
要素を追加 | できない | arr.append(要素) |
先頭要素へのアクセス | arr.front(); | arr[0] |
末尾要素へのアクセス | arr.back(); | arr[-1] |
任意要素へのアクセス(1) 6 | arr.at(i); | arr[i] |
任意要素へのアクセス(2) | arr[i]; 7 | 同上 |
要素を任意位置に挿入 | 不可 | arr.insert(位置, 要素) |
要素を削除 | 不可 | arr.pop() 8 |
要素を指定して削除 | 不可 | arr.remove(要素) |
要素数を取得 | arr.size(); | len(arr) |
配列が空か確認 | arr.empty(); | len(arr) == 0 |
list
双方向リンクリストである。片方向リストとしてはforward_list
がある。
list
は、任意位置における挿入/削除を$O(1)$で実行することができる。しかし、シーケンス内のランダムアクセスは$O(N)$とvector
と逆のようなふるまいをする。
Pythonのlist
とは異なり、任意要素へのアクセスはできず、アクセスできるのは先頭/末尾要素のみとなる。
C++ではこう書く
list<型> 変数名;
で宣言。
まとめ
C++ | Python | |
---|---|---|
宣言 / 定義 | list<型> ls; | ls = list() |
先頭に要素を追加 | ls.push_front(); | 不可 9 |
先頭の要素を削除 | ls.pop_front(); | ls.pop(0) 10 |
先頭要素へのアクセス | ls.front(); | ls[0] |
末尾に要素を追加 | ls.push_back(); | ls.append() |
末尾の要素を削除 | ls.pop_back(); | ls.pop() |
末尾要素へのアクセス | ls.back(); | ls[-1] |
要素を任意位置に挿入 11 | ls.insert(位置, 要素); | ls.insert(位置, 要素) |
任意要素の削除 | ls.erase(位置); 12 | ls.remove(要素) |
要素数を取得 | ls.size(); | len(ls) |
配列が空か確認 | ls.empty(); | len(ls) == 0 |
要素の並び替え 13 | ls.sort(); | ls.sort() |
要素の反転 14 | ls.reverse(); | ls.reverse() |
要素の合成 | ls.merge(ls2); | ls.extend(ls2) |
全要素を削除 | ls.clear(); | ls.clear() |
-
C++もPythonも境界チェックを行う。すなわち、範囲外にアクセスしたときにC++では
out_of_range
例外、PythonではIndexError
例外が投げられる ↩ -
境界チェックは行われず、範囲外のインデックスを指定しても動く可能性がある ↩
-
C++ではイテレータ / Pythonではインデックスを位置引数として受け取る ↩
-
Pythonの
pop()
は引数を指定しない場合には最後に追加した要素を削除する(vector
のpop()
と同じ挙動を示す)が、インデックスを引数として渡すことでそのインデックスの要素を削除できる ↩ -
位置引数としてイテレータを受け取り、その位置の要素を削除する ↩
-
C++もPythonも境界チェックを行う。すなわち、範囲外にアクセスしたときにC++では
out_of_range
例外、PythonではIndexError
例外が投げられる ↩ -
境界チェックは行われず、範囲外のインデックスを指定しても動く可能性がある ↩
-
引数を指定しない場合には最後に追加した要素を削除するが、インデックスを引数として渡すことでそのインデックスの要素を削除できる ↩
-
ls = [要素] + ls
とすれば同様の結果は得られる ↩ -
Pythonの場合
pop(0)
は非常に遅い ↩ -
C++ではイテレータ / Pythonではインデックスを位置引数として受け取る ↩
-
位置引数としてイテレータを受け取り、その位置の要素を削除する ↩
-
C++もPythonも返り値なし(その場で配列の中身がソートされる) ↩
-
C++もPythonも返り値なし(その場で配列の中身が反転される) ↩