Pythonの配列っぽいやつらを比べよう

本稿は[NCC Advent Calender 2019](https://qiita.com/advent-calendar/2019/ncc)の14日目記事です．

# はじめに
プログラム書くとき，配列って大事です．
しかし，**Python 配列っぽいの多すぎ問題**があります．
Pythonの標準機能だけでも
`list`，`dict`，`set`，`tuple`
があります．
加えて`numpy`などのライブラリを使い始めるとさらに似たようなものが増えます．

なので，今回はそいつらを以下の4点でまとめて使い分けができるように解説していきます．

1. 概説
2. いいとこ
3. わるいとこ
4. 所感

あくまで**使い分け**のための記事なので，細かい使い方などには触れません．
また，配列が何かはなんとなくわかってる前提で書きます．
さらになるべく簡単な言葉を使って説明します．
* 説明の易化のために，本質とは異なる説明をしている場合があります．

## 対象読者
- Pythonを使い始めたけど，配列多すぎて困ってる
- 他の言語やってたけど，Pythonでの各配列の立ち位置がわからない
- 実装中，ここどれ使うべきかなってなった人
- numpyなどのライブラリを使い始めた人

## とりあげるもの
### Python標準機能
- `list`
- `dict`
- `set`
- `tuple`

### ライブラリ
#### `numpy`
- `numpy.array`/`numpy.ndarray`
- `numpy.matrix`

# 本編
## `list`
### 概説
`list`は簡単に言えば**一番の基本のやつ**です．
他言語でいう一般的な配列と同じで，Pythonでは「リスト」と呼ぶのが一般的です．
記号としては`[]`なので，これで囲まれて，中に`,`がいっぱいあったらリストだと思ってください．

Pythonではリスト内の型が複数でも構いません．
また，同じ値を何個でも入れることができます．
その他，世間一般に言う配列の機能を持っています．

あとで，`dict`以下の説明をするときに，よく`list`と比較すると思うので，それで`list`の性質はそれを見ながら把握してもらえるといいかなと思います．
(普通のやつなのであまり説明がないです．)


### いいとこ
- 扱いが簡単
- 難しいこと考えず，とりあえず`list`にしとけば実装はできる

### わるいとこ
- 次元が多くなるとわかりにくくなる
 - 一個一個の配列の長さをまとめて見れない(`numpy`の`shape`みたいなのがない)
- 参照が0から続く数字のみ

### 所感
良くも悪くも原点．
`numpy`とか`pandas`でも一旦`list`に変換してから使うこともザラですね．
1次元で長さが不定な場合や，順序にい意味があるが番号に意味がない時も`list`です．
ただ多次元ではあまり向かないです．
(そもそも次元が多すぎるような設計をするな)
数値であれば`numpy`，複合的なデータであれば`dict`と組み合わせるとスッキリします．

## `dict`
### 概説
いわゆる**連想配列**ってやつです．
Pythonでは辞書型または`dictionary`(略して`dict`)です．
記号としては`{}`で，`{key: value}`にように，一つ一つの要素が`:`で結ばれます．
例に及んで詳しくは説明しません．レファレンスなどを参照してください．

`dict`の`key`の型はなんでもよかった気がしますが，文字列か整数を使った方がわかりやすいです．
あと中身がいっぱいある時は`key`ごと改行すると見やすいです．またインデントは揃えましょう．(jsonみたいに)
例を載せておきます．

```python

ncc = { 'name': 'ncc',
        'full name': 'nakano computer club',
        'estimate': 2015,
        'web site': 'https://meiji-ncc.tech/'
        }
```
*インデントに関してはいくつか宗派があるのでお好みのものを使用しましょう．
また一行一要素で長すぎる場合は，3個ずつなどにしたほうが見やすい場合もあります．
この辺は内容に合わせて臨機応変に対応しましょう．

### いいとこ
- わかりやすい
- jsonとの相互利用がしやすい
- 参照に文字列が使える

### わるいとこ
- 慣れないとエラーのもと
- 深くなると取り出しづらい
 - `dic['first']['second']['third']`みたいになっていく

### 所感
値とその名前が重要になる時はこいつで決まり！
数字でも間隔が不規則な時は`dict`のほうが扱いやすいです．
あと，`pandas`(ライブラリ)の`DataFrame`に変換する前に一旦`dict`でまとめることも多いです．そのほうが扱いやすいので．
`key`と`value`を結びつけるイメージで使うといいかと思います．
なんやかんやjsonとの対応が便利だったり．．(jsonライブラリ使えば読み書き簡単)


## `set`
### 概説
`set`は**被り禁止の`list`**というのが端的な表し方です．
記号は`{}`で`dict`と同じですが，`:`は使いません．
`list`と同じように`,`で値をポンポン並べていきます．
なので，`{}`で囲まれて，`,`がいっぱいあったら`set`です
厳密には集合を表すものです．
なので集合演算もできます．(ここでは触れませんが)

`list`は`[0, 1, 2, 1, 0]`のように同じ値を持つことができますが`set`ではできません．
上記の`list`を`set`に変換すると`{0, 1, 2}`となります．
これは数字以外でも同じです．

逆を言えば，被りをなくたい時は`set`に変換するといけます．
この場合，リストとしてまた扱いたい場合は`set`の上から`list`に変換してリストに戻す必要があります．
例を書いておきます．

```python
list_duplicate = [0, 1, 2, 2, 1, 0, 3]
list_non_duplicate = list(set(list_duplicate))
print(list_non_duplicate) # out: [0, 1, 2, 3]
```

### いいとこ
- 被りなく入れられる
- 集合演算ができる

### わるいとこ
- 要素の順番にルーズ
 - (`list`から変換した場合，被りが除かれてその分前につめます)
- 記号が`dict`と一緒でわかりづらい(若干)

### 所感
最初から`set`で使うことはあまりないです．
被りをなくしたいときや，複数のリストの要素の共通部分などを抽出する時などにリストから変換することが多いです．
なので，実装時に集合的アプローチをとるときに使うものと思っておけばいいと思います．

## `tuple`
### 概説
`tuple`は**ちょっとお堅い`list`**と言うべきでしょうか．
少しクセがあります．
記号は`()`です．

基本は`list`のようなものですが，色々違います．
大まかに言えば，**一度作ったものをいじくることができません**．
後ろに別のタプルを追加することはできます．(1)
タプル自体を丸々別のものに変えることもできます．(2)
その他，要素の書き換えなどはできません．
やや難しいので，実例で示します．

```python
t = (0, 1, 2)
# 後ろにタプルを追加
t += (3, 4) # OK(1)
# タプル自体の書き換え
t = (0, 1, 2) # OK(2)
# 要素の書き換え
t[0] = 1 # Error
```
代入や，削除などにはそもそもメソッドが用意されてません．
また，要素の書き換えができないので，特定の順番を変えることもできません．
この辺をやるには，`list`に変換する必要があります．

### いいとこ
- 一回作ったら書き換えできない
- 順番も作成時のものが常に保証される
- 挙動を確定させられる
- 定数として使える

### わるいとこ
- 柔軟性が皆無
- 扱いづらい
- エラーのもと

### 所感
Pythonには定数を表す型(jsでいうconst)がないので，それを擬似的に`tuple`で行うことができます．
ただ，動的なことはいっさいできないので，あまり使いません．
ライブラリのメソッドの返り値が`tuple`のことがあるので，そこでつかうぐらいです．

---
続いてPythonのライブラリである`numpy`における配列系の説明に移ります．
その前に，`numpy`の説明をさっくりします．

## (補足)`numpy`とは
`numpy`は線形代数でやる行列演算を行えるライブラリです．
配列の要素同士の足し算，引き算．
配列全体に数値をかける時などに使えます．
もっと高度なこともできますが，**配列の数値計算が便利になる**と思っておけば大丈夫です．

## `numpy.array`/`numpy.ndarray`
### 概説
`numpy`の1次元の配列が`numpy.array`です．
多次元は`numpy.ndarray`になります．
1次元でも多次元でも扱いはあまり変わりません．
こいつはライブラリなので，特定の記号で表すことはできません．
`list`などを`numpy.array()`で囲んであげると変換されます．
* 多次元でも`numpy.array()`で囲みます，`numpy.ndarray`では囲めません．
`list`との違いは何と言っても，**配列同士の演算ができる**ことです．
ただ，空の`numpy.array`を作成することはできません．
`list`から変換しましょう．

```python
import numpy as np

# 通常のlistを定義
list_num0 = [0, 1, 2, 3, 4]

# numpy配列に変換
np_num0 = np.array(list_num0)
print(np_num0) # out: [0 1 2 3 4]

# 直接numpy配列を生成
np_num1 = np.array([5, 6, 7, 8, 9])
print(np_num1) # out: [5 6 7 8 9]

# numpy配列をlistに変換
list_num1 = list(np_num1) 
print(list_num1) # out: [5, 6, 7, 8, 9]

# list，numpy配列それぞれを2倍してみる
list_num0_twice = 2*list_num0
print(list_num0_twice) # out: [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
np_num0_twice = 2*np_num0
print(np_num0_twice) # out: [0 2 4 6 8]

# list, numpy配列でそれぞれ足し算してみる
list_num_add = list_num0 + list_num1
print(list_num_add) # out: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
np_num_add = np_num0 + np_num1
print(np_num_add) # out: [ 5  7  9 11 13]
```
このように，配列の要素内の演算を`numpy.array`では簡単にやることができます．

### いいとこ
- 配列の要素同士の演算を簡単にできる
- 多次元でも扱いやすい
- 計算が早い(ことが多い)

### わるいとこ
- 慣れないと扱いにくい
- 1つ1つの長さが違う多次元配列には向かない
- 数値以外は使いづらい

### 所感
数学的なことをやるなら絶対`numpy`です．
`scipy`という応用的な計算(積分とか)ができるライブラリとも相性がいいです．
計算をいっぱいやるときに慣れてきたら，使ってみるといいと思います．
`list`との違いを把握すれば，使いやすいです．

## `numpy.matrix`
- 使ったことない
- 色々調べましたが，`ndarray`使えばいいみたいです
- m×nの行列を使う時は便利みたい
- 以上．


# まとめ
それぞれを一言でまとめるとこうなります．

`list`: 基本
`dict`: 名前と値の結びつきが強い
`set`: 被りなしの`list`
`tuple`: 書き換え不可の`list`
`numpy.array`/`numpy.ndarray`: 数値計算特化`list`
`numpy.matrix`:`numpy.ndarray`とこんがらがるから使わない 

## フローチャート
個人的にどれを使うかを決める時のなんとなくのフローを図で示すとこうなります．
![flowchart.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175691/84ee0e5e-207c-26d2-6335-686ca7ac039e.png)

実際はもう少し複雑ですが，慣れるまではこんな感じで考えるといいかと思います．
`tuple`は使わないので入ってません．
(本当は`set`もあまり使わない)


# 終わりに
今回はPythonの配列っぽいやつらを比べました．
細かいところやライブラリを含めるともっとあります．
しかし，この辺が基本となるので，ここが理解できれば，他の理解も捗るかと思います．