PythonistaのためのC++記法入門: それC++ではどう書くの2 ~基本的な配列~

[PythonistaのためのC++記法入門: それC++ではどう書くの1](https://qiita.com/nctp/items/ba7801de3b85eeff6ad4)の続き。今回は配列。

# 配列

Pythonにおける配列として代表的なのは`list`であり、C++でそれに近い挙動をするのは`array`や`list`および`vector`である。ここでは主に`vector`を例に挙げてPythonの`list`との用法を比較していく。

## vector

`vector`は*末尾への要素追加*と*シーケンス内要素へのランダムアクセス*が$O(1)$と高速に行える一方、任意位置への要素の挿入は$O(N)$と要素数に応じた時間がかかる。こういった特性、および`array`と異なり*動的配列*をサポートしているなど、Pythonの`list`はこちらに近い。

## 1次元配列

### Pythonではこう書いた

```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++ではこう書く

```cpp
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}からなる配列となる
```

```cpp
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"が出力される

```

以下のような書き方もある。

```cpp
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++ではこう書く

```cpp
// 縦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() |

[^1]: C++もPythonも境界チェックを行う。すなわち、範囲外にアクセスしたときにC++では`out_of_range`例外、Pythonでは`IndexError`例外が投げられる
[^2]: 境界チェックは行われず、範囲外のインデックスを指定しても動く可能性がある
[^3]: C++ではイテレータ / Pythonではインデックスを位置引数として受け取る
[^4]: Pythonの`pop()`は引数を指定しない場合には最後に追加した要素を削除する（`vector`の`pop()`と同じ挙動を示す）が、インデックスを引数として渡すことでそのインデックスの要素を削除できる
[^5]: 位置引数としてイテレータを受け取り、その位置の要素を削除する


## 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 |

[^6]: C++もPythonも境界チェックを行う。すなわち、範囲外にアクセスしたときにC++では`out_of_range`例外、Pythonでは`IndexError`例外が投げられる
[^7]: 境界チェックは行われず、範囲外のインデックスを指定しても動く可能性がある
[^8]: 引数を指定しない場合には最後に追加した要素を削除するが、インデックスを引数として渡すことでそのインデックスの要素を削除できる

## 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() |

[^9]: `ls = [要素]　+ ls`とすれば同様の結果は得られる
[^10]: Pythonの場合`pop(0)`は非常に遅い
[^11]: C++ではイテレータ / Pythonではインデックスを位置引数として受け取る
[^12]: 位置引数としてイテレータを受け取り、その位置の要素を削除する
[^13]: C++もPythonも返り値なし（その場で配列の中身がソートされる）
[^14]: C++もPythonも返り値なし（その場で配列の中身が反転される）