Pythonのデータ構造と操作（Python学習メモ③）

今回はPythonで扱うオブジェクトのデータ構造についての学習メモです

## リストで使えるメソッド

### 要素の追加

#### list.append(x)
リストの末尾にアイテムを1つ追加する
a[list(a):] = [x]と等価

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

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

### 要素の削除、取出し

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

#### list.pop([i])
指定された位置のアイテムをリストから削除し、このアイテムを返す
インデックスが指定されていない場合は最後のアイテムを返し、リストから削除する
※`[]`はオプションの意。実際`[]`を記述するわけではない

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

### その他操作

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

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

#### list.sort(key=None, reverse=False)
リストオブジェクトを直接ソートする（リストのコピーを取らず直接変更を加える）
引数でソートのカスタマイズができる

#### list.reverse()
リストオブジェクトを直接逆順にする

#### list.copy()
リストのシャローコピーを返す
a[:]と等価

```python
a = [66.25, 333, 333, 1, 1234.5]

print(a.count(333), a.count(66.25), a.count('x'))
# 2 1 0

a.insert(2, -1)
a.append(333)
print(a)
# [66.25, 333, -1, 333, 1, 1234.5, 333]

print(a.index(333))
# 1

a.remove(333)
print(a)
# [66.25, -1, 333, 1, 1234.5, 333]

a.reverse()
print(a)
# [333, 1234.5, 1, 333, -1, 66.25]

a.sort()
print(a)
# [-1, 1, 66.25, 333, 333, 1234.5]

print(a.pop())
# 1234.5

print(a)
# [-1, 1, 66.25, 333, 333]
```

## リストをスタックとして使う
ポイント

* リストのメソッドを使うと、リストをスタックとして使うのが簡単になる

```python:後入れ先出の例
stack = [3, 4, 5]

stack.append(6)
stack.append(7)

print(stack)
# [3, 4, 5, 6, 7]

print(stack.pop())
# 7

print(stack)
# [3, 4, 5, 6]

print(stack.pop())
# 6
```

## リストをキューとして使う
ポイント

* 同じようのキューとしても便利に使うことができる
* しかし、append,popと違いリストの先頭でのinsertやpopは低速なので、キューの実装には`collection.deque`を使うべき


```python:collection.dequeを使ったキュー(先入れ先出し)の実装
from collections import deque

queue = deque(["Bill", "Earl", "Sam"])
queue.append("Andy")
queue.append("Chris")
print(queue.popleft())
# Bill
print(queue.popleft())
# Earl
print(queue)
# deque(['Sam', 'Andy', 'Chris'])
```

## リスト内包
ポイント

* 式とそれに続くfor節から成る
* さらに0個以上のfor節やif節を後ろに続け、全体を`[]`で囲む
* 最初の式を後続のfor節やif節で評価した値による新しいリストが得られる
* ほかにもリストのすべての要素に共通の処理を行いたい場合などに便利

```python:2乗数のリストを生成するシンプルな例
# 2乗数のリストを生成
squares = []
for x in range(10):
    squares.append(x**2)
    
print(squares)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 

# 上の処理によってxという変数が残ってしまう
print(x)

# 以下のようにすると副作用なくリストを作成できる
squares = list(map(lambda x: x**2, range(10)))

# ↑をリスト内包で書くと以下のようになる
squares = [x**2 for x in range(10)]
```

```python:もうちょっと複雑な例
combs = [(x, y) for x in [1, 2, 3] for y in [3, 1, 4] if x != y]
print(combs)
# [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

# これは以下と等価
combs = []
for x in [1, 2, 3]:
    for y in [3, 1, 4]:
        if x != y:
            combs.append((x, y))

print(combs)
# [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
```

```python:ほかの便利な使い方
vec = [-4, -2, 0, 2, 4]
# 値を2倍にした新しいリストを生成
double = [x*2 for x in vec]

print(double)
# [-8, -4, 0, 4, 8]

# 負の数を除去するフィルター
positive_list = [x for x in vec if x >= 0]
print(positive_list)
# [0, 2, 4]

```

## 入れ子のリスト内包
ポイント

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

```python:配列の行と列を入れ替える
matrix = [
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12]
]

# 行と列を入れ替える
reversed_matrix = [[row[i] for row in matrix] for i in range(4)]
print(reversed_matrix)
# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
```

* 現実的には複雑なフローはビルトイン関数を使うべき

