HEXA BLOG

プログラム

HEXA BLOGプログラム2014.10.30

関数型プログラミングをpythonで始めてみよう ! Func.2

お久しぶりです、コウスケです。

 

前回は、関数型プログラミングを簡単に紹介しました。
関数型言語は関数を変数、すなわち引数や返り値として
用いることが出来る

のが特徴でしたが、今回紹介する

map, filter, reduce はその恩恵を充分に
実感できる機能です。

 

関数型言語が提供する肝となる要素であり、概念になります。
前回と同様, pythonを用いて説明をしていきたいと思います。

 

さっそくですが、一連の要素(リストと呼びます、C言語でいう配列を想像してください)に、

・それぞれに同じ処理/操作を適用させたい
・条件に合うものだけを抜き出したい
・2つの要素に処理を適用させ、その結果を次の要素に次々と適用させたい

ということはよくあることではないでしょうか(最後がやや強引ですが・・)?

 

実はそれぞれが map/filter/reduce の機能そのものなのです。
(reduceはfoldとも呼ばれます。)
それぞれが関数として提供されています。

 

例えばmap関数は、引数に関数とリストをとり、
リストの要素に対して渡された関数を適用させる機能を持ちます。
サンプルコードで見たほうが早いと思います。

 

 [サンプルコード] <br />def sample1():<br />    # 操作対象のリスト<br />    lst = [1,2,3,4,5,6]<br />    <br />    #----------------<br />    # mapの例<br />    #----------------<br />    print "[map]-----"<br />    def double(angel):<br />        """<br />        値を2倍する関数<br />        pythonでは関数内で関数を定義できます<br />        これはドキュメンテーション文字列と呼ばれる関数コメントです<br />        """<br />        return a*2<br />    <br />    print lst, "-&gt;"<br />    # リストの要素を2倍したものを返します<br />    lst = map(double, lst)<br />    print lst<br />    <br />    #----------------<br />    # filterの例<br />    #----------------<br />    print "[filter]-----"<br />    def isMod4(angel):<br />        """<br />        4で割り切れるか判定する関数<br />        """<br />        return a%4 == 0<br />    <br />    print lst, "-&gt;"<br />    # リストの要素から4で割り切れるものを返します<br />    lst = filter(isMod4, lst)<br />    print lst<br />    <br />    #----------------<br />    # reduceの例<br />    #----------------<br />    print "[reduce]-----"<br />    def add(a, b):<br />        """<br />        与えられた2引数を加算する関数<br />        """<br />        return a+b<br />    # リストの要素を合計したものを返します<br />    sum_val = reduce(add, lst)<br />    print "sum=", sum_val

 

[実行結果]<br />[map]-----<br />[1, 2, 3, 4, 5, 6] -&gt;<br />[2, 4, 6, 8, 10, 12]<br /><br />[filter]-----<br />[2, 4, 6, 8, 10, 12] -&gt;<br />[4, 8, 12]<br /><br />[reduce]-----<br />sum= 24<br />

 

これらは一体何なのか・・何が大切なのか・・というと、
このmap/filter/reduce 関数は、C言語でいうとfor 文に相当する、
要素を走査して繰り返し何かを行う」機能自体を抽象化して、
提供しているのです。

 

こうして通常の言語ならベタに命令列として記述してしまうものを
できるだけ関数化して提供し、それらを組み合わせることで
大きな柔軟性、簡潔性が得られるわけです。

 

オブジェクト指向では「データとその操作」を抽象化しますが、
関数型では「操作(手続き)の抽象化」に重きをおいているといえると思います。

 

例えば、mapやfilterでは上述のような例だけでなく
任意の関数を与えることであらやる処理や条件を記述できるのは
容易に想像できると思いますし、
reduceでも下記のような比較関数を用意して
最大値、最小値が求められます。

 

[サンプルコード]<br />def sample2():<br />    lst = [4,8,12]<br />    #--- mapの例追加<br />    def puts(s):<br />        print s # pythonではprintは関数ではないため<br />    # lst要素の表示<br />    map(puts, lst)<br />    <br />    #--- reduceの例追加<br />    def getLarger(a, b):<br />        # pythonでの三項演算子 (a==b)? a:b の表記法です<br />        return a if a&gt;b else b <br />    def getSmaller(a, b):<br />        return a if a&lt;b else b<br />    # 最大値、最小値の表示<br />    maxval = reduce(getLarger, lst)<br />    print "max=", maxval<br />    minval = reduce(getSmaller, lst)<br />    print "min=", minval<br />---------------- 
[実行結果]<br />4<br />8<br />12<br />max= 12<br />min= 4<br />

最後にもう少し実用的な例を示したいと思います。
カレントフォルダから.txt拡張子を探し、
tmpフォルダにコピーし、合計サイズを返します。

 

[サンプルコード]<br /><br />import os, os.path<br />import shutil<br />import operator<br /><br />def sample3():<br />    # tmpフォルダを作っておく<br />    filelst = os.listdir(".") # カレントフォルダのファイルリスト<br />    if not os.path.exists("tmp"):<br />        os.mkdir("tmp")<br />    <br />    # .txt拡張子ファイルを抽出<br />    def is_text_file(fname):<br />        return os.path.splitext(fname)[-1] == ".txt"<br />    filelst = filter(is_text_file, filelst)<br />    print filelst<br />    <br />    # tmpフォルダにコピー<br />    def copy_to_tmp(fname):<br />        shutil.copy2(fname, "tmp")<br />        return os.path.join("tmp", fname) # コピー先パスを返す<br />    # サイズリストを取得:mapの適用をつなぎあわせています<br />    fsizelst = map(os.path.getsize, map(copy_to_tmp, filelst))<br />    print fsizelst<br />    <br />    # 合計サイズを表示<br />    size_sum = reduce(operator.add, fsizelst)<br />    print size_sum<br />

最後に、「面白い書き方だけど逐一関数を定義するのが面倒かな・・
とか思う人も多いと思います。

 

pythonでは「リスト内包表記」という、
より直感的でわかりやすい記述が可能です。

私も今回は無理やりmap/filter/reduceを使いましたが、
普段はこれを使っています。

興味があれば調べてみてください。

 

今回のまとめ
・map/fiter/reduceは関数型プログラミングの肝となる基本機能だ。
・map/fiter/reduceはリストの走査と要素の操作を抽象化する。
・これらの関数を組み合わせることによりリスト要素の柔軟な操作が可能になる。

次回は関数を返り値として返すことの便利さを紹介します。

 

RECRUIT

大阪・東京共にスタッフを募集しています。
特にキャリア採用のプログラマー・アーティストに興味がある方は下のボタンをクリックしてください

RECRUIT SITE