too late amatuar programmer

[python] 辞書にmap()のような走査関数を適用したい

2011-06-30 by ebon | Lavel:

pythonを使っているとやたらと辞書を使うようになる。
そしてmapの写像という概念を心地よく感じる人間は、辞書に対して近しい走査関数がないかと思うようになる。

ここでは例として、「辞書の全ての値をunicodeに変換する」という問題を例に考えてみたい。
アプローチとして、自前の簡易版とfunctionalモジュールのmapdictの紹介をする。


最も簡易なアプローチ

手っ取り早い方法として、内包表記とdict()を用い、一度タプルにしてから
再度辞書に戻すことで目的のものを得ている。

testdict_u = dict([(k, unicode(v,'shift-jis').encode('utf-8'))
                   for k,v in testdict.items()])  

ちなみに、このdictという関数も多くの可能性を持っている。
  • 作成時、keyをクォートする必要がない
  • 既存辞書を引数に取った場合でもを上書きはせず、新しい辞書を返す

d = {'a':'b'}
dict(c='d', **d)
# 純粋に、要素の上書きするには素直に.update()使うしかないか

次に自前高階関数


例の問題だと適用したい関数が短いため苦にならないが、
複雑な処理を施したい場合、やはり高階関数化したくなる。
ここではサクッとlambdaで。

value_map = lambda f,d: dict([(k,f(v)) for k,v in d.items()])
key_map = lambda f,d: dict([(f(k),v) for k,v in d.items()])

適用したい対象がkeyかvalueによって関数を使い分けなければならない点が残念だ。

functionalモジュールのmapdict


そして本命、functionalモジュールを使った例だ。
functionalモジュールカリー化やその他の高階関数のような追加機能を含んだモジュールで、xoltor関連のプロジェクトである。
Xoltar Toolkit
インストールはxoltorをダウンロードしてきて、展開、どこかmoduleパスの通った場所に置くだけ。
compose,also()などすばらしい関数群達で幸せになれる。
ただ作成は古く、この中の一部は時を経て標準でサポートされていたりする。
それでも、今でも、以下記事を読む価値は十分あると思っている。

functionalモジュールについての解説が含まれる記事:

求めている関数はmapdict()。そのままの名前だ。
ちょっとソースを見てみよう。

def mapdict(itemfunc, dictionary):
    """
    Much like the builtin function 'map', but works on dictionaries.
    *itemfunc* should be a function which takes one parameter, a (key,
    value) pair, and returns a new (or same) (key, value) pair to go in
    the dictionary.
    """
    return dict(map(itemfunc, dictionary.items()))

処理関数、itemfuncでは、(key, value)のペアを引数に取り、またそのペアを返す
という関数であることを担保している必要がある。
※functionalモジュールの作成自体は2001年ととても古いので、今では非推奨のmap()が直接使われてたりする。   

例題に対して、mapdict()を使用した解が以下だ。

jis_to_utf8_val = lambda (key,value): (key,unicode(value, 'shitf-jis'))
dict_utf8       = lambda dict: mapdict(jis_to_utf8_val, dict) 

辞書の各key:valueペアへの単一処理と、走査処理が明確に分離され、より抽象的に扱えるようになった。

簡単な処理なら、最初の簡易アプローチが最も手軽だ。pairを渡してpairを返す関数の設計が億劫に思える。
ただ、適用したい対象がkey,valueどちらか(あるいは両方)の可能性がある以上、
pair渡しの形で走査の高階化をすることで、より抽象性を高めている。
そこがが大事なのだろう。

より抽象表現化されたコードとなった。

終わりに

それにしても高階化はイージーソリューションというか素人にも即効性がある。
より抽象的な設計を好むようになり、純度の高いutilityが増えていくのはやはり単純に楽しい。

ただ高階化を下手に押し進める害を意識する必要も懸念としてある。
最初のアプローチのように、最小サブセット主義というか、無理に汎用化する必要がないとも思える。

関数脳の恐怖などよく聞くけど、そもそも関数型を知らない人にとっては激しく拒絶反応を起こす可能性がある。慣れれば凄いラクなんだけども。
lisperやhaskererが常に不満を持って生きなければならないというのが少しだけ分かってきた。
Rubyのブロックは高階関数 の一種だと思うけど、言われてるほどそんなに書きやすいかな〜。

というか、この辞書形式への走査に対するhaskellのアプローチをまず見るべきだったな...


話変わって最近、jsでunderscore.jsという、関数型の標準関数系を提供するライブラリが人気だ。
Domの王者、jQueryが幅を利かせるクライアントサイドはまだしも、
Dom以外の処理、取り分けサーバーサイドjsなどではかなり活躍の機会がありそう。
なんちゃって関数型好きの自分にとっては乱用したくなってしまう。楽だから。。。
遅延評価的な事がやりたいのでその有用性を見ておきたい。

0 comment:

Post a Comment