```python:zip関数の例
matrix = [
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12],
]

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

## del文
ポイント

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

```python:del文を使用する例
a = [-1, 1, 66.25, 333, 333, 1234.5]

# インデックスで指定
del a[0]

print(a)
# [1, 66.25, 333, 333, 1234.5]

# スライスで指定
del a[2:4]

print(a)
# [1, 66.25, 1234.5]

# リスト全体の消去
del a[:]

print(a)
# []

# 変数を丸ごと削除（これ以降はaを参照するとエラーになる）
del a

```

### タプルとシーケンス
ポイント

* タプルのアイテムに代入を行うことは不可能
* リストなどの変更可能オブジェクトを含んだタプルを生成することは可能
* タプルは変更不能(immutable)であり、通常、異種の要素によってシーケンスを作る
* 各要素にはアンパッキングやインデックス、属性でアクセスするというのが通例
* リストと似ているが、リストは変更可能(mutable)で、普通は同種の要素から成り、リストに反復をかけることでこれらにアクセスする

```python:タプルの操作
# タプルはカンマで区切られた値からなる（タプルパッキング）
t = 12345, 54321, 'hello!'

# 以下の方法でアンパッキングも可能(シーケンスアンパッキング)
x, y, z = t
print(x) # 12345
print(y) # 54321
print(z) # hello!

print(t)
# (12345, 54321, 'hello!')

# タプルは入れ子にできる
u = t, (1, 2, 3, 4, 5)

print(u)
# ((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))

# タプルは変更不能
# t[0] = 88888

# 変更可能オブジェクトは格納できる
v = ([1, 2, 3], [3, 2, 1])
print(v)
# ([1, 2, 3], [3, 2, 1])

# 空のタプル
empty = ()

# 要素数が1のタプル
singleton = 'hello', <- 末尾にカンマをつける
```

## 集合(set)
ポイント

* 集合とは、重複しない要素を順不同で集めたもの
* 基本的な用途としては存在判定や、重複エントリの排除がある
* 集合オブジェクトは、和、交差、差、対称差といった数学的演算をサポートしている
* 集合の生成には中かっこ`{}`または`set()`関数を使用する
* 空の集合を生成するには`{}`ではなく`set()`を使う必要がある（前者は空のディク書なりを生成してしまう）

```python:setの例
basket = {'apple', 'orage', 'apple', 'pear', 'orange', 'banana'}
print(basket)
# {'banana', 'pear', 'apple', 'orage', 'orange'}

print('orange' in basket) # 高速な存在判定
# True
print('crabgrass' in basket)
# False

# 2つの単語からユニークな文字をとって集合演算
a = set('abracadabra')
b = set('alacazam')

# aのユニーク文字
print(a)
# {'r', 'a', 'c', 'b', 'd'}

# aに存在し、bにはない文字
print(a - b)
{'r', 'b', 'd'}

# aまたはbに存在する文字
print(a | b)
# {'z', 'r', 'a', 'c', 'b', 'd', 'l', 'm'}

# aにもbにも存在する文字
print(a & b)
# {'c', 'a'}

# aまたはbにある共通しない文字
print(a ^ b)
# {'m', 'b', 'l', 'z', 'd', 'r'}

# リスト内包とよく似た集合内包もサポートされている
a = {x for x in 'abracadabra' if x not in 'abc'}
print(a)
# {'r', 'd'}
```
## ディクショナリ
ポイント

* キーによるインデックス
* キーにはあらゆる変更不能型が使える
* 文字列や数値は常にキーとして使える
* タプルもキーとして使える(ただし、文字列、数値、タプルのみを含む場合)
* 可変型のオブジェクトが直接間接に含まれているタプルは、キーとして使えない
* リストもキーとして使えない
* ディクショナリのキーはユニークであること
* 中カッコを書けば空のディクショナリになる`{}`

```python:ディクショナリ型の用例
# 初期化
tel = {'jack': 4098, 'sape': 4139}

# 追加
tel['guido'] = 4127
print(tel) # {'jack': 4098, 'sape': 4139, 'guido': 4127}
print(tel['jack']) # 4098

# 削除
del tel['sape']
tel['irv'] = 4127
print(tel)
# {'jack': 4098, 'guido': 4127, 'irv': 4127}

# キーのリストを取得
print(list(tel.keys()))
# ['jack', 'guido', 'irv']

