2008年9月30日火曜日

Python の map, filter, reduce とリスト内包表記

1. リストを操作する関数で重要なのはどれ?

リスト内包表記は慣れたら使いやすい

Python のリスト内包表記に出会って 4 ヶ月が経った。

Python のリスト内包表記」を読みなおしてみると、

… 同じく数値のリストから、特定の条件に合う要素を抽出する。

print [x for x in [1,2,3,4,5] if x > 3]

これまた読みにくい。 (@_@;)

と書いていたけれど、今では「シンプルで読みやすく、また書きやすい」と思える。慣れとは恐ろしい。 ^^;

(リスト内包表記がネストしてたりすると、すぐに理解出来ないけれど。)

 

Ruby の Enumerable モジュールにはたくさんのメソッドが定義されている

Java しか知らなかった頃、Ruby の 配列に定義されているメソッドを見て、「便利なメソッドがたくさんあるなぁ」と思った。Ruby の配列は、Enumerable モジュールをインクルードしており、そこにいくつかメソッドが定義されている。

配列だけではなく、自分でクラスを定義した場合でも、 Mix-in の機能により、each を定義するだけで簡単にパワーアップ。

しかし、 Enumerable モジュールに定義されている、20個程度のメソッドを見ても、どれが重要なのかわからない。 (@_@;) 

sort メソッド以外では、

  • each
  • each_with_index

ばかりに目が行ってしまい、

  • map
  • select
  • inject

なんて便利なんだけど脇役だと思ってた。

 

Haskell, Python でも同じような関数が定義されている

Haskell, Python を触るようになって、上記の印象が変わった。両方とも、リスト内包表記が特別に用意されている言語。

例えば、Haskell の Prelude の目次を見ると、List operations として真っ先に map が定義されている。リストの結合(++) に続いて filter , 少し間を空けて、Reducing lists (folds) がある。

あ~、なるほど、これらが重要な関数なんだと理解できた。

map, filter, reduce 関数が重要だと認識しても、昔からの慣習の力はすごい。 (+_+) リストの要素に関数を適用することを考える場合、for ループで要素を走査するイメージのしやすは、コードを書くときのの強力な引力として働く。

for ループを書くこと自体、別に悪くない。しかし、面倒くさがりやなので、お決まりのコードはなるべく短く書きたい。そこで、for ループとの対応を明確にして、map, filter, reduce 関数の使い方を練習しておく。できれば、リスト操作を考えるとき、ループをイメージするのを飛びこして、 map, filter, reduce へと思考がダイレクトに反応することを願って。 o(^^)o

 

2. リスト全体に対する計算 : reduce

合計

リストを走査して合計を求める。

L = [1,2,3,4,5]

result = 0
for e in L:
    result += e
print result

以後、リスト L を使う。

reduce を使えば、

print reduce(lambda a,b: a+b, L)

合計に関しては特別に、

print sum(L)

 

総乗
result = L[0]
for e in L[1:]:
    result *= e
print result

reduce を使えば、

print reduce(lambda a,b: a*b, L)

 

リストの要素全体に渡って、何らかの演算をした結果を積立てていきたいときは reduce を思い出すこと。

(1+2)+3)+4)+5)

 

3. リストの要素に演算を適用 : map

例えば、要素を 2 倍する

result = []
for e in L:
    result += [e*2]
print result

map, リスト内包表記を使うと、それぞれ、

print map(lambda x: x*2, L)
print [x*2 for x in L]

reduce を使うなら、

print reduce(lambda a,b: a+[b*2], L, [])

空のリストを初期値として渡し、要素に対して演算を適用した後はリストにするところがポイント。

 

4. リストの要素を抽出 : filter

例えば、 3 より大きい要素を得る場合、

result = []
for e in L:
    if e > 3:
        result += [e]
print result
filter, リスト内包表記を使うなら、 それぞれ、
print filter(lambda x: x>3, L)
print [x for x in L if x > 3]

reduce を使うなら、

print reduce(lambda a,b: a+[b] if b > 3 else [], L, [])

空のリスト[] をリストに足しても変わらないことがポイント。

 

5. リストの要素を抽出して演算を適用 : filter と map

例えば、3 より大きい要素を得て 2 倍する

result = []
for e in L:
    if e > 3:
        result += [e*2]
print result

filter と map 、リスト内包表記を使うと、それぞれ

print map(lambda x: x*2, filter(lambda x: x > 3, L))
print [x*2 for x in L if x > 3]

reduce を使うなら、

print reduce(lambda a,b: a+[b*2] if b > 3 else [], L, [])

やはりリスト内包表記はシンプルでいい。 ^^