【Pythonチュートリアル】データ構造

# はじめに
Pythonチュートリアル第3版を読み進めて、勉強した内容をメモしていこうという試みです

[Pythonチュートリアル 第3版](https://www.oreilly.co.jp/books/9784873117539/)

そして読み終えた暁にはこの試験を受けたいと思います
終わるころには試験も開始している・・・！

**開始しとるやないか！**

[Python 3 エンジニア認定基礎試験](https://www.pythonic-exam.com/exam/basic)

続くといいな、続けばいいな

# データ構造
## リストについての補足
### リストオブジェクトの全メソッド
#### **list.append(x)**
- リストの末尾にアイテムを1 つ追加する
- `a[len(a):] = [x]` 、 `a += [x]` と等価

```pycon
>>> a = [66.25, 333, 333, 1, 1234.5]
>>> a.append(333)
>>> a
[66.25, 333, 333, 1, 1234.5, 333]
```

#### **list.extend(L)**
- リスト末尾に、与えられたリストL の全アイテムを追加することで、リストを伸長する
- `a[len(a):] = L` 、`a += L`と等価。

```pycon
>>> a = [66.25, 333, 333, 1, 1234.5]
>>> b = [1, 2, 3]
>>> a.extend(b)
>>> a
[66.25, 333, 333, 1, 1234.5, 1, 2, 3]
```

#### **list.insert(i, x)**
- 指定された位置にアイテムを挿入する
- 第1 引数は要素のインデックスである
- つまり挿入はこの要素の前に行われる
- `a.insert(0, x)` とするとリストの先頭に挿入される
- `a.insert(len(a), x)` は`a.append(x)` と等価である

```pycon
>>> a = [66.25, 333, 333, 1, 1234.5]
>>> a.insert(2, -1)
>>> a
[66.25, 333, -1, 333, 1, 1234.5]
```

#### **list.remove(x)**
- 値が`x` である最初のアイテムを削除する
- そのようなアイテムが存在しなければエラーになる

```pycon
>>> a = [66.25, 333, 333, 1, 1234.5]
>>> a.remove(333)
>>> a
[66.25, 333, 1, 1234.5]
```

#### **list.pop([i])**
- 指定された位置のアイテムをリストから削除し、このアイテムを返す
- インデックスが指定されないと、a.pop() はリストの最後のアイテムを返し、リストから削除する<br>※このメソッドシグネチャ（定義表記）で**インデックスi を囲むのに使った角カッコは、引数がオプションであることを示しているだけ**で、この位置に角
カッコを入力すべきだという意味ではない。こうした表記はライブラリリファレンスで、しばしば目にすることになる

```pycon
>>> a = [66.25, 333, 333, 1, 1234.5]
>>> a.pop()
1234.5
>>> a
[66.25, 333, 333, 1]
>>> a.pop(2)
333
>>> a
[66.25, 333, 1]
```

#### **list.clear()**
- リストからすべてのアイテムを削除する
- `del a[:]` と等価

```pycon
>>> a = [66.25, 333, 333, 1, 1234.5]
>>> a.clear()
>>> a
[]
```

#### **list.index(x)**
- 値がx である最初のアイテムのインデックスを返す
- そのようなアイテムが存在しなければエラーになる

```pycon
>>> a = [66.25, 333, 333, 1, 1234.5]
>>> a.index(1)
3
```

#### **list.count(x)**
- リスト中のx の個数を返す

```pycon
>>> a = [66.25, 333, 333, 1, 1234.5]
>>> print(a.count(333), a.count(66.25), a.count('x'))
2 1 0
```

#### **list.sort(key=None, reverse=False)**
- リストをインプレースで（＝コピーを取らず、そのリストオブジェクトを直接）ソートする
- 引数でソートのカスタマイズができる

```pycon
>>> a = [333, 1234.5, 1, 333, -1, 66.25]
>>> a.sort()
>>> a
[-1, 1, 66.25, 333, 333, 1234.5]
>>> a.sort(reverse=True)
>>> a
[1234.5, 333, 333, 66.25, 1, -1]
```

#### **list.reverse()**
- リストの要素をインプレースで逆順にする

```pycon
>>> a = [333, 1234.5, 1, 333, -1, 66.25]
>>> a.reverse()
>>> a
[66.25, -1, 333, 1, 1234.5, 333]
```

#### **list.copy()**
- リストのシャローコピー（浅いコピー）を返す
- `a[:]` と等価

```pycon
>>> a = [333, 1234.5, 1, 333, -1, 66.25]
>>> a.copy()
[333, 1234.5, 1, 333, -1, 66.25]
```


### リストをスタックとして使う
- スタックでは最後に追加された要素が最初に取得される
- スタックのトップにアイテムを積むには`append()`を使う
- スタックのトップからアイテムを取得するには、インデックスを指定しない`pop()`を使う

```pycon
>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack.append(7)
>>> stack
[3, 4, 5, 6, 7]
>>> stack.pop()
7
>>> stack
[3, 4, 5, 6]
>>> stack.pop()
6
>>> stack
[3, 4, 5]
>>> stack.pop()
5
>>> stack
[3, 4]
```

### リストをキューとして使う
- キューでは要素を入れた順に取得する
- リストの末尾で`append`や`pop`するのは高速だが、リストの先頭での`insert`や`pop`は低速<br>（これは他の要素をすべて1だけシフトする必要があるため）
- **キューの実装には`collections.deque`を使う**
- `deque`は先頭と末尾の両方で要素の追加とポップが高速になるよう設計されている

```pycon
>>> from collections import deque
>>> queue = deque(["Eric", "John", "Michael"])
>>> queue.append("Terry")
>>> queue.append("Graham")
>>> queue.popleft()
'Eric'
>>> queue.popleft()
'John'
>>> queue
deque(['Michael', 'Terry', 'Graham'])
```

### リスト内包
- リスト内包はリストを生成する完結な方法を提供する
- よくある使い方として、シーケンスや反復可能体のメンバそれぞれになんらかの処理を加えて新しいリストを生成したり、ある条件にかなう要素のみを取り出してサブシーケンスを生成するというのがある

```pycon:2乗数のリストを生成するプログラム
>>> squares = []
>>> for x in range(10):
...     squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
```

```pycon:2乗数のリストを生成するプログラム（lamdba式を使用した場合）
>>> squares = list(map(lambda x: x**2, range(10)))
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
```

```pycon:2乗数のリストを生成するプログラム（リスト内包を使用した場合）
>>> squares = [x**2 for x in range(10)]
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
```


リスト内包とは、式とそれに続く`for`節から成り、さらに0個以上の`for`節や`if`節を後ろに続け、全体を大カッコ（[]）で囲んだものである

得られるものは、最初の式を後続の`for`節や`if`節の文脈で評価した値による新しいリストである

たとえば次のリスト内包は、2つのリストから要素を取り、両社が同一でなければタプルにまとめる

```pycon
>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x !=y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
```

これは以下のプログラムと等価である

```pycon
>>> combs = []
>>> for x in [1,2,4]:
...     for y in [3,1,4]:
...             if x != y:
...                     combs.append((x, y))
...
>>> combs
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (4, 3), (4, 1)]
```

下記プログラムの両者のfor 文とif 文が同じ順序で並んでいることに注目
式がタプルであるときは丸カッコが必須である

```pycon
>>> # 値を2 倍にした新しいリストを生成
>>> vec = [-4, -2, 0, 2, 4]
>>> [x*2 for x in vec]
[-8, -4, 0, 4, 8]
>>> # 負の数を除去するようにフィルタをかける
>>> [x for x in vec if x >= 0]
[0, 2, 4]
>>> # すべての要素に関数を適用
>>> [abs(x) for x in vec]
[4, 2, 0, 2, 4]
>>> # 各要素にメソッドをコール
>>> freshfruit = [' banana', ' loganberry ', 'passion fruit ']
>>> [weapon.strip() for weapon in freshfruit]
['banana', 'loganberry', 'passion fruit']
>>> # 2 値のタプル( 数値, 2 乗値) のリストを生成
>>> [(x, x**2) for x in range(6)]
[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
>>> # タプルを丸カッコで囲わないとエラーになる
>>> [x, x**2 for x in range(6)]
  File "<stdin>", line 1
    [x, x**2 for x in range(6)]
               ^
SyntaxError: invalid syntax
>>> # for を2 つ使ってリストを平滑化（1 次元化）する
>>> vec = [[1,2,3], [4,5,6], [7,8,9]]
>>> [num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
```

リスト内包には複合式や入れ子の関数を含むことができる

```pycon
>>> from math import pi
>>> [str(round(pi, i)) for i in range(1, 6)]
['3.1', '3.14', '3.142', '3.1416', '3.14159']
```

### 入れ子のリスト内包
- リスト内包の先頭の式には任意のあらゆる式が使え、ここに他のリスト内包を入れることもできる

```pycon:長さ4のリスト3個で実装された3×4の行列
>>> matrix = [
...     [1, 2, 3, 4],
...     [5, 6, 7, 8],
...     [9, 10, 11, 12],
... ]
```

```pycon:行と列を入れかえる
>>> [[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
```

上記プログラムは以下と等価

```pycon
>>> transposed = []
>>> for i in range(4):
...     transposed.append([row[i] for row in matrix])
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
```

つまり、以下と等価

```pycon
>>> transposed = []
>>> for i in range(4):
...     transposed_row = []
...     for row in matrix:
...             transposed_row.append(row[i])
...     transposed.append(transposed_row)
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
```

現実的に、複雑なフローにはビルトイン関数を使用する
`zip`関数はこうした場面で活用できる

```pycon
>>> list(zip(*matrix))
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]
```



## del 文
- del文はリストのアイテムを削除する際、値でなくインデックスで指定する
- 値を返さないところが`pop()`メソッドとは異なる
- del文はリストからスライスで削除したり、リスト全体の消去にも使える

```pycon
>>> a = [-1, 1, 66.25, 333, 333, 1234.5]
>>> del a[0]
>>> a
[1, 66.25, 333, 333, 1234.5]
>>> del a[2:4]
>>> a
[1, 66.25, 1234.5]
>>> del a[:]
>>> a
[]
```

`del`は変数をまるごと削除するのにも使える

```pycon
>>> a = [-1, 1, 66.25, 333, 333, 1234.5]
>>> del a
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
```

## タプルとシーケンス
- タプルはカンマで区切られた値からなる

```pycon
>>> t = 12345, 54321, 'hello!'
>>> t[0]
12345
>>> t
(12345, 54321, 'hello!')
>>> # タプルは入れ子にできる
>>> u = t, (1, 2, 3, 4, 5)
>>> u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
>>> # タプルは変更不能
>>> t[0] = 88888
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
```

可変オブジェクトを格納できる

```pycon
>>> v = ([1, 2, 3], [3, 2, 1])
>>> v
([1, 2, 3], [3, 2, 1])
```

出力されるタプルは常に丸括弧に囲まれているので、入れ子のタプルも正しく解釈される
タプルのアイテムに代入を行うことは不可能だが、リストなどの変更可能オブジェクトを含んだタプルを生成することは可能

タプルはリストに似たものに見えるが、使用される状況は異なるし、用途も異なる
タプルは**変更不能（immutable）**であり、異種の要素によってシーケンスを作り、各要素にはアンパッキングやインデックスで（名前月タプルの場合は属性でも）アクセスする、というのが通例

リストは**変更可能（mutable）**であり、普通は同種の要素から成、リストに反復をかけることでこれらにアクセスする

アイテム数が0、か1のタプルを作ることには特殊な問題（区切りのカンマがないので他の型と区別できない）があるので、構文にはこれらに対処する逃げが作ってある
まず、空のタプルは対になった丸括弧の中を空にしたもので作る
そして1アイテムのタプルは、1つの値の後ろにカンマを付けることで作る

```pycon
>>> empty = ()
>>> singleton = 'hello',
>>> len(empty)
0
>>> singleton
('hello',)
```

以下の文は、タプル・パッキング（タプル梱包）の例
12345, 54321, hello! は1つのタプルに入る

```pycon
>>> t = 12345, 54321, 'hello!'
>>> t
(12345, 54321, 'hello!')
```

以下のような処理を**シーケンス・アンパッキング（開梱）**といい、普遍にどんなシーケンスが来てもよい
シーケンスをアンパックするときは、シーケンスの長さに等しい個数の変数のリストが左辺に必要である
多重代入とはタプル・パッキングとシーケンス・アンパッキングの組み合わせにすぎないことに注意

```pycon
>>> t = 12345, 54321, 'hello!'
>>> x, y, z = t
>>> x
12345
>>> y
54321
>>> z
'hello!'
```


## 集合（set）
- 集合とは重複しない要素を順不同で集めたもの
- 基本的な用途としては存在判定（membership testing）や、重複エントリの排除がある
- 集合オブジェクトはまた、和、交差、差、対称差といった数学的演算をサポートしている
- 集合の生成には中括弧{}または`set()`を使う必要がある（全社は空のディクショナリを生成してしまう）

```pycon
>>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
>>> print(basket)
{'orange', 'pear', 'banana', 'apple'}
>>> 'orange' in basket
True
>>> 'crabgrass' in basket
False
```

2つの単語から非重複（ユニーク）文字を取って集合演算をを実演

```pycon
>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> # a のユニーク文字
>>> a
{'b', 'r', 'a', 'd', 'c'}
>>> # aに存在し b にはない文字
>>> a - b
{'r', 'b', 'd'}
>>> # a または b に存在する文字
>>> a | b
{'l', 'm', 'b', 'z', 'r', 'a', 'd', 'c'}
>>> # a にも b にも存在する文字
>>> a & b
{'a', 'c'}
>>> # a または b にある共通しない文字
>>> a ^ b
{'m', 'b', 'z', 'r', 'd', 'l'}
```

リスト内包とよく似た集合内包もサポートされている

```pycon
>>> a = { x for x in 'abracadabra' if x not in 'abc'}
>>> a
{'r', 'd'}
```


## ディクショナリ（辞書）
- [Pythonライブラリリファレンス 4.10. マッピング型 — dict](http://docs.python.jp/3/library/stdtypes.html#mapping-types-dict)参照
- ディクショナリは他の言語でも「連想記憶」や「連想配列」、「ハッシュ」として存在することがある
- シーケンスには連続した数字によるインデックスがついているのに対し、ディクショナリにはキーによるインデックスがついている
- キーにはあらゆる変更不能型が使える
- 文字列や数値は常にキーとして使える
- タプルもキーとして使える（ただしこれはタプルが文字列、数値、タプルのみをを含む場合）
- 可変型のオブジェクトが直接間接に含まれているタプルは、キーとして使えない
- リストもキーとして使えない<br>※インデックス代入やスライス代入、さらには`append()`や`extend()`といったメソッドにより、インプレースで改変できてしまうからである
- ディクショナリはキーの唯一性（ひとつのディクショナリの中で重複しないこと）を条件とするので、「キー：値」ペアによるソートされない集合と考えるのが適切
- 中括弧対「｛｝」を書けば空のディクショナリとなる
- カンマで区切った一連の「キー：値」ペアをこの中括弧に入れれば、ディクショナリの初期値としてこの「キー：値：ペア群を与えることになる
- ディクショナリの主たる作用は、値を何らかのキーとともに格納し、キー指定で値を引き出すこと
- `del`により「キー：値」をペアごと削除することもできる
- すでに使われているキーを使って格納を行うと、前の値は失われる
- 存在しないキーで値を引き出そうとすればエラーになる
- ディクショナリに`list(d.keys())`をかけると、そのディクショナリにあるすべてのキーからなる未ソートのリストを返す（ソートしたい場合は、代わりに`sorted(d.keys())`とする
- あるキーがディクショナリに存在するかチェックしたいときは、キーワード`in`を使うと良い

```pycon
>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> tel
{'jack': 4098, 'sape': 4139, 'guido': 4127}
>>> tel['jack']
4098
>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{'jack': 4098, 'guido': 4127, 'irv': 4127}
>>> list(tel.keys())
['jack', 'guido', 'irv']
>>> sorted(tel.keys())
['guido', 'irv', 'jack']
>>> 'guido' in tel
True
>>> 'jack' not in tel
False
```

`dict`コンストラクタは、「キー：値」ペアのタプルからなるシーケンスからディクショナリを構築する

```pycon
>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'guido': 4127, 'jack': 4098}
```

辞書内包を使えばキーと値を与える任意の式からディクショナリが生成できる

```pycon
>>> {x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}
```

キーが簡単な文字列なら、キーワード引数でペアを指定するのが楽な場合もある

```pycon
>>> dict(sape=4139, guido=4127, jack=4098)
{'sape': 4139, 'guido': 4127, 'jack': 4098}
```


## ループのテクニック
ディクショナリにループをかけるとき、`items()`メソッドを使えば、キーとそれに対応した値を同時に得られる

```pycon
>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
>>> for k, v in knights.items():
...     print(k, v)
...
gallahad the pure
robin the brave
```

シーケンスにループをかけるとき、`enumerate()`関数を使うと位置インデックスとそれに対応した値を同時に得ることができる

```pycon
>>> for i, v in enumerate(['tic', 'tac', 'toe']):
...     print(i, v)
...
0 tic
1 tac
2 toe
```

2つ以上のシーケンスに同時にループをかけるときは、`zip()`関数を使うと両社のエントリをペアにできる

```pycon
>>> questions = ['name', 'quest', 'favorite color']
>>> answers = ['lancelot', 'the holy grail', 'blue']
>>> for q, a in zip(questions, answers):
...     print('What is your {0}? It is {1}.'.format(q, a))
...
What is your name? It is lancelot.
What is your quest? It is the holy grail.
What is your favorite color? It is blue.
```

シーケンスを逆順にループさせるには、まずシーケンスを正順で指定し、これに`reversed()`関数をコールする

```pycon
>>> for i in reversed(range(1, 10, 2)):
...     print(i)
...
9
7
5
3
1
```

シーケンスをソート順にループするには`sorted()`関数を使う
この関数は元のシーケンスには触らず、新たにソート済みのリストを返す

```pycon
>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
>>> for f in sorted(set(basket)):
...     print(f)
...
apple
banana
orange
pear
```

ループ中のリストを改変したい場合、新しいリストを作った方が簡単で安全

```pycon
>>> import math
>>> raw_data = [56.2, float('NaN'), 51.7, 55.3, 52.5, float('NaN'), 47.8]
>>> filtered_data = []
>>> for value in raw_data:
...     if not math.isnan(value):
...             filtered_data.append(value)
...
>>> filtered_data
[56.2, 51.7, 55.3, 52.5, 47.8]
```


## 条件についての補足
- `while`文や`if`文で使われる条件には、比較だけでなくあらゆる演算子が使える
- 比較演算子`in`および`not in`は、シーケンスに値が存在するかしないかをチェックする
- 演算子`is`および`is not`は2つのオブジェクトを比較して完全に同一であるかを調べる<br>※同一性が問題になるのはリストのような可変オブジェクトのみ
- 比較演算子の優先順位はすべて同等で、どれもすべての数値演算子より低い
- たとえば `a < b == c` とすれば、 a が b より小さく、かつ b が c に等しいかどうか判定できるというように比較を連鎖させることができる
- 比較はブール演算子の `and` および `or` により組み合わせることができ、また比較の結論（および他の全ブール式）は、 `not` による否定ができる
- この演算子の中では`not`の順位が最も高く、`or`がもっとも低いので、`A and not B or C` は `(A and (not B)) or C` と等価となる
- ブール演算子 `and` および `or` はよく**短絡演算子**と呼ばれる<br>※引数（演算対象）の評価が左から右に行われ、結論が決定した時点で評価をやめるため
- `if A and C` が真のときも、Bが偽であれば `A and B and C` は式Cを評価しない
- ブール値でなく一般値が使われた時は、短絡演算子の返り値は最後に評価された引数となる

```pycon:比較その他のブール式の結果は変数に代入できる
>>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
>>> non_null = string1 or string2 or string3
>>> non_null
'Trondheim'
```

## シーケンスの比較、その他の型の比較
- シーケンスオブジェクトは、同じシーケンス型を持つオブジェクトと比較できる
- 比較には辞書的順序を使用する
- 最初のアイテム動詞を比較し、両社が異なっていればその大小が結論として使われ、同じであれば2番めのアイテム動詞の比較にいく
- 比較されている2つのアイテム動詞がまた同じシーケンス型同士だった場合、辞書的比較が再帰的に行われる
- 文字列の辞書的順序には、個々の文字のUnicodeコードポイント番号を使う
- ことなる型のオブジェクト同士を `<` や `>` で比較することも、それらのオブジェクトが適切な比較メソッドを有す限り正当な操作である
- たとえば異なる数値型同士は、その数字の値で比較されるので、0と0.0は等しくなる
- このような場合以外、インタープリタは懇意的な順序づけを提供することはせず、TypeError例外を創出する

## 用語
### インプレース
- 元のデータを置き換えるやりかた
- コピーを取らず、そのリストオブジェクトを直接操作する

### シャローコピー
- 浅いコピー (shallow copy) は新たな複合オブジェクトを作成し、その後 (可能な限り) 元のオブジェクト中に見つかったオブジェクトに対する 参照 を挿入します
- 参考：[Python標準ライブラリ　8.10. copy — 浅いコピーおよび深いコピー操作](https://docs.python.jp/3/library/copy.html)