【自分用メモ】Pythonでデータサイエンスをするときに役立ちそうなTips: yield, partial, map, filter, reduce, enumerate

※本記事は[Data Science from Scratch](http://shop.oreilly.com/product/0636920033400.do)のChapter2に出てきたTipsの自分用メモです。
参考になりそうなリンクと簡単な使い方だけ書いてます。（Python3です）

# yield
`yield`についてのイメージは、[こちらのブログ](http://ailaby.com/yield/)がわかりやすく、その後[こちらのブログ](http://giraffe.topaz.ne.jp/wiki/doku.php/py:lesson14)を参考にすると理解が深まります。
簡単に言うと、「`return`を使うとその行で処理が終わって値をすべて戻してしまうので、膨大なデータを用いた繰り返し作業を行う場合、繰り返しごとにメモリを無駄に消費して大変だよね。そういうときには`yield`っていう便利な物があるんだよ」ということだと思います。

```python3:yield.py
def func():
    a = 10
    b = 20
    c = a + b
    yield c      # ここで一旦停止
 
    a = 100
    b = 200
    c = a + b
    yield c      # ここで一旦停止
 
    a = 1000
    b = 2000
    c = a + b
    yield c      # ここで一旦停止
 
 
for x in func():
    print (x)
 
#---- 結果 -----
# 30
# 300
# 3000
#---------------
```

# partial
>When passing functions around, sometimes we’ll want to partially apply (or curry) functions to create new functions.

「関数回してると時々関数の一部を使って新しい関数を作りたい時あるよね〜」な人に役立つのが`partial`だそうです。

使用時には`functools`から`import`する必要があります。

```python3:exp.py
# 例えば base を power乗 する関数を作る
def exp(base, power):
    return base ** power
```
これを使って2のx乗を計算する関数を作る場合こんな風に書きますよね。

```python3:two_to_the.py
def two_to_the(power):
    return exp(2, power)
```
でも`partial`を使うとこんな風に書けます。

```python3:partial.py
from functools import partial
two_to_the = partial(exp, 2)   # 最初の引数で使用する関数、2つ目の引数でbaseを指定
print two_to_the(3)            # 8
```

参照する関数の別の引数を指定したい場合は、その引数の名前を含めて指定します。

```python3:latter_arg.py
square_of = partial(exp, power=2)   # 今回はpowerを指定
print square_of(3)                  # 9
```

この`partial`すごく便利らしいので以下の説明でも`partial`を使って慣れようと思います。

#Pythonの基本的な高階関数（higher-order functions）

`map`, `filter`, `reduce`は高階関数と呼ばれるものだそうです。
>高階関数とは、英語の「 higher-order function 」の訳で、「他の関数を引数として受け取る関数」のことです。たとえば、リストの各要素に共通の処理を加えたリストを作成したい場合なんかに、処理をわかりやすく簡潔に記述することができます。
by [LIFE WITH PYTHON](https://www.lifewithpython.com/2013/12/python-higher-order-functions-in-standard-library.html)

## map
`map(x, y)`は`x(i) for i in y`です。

```python3:map.py

def double(x):
    return 2 * x

xs = [1, 2, 3, 4]

twice_xs = [double(x) for x in xs]   # [2, 4, 6, 8]
twice_xs = map(double, xs)           # same as above

list_doubler = partial(map, double)  # *function* that doubles a list
twice_xs = list_doubler(xs)          # [2, 4, 6, 8]
```

複数の引数を持つリストが与えられていれば、複数の引数を持つ関数にも使用することができるそうです。

```python3:multiple_arg_map.py
def multiply(x, y):
    return x * y

products = map(multiply, [1, 2], [4, 5])   # [1 * 4, 2 * 5] = [4, 10]
```

## filter
`filter(x, y)`は`i for i in y if x(i)`です。

```python3:filter.py
def is_even(x):   # True if x is even, False if x is odd
    return x % 2 == 0

x_evens = [x for x in xs if is_even(x)]   # [2, 4]
x_evens = filter(is_even, xs)             # same as above

list_evener = partial(filter, is_even)    # *function* that filters a list
x_evens = list_evener(xs)                 # [2, 4]
```

## reduce
複数の要素を1つにまとめたいときに使う。
具体的には配列の先頭から2つの要素を取り出して処理を行い、次はその結果とその次の要素に処理を行う…ということを繰り返すそうです。
python3からは`functools`から`import`しないとダメになったそうです。

```python3:reduce.py
from functools import reduce

def multiply(x, y): return x * y

x_product = reduce(multiply, xs)           # = 1 * 2 * 3 * 4 = 24

list_product = partial(reduce, multiply)   # *function* that reduces a list
x_product = list_product(xs)               # 24
```

# enumerate
ループ処理を行うときに`enumerate`を使うと、要素のインデックスと要素の両方を同時に取得できます。

例えば`documents`というリストの要素とインデックス両方に`do_something`という処理を繰り返し行う場合

```python3:do_something_1.py
 # not Pythonic
for i in range(len(documents)):
    document = documents[i]
    do_something(i, document)
```

とすれば良いのですが、Pythonicじゃないそうです。
なので、`enumerate`を使い`(index, element)`のタプルを作ります以下のようにします。

```python3:enumerate.py
for i, document in enumerate(documents):
    do_something(i, document)
```
仮にインデックスのみを取得したい場合は以下のようにします。（2行目が好ましい）

```python3:enumerate_index.py
for i in range(len(documents)): do_something(i)     # not Pythonic
for i, _ in enumerate(documents): do_something(i)   # Pythonic
```

## enumerateとzipの違い

```python3:enumerate_vs_zip.py
a = [a1, a2, a3]
b = [b1, b2, b3]

# enumerate
for i, a_i in enumerate(a):
    print(i, a_i)

#---- 結果 -----
# 0 a1
# 1 a2
# 2 a3
#---------------


# zip
for a_i, b_i in zip(a,b):
    print(a_i, b_i)

#---- 結果 -----
# a1 b1
# a2 b2
# a3 b3
#---------------
```

#参照
- [Data Science from Scratch](http://shop.oreilly.com/product/0636920033400.do) 
- [python の yield。サクッと理解するには return と比較](http://ailaby.com/yield/)
- [yieldのちょっとした理解](http://giraffe.topaz.ne.jp/wiki/doku.php/py:lesson14)
- [LIFE WITH PYTHON](https://www.lifewithpython.com/2013/12/python-higher-order-functions-in-standard-library.html)