書き方いろいろ

beanz (2008年11月 3日 01:18) | コメント(0) | トラックバック(0)

前回のエントリーでmap関数(mapメソッド,map演算子)について書いているが、書いているうちにいろいろと気づく事があった。

たとえばRubyのコード

#!/usr/bin/env ruby
Dir.chdir File.dirname(ENV["_"])
Dir.open(".") do |dir|
    fileAndSize = dir.select {|i| i =~ /^[^.]/}.map {|i| [i, FileTest.size(i)]}
    fileAndSize.each {|l| puts "#{l[0]} => #{l[1]}byte"}
end

では、まずEnumerableモジュールのselectメソッドを呼び出しリストの内容をふるいにかけた後mapメソッドで変換の作業をしている。
次の部分だ。

fileAndSize = dir.select {|i| i =~ /^[^.]/}.map {|i| [i, FileTest.size(i)]}

map部分では返したい値をブラケットで囲う事で単一の値ではなくリストとして返している。
これはselectメソッド、mapメソッドを連結して呼び出さずとも以下のように書ける。

fileAndSize = dir.grep(/^[^.]/) {|i| [i, FileTest.size(i)]}

同じくEnumerableモジュールのgrepメソッドを使っているがこのメソッドはまずカッコに渡したパターンで正規表現によるマッチングを行った後ブロックコード内では変換処理を行っている。そして変換された結果が帰ってくる。
grepに渡す引数はごく単純なものしか駄目なようだ。ライブラリリファレンス等を読むと"pattern === item が成立する要素"と書いてある。例えばここにコールバック関数みたいものを渡す事は出来ないらしい。

a.grep(b.include?)

こんなコードは動かない
この場合はselectを使って

a.select {|i| b.include?(i)}

とすればうまくいく。
いずれにしろ文字列なんかの場合は正規表現でフィルタリングする事はかなり多いと思うのでそういった場合はgrepで大丈夫だろう。
Perlのgrep演算子はRubyではgrepメソッドよりもselectメソッドに近い。

次にPythonのコード

#!/usr/bin/env python
import os, re
os.chdir(os.path.dirname(os.environ['_']))
fileAndSize = map(lambda i: [i, int(os.path.getsize(i))], filter(lambda i: re.match('^[^.]', i), os.listdir(".")))
for i in fileAndSize: print "%s => %sbyte" % (i[0], i[1])

のこの部分

fileAndSize = map(lambda i: [i, int(os.path.getsize(i))], filter(lambda i: re.match('^[^.]', i), os.listdir(".")))

同じようにfilter関数で絞り込まれたリストがmap関数に渡されている。
Pythonの場合filterもmapも第一引数は関数オブジェクトでなければならない。
どんな短い関数でも定義しなければいけないところが若干面倒だがlambdaを使えばそれぞれの関数内に閉じ込めておける。
ここをリスト内包表記を使って書くと以下のようになる。

fileAndSize = [[i, int(os.path.getsize(i))] for i in os.listdir(".") if re.match('^[^.]', i)]

分かりづらいコードだが
まずリスト内包表記とは、簡単なサンプルで説明すると以下のような感じだ

list1 = [1,2,3,4,5]
list2 = [i + 1 for i in list1]

list2は[2, 3, 4, 5, 6]となっている。

これはforループで

list2 = []
for i in list1:
    list2.append(i + 1)

と同じ結果になる。
前回の例だと

fileAndSize = []
for i in os.listdir("."):
    if re.match('^[^.]', i):
        fileAndSize.append([i, int(os.path.getsize(i))])

を表している事になる。
リスト内包表記はかなり柔軟な書き方が出来てループの入れ子なんかも表現できる。
さらにカッコで適切にくくれば

[i + " Hello!" for i in (i + " Hello!" for i in a if i in b)]

こんな風にリストの変換の変換みたいな事も出来る
もちろんコードを短くすればいいってもんではないが(たいていは可読性が悪くなる)書き捨てのスクリプトや自分しか読まないコードなどではすごく重宝する。

Perlで

foreach my $line (@list) {
    print "$line\n";
}

こうではなく

print "$_\n" for(@list);

こう書けるのもすごくありがたい。

カテゴリ:

トラックバック(0)

トラックバックURL: http://blog.beanz-net.jp/beanz_mtos/mt-tb.cgi/13

コメントする