# キーでソート
print(sorted(tel.keys()))
# ['guido', 'irv', 'jack']

# 存在チェック
print('guido' in tel) # True
print('jack' not in tel) # False

# dict()コンストラクタは、キー:値ペアのタプルからなるシーケンスから
# ディクショナリを構築する
tel2 = dict([('sape', 4139), ('guide', 4127), ('jack', 4098)])
print(tel2)
# {'sape': 4139, 'guide': 4127, 'jack': 4098}

# 辞書内包を使えばキーと値を与える任意の式からディクショナリが生成できる
print({x: x**2 for x in (2, 4, 6)})
# {2: 4, 4: 16, 6: 36}

# キーが文字列なら、キーワード引数でペアを指定するのが楽な場合もある
tel3 = dict(sape=4139, guido=4127, jack=4098)
print(tel3)
# {'sape': 4139, 'guido': 4127, 'jack': 4098}

```

## ループのテクニック
ポイント

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

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

# gallahad the pure
# robin the brave
```

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

```python
for i, v in enumerate(['tic', 'tac', 'toe']):
    print(i, v)

# 0 tic
# 1 tac
# 2 toe
```

* 2つ以上のシーケンスに同時にループをかけるときは`zip()`関数を使うと両者のエントリをペアにできる
* ちなみに配列の数が異なってペアが作れない場合は多い方が省略される

```python
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()`関数をコールする

```python
for i in reversed(range(1, 10, 2)):
    print(i)
    
# 9
# 7
# 5
# 3
# 1
```

* シーケンスをソート順にループするには`sorted()`関数を使う。この関数は元のシーケンスには触らず、新たにソート済みのリストを返す
 
```python
basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']

for f in sorted(set(basket)):
    print(f)
    
# apple
# banana
# orange
# pear
```

* リストの中身を改変したいときは新しいリストを作った方が簡単で安全

```python


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)
        
print(filtered_data)
# [56.2, 51.7, 55.3, 52.5, 47.8]
```

## オブジェクトの比較で使う条件について
ポイント

* 比較演算子`is`および`is not`は2つのオブジェクトを比較して完全に同一であるかを調べる
* 比較演算子の優先順位はすべて同等で、どれもすべての数値演算子より低い
* 比較はブール演算子の`and`および`or`により組み合わせることができる
* 比較の結論（およびほかの全ブール式）は`not`による否定ができる。
* and, or, notの優先順位は比較演算子より低い
* 優先順位は not > and > or
    * A and not B or C は (A and (not B)) or C と等価
* and および or は短絡演算子と呼ばれる
* and および or は演算対象の評価が左から右に行われ、結論が決定した時点で評価をやめる
    * A and B and C とあったとき、Bが偽であればCは評価しない
* ブール値ではなく一般値が使われたときは、短絡演算子の返り値は最後に評価された引数となる

```
string1, string2, string3 = '', 'Tronbheim', 'Hammer Dance'
non_null = string1 or string2 or string3
print(non_null)
# Tronbheim
```

## シーケンスの比較、その他の型の比較
ポイント

* シーケンスオブジェクトは、同じシーケンス型を持つオブジェクトと比較できる
* この比較には辞書的順序を使用する
* 比較の順序は以下
    * まず最初のアイテム同士を比較し、両者が異なっていればその大小が結論として使われ、同じであれば2番目のアイテム同士の比較にいく
    * 同じアイテムが続くなら、どちらかのシーケンスがなくなるまで比較が続けられる
    * 比較されている2つのアイテム同士がまた同じシーケンス型同士だった場合、辞書的比較が再帰的に行われる
    * 2つのシーケンスのアイテムがすべて同一であれば、両シーケンスは同一であると考えられる
* 2つが基本的に同じシーケンスで、片方の長さが短いときはこの短い方が小となる
* 文字列の辞書的順序には、個々の文字のUnicodeコードポイント番号を使う

```python:オブジェクトの比較
(1, 2, 3) < (1, 2, 4)
[1, 2, 3] < [1, 2, 4]
'ABC' < 'C' < 'Pascal' < 'Python'
(1, 2, 3, 4) < (1, 2, 4)
(1, 2) < (1, 2, -1)
(1, 2, 3) == (1.0, 2.0, 3.0)
(1, 2, ('aa', 'ab')) < (1, 2, ('abc', 'a'), 4)
```