操作対象としてのリスト
map 関数は、2.1 組み込み関数 によると、
map(function, list, ...)
function を list の全ての要素に適用し、返された値からなるリストを返します。
操作対象のリストがあり、それを操作する関数を定義するというイメージ。
例えば、リストの各要素を 2 倍したいなら、
print map(lambda x: x*2, [1,2,3,4,5])
map 関数の第 2 引数のリストに対して、第 1 引数の関数を適用する。
リスト内包表記を使うなら、(cf. Python のリスト内包表記)
print [x*2 for x in [1,2,3,4,5]]
対象のリストがあって、そこから一つずつ取り出して関数を適用する。
リストは「料理される側」であって、関数はそれに対する「包丁」というイメージが自分の頭に固定された。 (+_+)
手段としてのリスト
そういう固定観念があったので、前回「Python のジェネレータ (3)」で参考にした「数字にコンマを振る」の記事のコメントにあった map 関数の使い方を見て、なるほどと思った。なぜかと言うと、 map の第 2 引数が「関数全体の目的」に対して「手段」として使われていたから。具体的には、他のリストを参照するための手段としてのリスト。
例えば、
a = "hogepiyofuga" print map(lambda x: a[x], [1,4,7,8])
結果は、
['o', 'p', 'o', 'f']
map 関数の第 2 引数が、他の変数を参照するための手段として使われている。 map 関数の外にある変数を、無名関数 lambda の中から参照。
リスト内包表記で書くなら、
print [a[x] for x in [1,4,7,8]]
頭が固いので、こういうイメージ、 map に対して全然わかなかった。 (+_+)
普通に書くとすると、対象を中心に考え、
print [e for i,e in enumerate("hogepiyofuga") if i in [1,4,7,8]]
map と filter 関数を使うならば、
print map(lambda (i,e): e, filter(lambda (i,e): i in [1,4,7,8], enumerate("hogepiyofuga")))
Ruby では?
Ruby で書くなら、
str = "hogepiyofuga".split(//s) p [1,4,7,8].map{|e| str[e]}
あ~、そうだ。これ書いていて、以前から違和感を感じていたコードの理由がなんとなくわかった。脱線してしまうが、 Ruby では 繰り返しの for が内部で呼出しているというイテレータ。例えば、ブロックを 5 回実行するには、
(0...5).each{|i| p i}
for 文の書き方に比べて感覚的にしっくり来なかった。 ( 10.times do … という書き方はしっくり来るんだけど ^^; )
繰り返しの処理を行うとき、意識の中ではブロックの中に記述されているコードが処理・関心の中心にある。繰り返す回数は、あくまでも回数をこなすためにカウントする手段に過ぎない。問題に対して脇役というイメージ。だから、その手段となっている Range オブジェクト が最初に来て、主語のように居座り、それに対して処理をお願いするという形がピンと来なかった。上記の map の使い方もイメージとしては同様に感じていたために、何か微妙な感じがしたのかな。
ついでなので、対象の方を「主」にして書くと、
result = "" "hogepiyofuga".split(//s).each_with_index do |e,i| result << e if [1,4,7,8].any?{|elem| elem == i} end p result
あれ?思ったよりも長くなった… (@_@;) 書き方違ってるのかな?
lambda
ちなみに、Python の 5.11 ラムダ (lambda) によると、
ラムダ形式で作成された関数は、実行文 (statement) を含むことができないので注意してください。
代入は、「代入文 (assignment statement)」、つまり statement の一種なので (cf. 6. 単純文 (simple statement))、 lambda の中に含むことはできない。 Ruby の proc{}, lambda{} とは異なる。(cf. Ruby のブロックと Proc)
だから、次のコードを実行しようとすると、「exceptions.SyntaxError: lambda cannot contain assignment」 というエラーが表示されてしまう。
a = list("hogepiyofuga") map(lambda x: a[x] = "X", [1,4,7,8])
まぁ、しかし上記のような目的にそんな風には書かないか ^^; 普通は、
result = "" for i,e in enumerate("hogepiyofuga"): result += "X" if i in [1,4,7,8] else e print result
ジェネレータを使うなら、
def g(): for i,e in enumerate("hogepiyofuga"): yield "X" if i in [1,4,7,8] else e print "".join(x for x in g())
上記のジェネレータを一般化すると、
def g2(str, L, x): for i,e in enumerate(str): yield x if i in L else e print "".join(x for x in g2("hogepiyofuga", [1,4,7,8], "X"))
どうしても、 lambda の中で外の変数を変更したいなら、オブジェクトのメソッド呼出しにすればいいか~。
class StringWrapper: def __init__(self, str): self.str = str def __getitem__(self, key): return self.str[key] def __setitem__(self, key, value): self.str = self.str[:key] + value + self.str[key+len(value):] def set(self, key, value): self.__setitem__(key, value) def __str__(self): return self.str s = StringWrapper("hogepiyofuga") # 参照 print map(lambda x: s[x], [1,4,7,8]) # 変更 map(lambda x: s.set(x,"X"), [1,4,7,8]) print s
(cf. Python で要素を添字で参照する - 特殊メソッドを使って)
しかし、もうこれは lambda を使う意味がなくなってる … ^^; これなら StringWrapper に、先ほどのジェネレータ g2 に相当するメソッドを作って、オブジェクトに対して普通にメソッド呼出しをするよね…。
余談
map 関数のドキュメントを読みなおしてはじめて気がついたけれど、これって複数のリストに対して適用できるのかぁ~ (@_@)
map(function, list, ...)
… 追加の list 引数を与えた場合、 function はそれらを引数として取らなければならず、関数はそのリストの全ての要素について個別に適用されます; 他のリストより短いリストがある場合、要素
None
で延長されます。(2.1 組み込み関数 の map より)
例えば、
print map(lambda x,y: x+y, [1,2,3],[10,20,30]) print map(lambda x,y,z: x+y+z, [1,2,3],[10,20,30],[100,200,300]) print map(lambda *x: [a*2 for a in x], [1,2,3],[10,20,30],[100,200,300])
結果は、
[11, 22, 33] [111, 222, 333] [[2, 20, 200], [4, 40, 400], [6, 60, 600]]
function が
None
の場合、恒等関数であると仮定されます (同上より)
恒等関数 – Wikipedia とは、
zip 関数の説明には次のようにある。
zip() は初期値引数が
None
の map() と似ています。
例えば、
print map(None, [1,2,3],[10,20,30])
結果は、
[(1, 10), (2, 20), (3, 30)]
追記(2008.9.25) : 違いについては、Python の zip と map の違い参照。