Python のリスト内包表記と Ruby の map

内包表記は Ruby は map でいけるんじゃね?という話。

pythonの内包表記を少し詳しく
の例を使ってみる。

例えば Python

[i for i in range(10)]

というのは

(0...10).map(&:itself)

とか。まあこれは Array.new(10, &:itself) とか 10.times.to_a とかもっとかんたんに [*0...10] でいいのだけれど。

また、

[i for i in range(10) if i%2==0]

だと select をそのまま使って

(0...10).select {|i| (i % 2).zero?}

ですね。これもしかし

0.step(9, 2).to_a

とした方がよい感じ。step() は Enumerator を返すのでこれができる。一般に、Enumerator を返すメソッドは配列の生成に便利。

Python

[i if i%2==0 else str(i) for i in range(10)]

だと、Ruby では三項演算子を使って

(0...10).map {|i| (i % 2).zero? ? i : i.to_s}

かな。


ここPython

[('fizzbuzz' if x % 15 == 0 else ('fizz' if x % 3 == 0 else ('buzz' if x % 5 == 0 else x))) for x in range(1, 30)]

というのがあったけど、これは Ruby だと

(1...30).map {|i| if (i % 15).zero? then "fizzbuzz" elsif (i % 3).zero? then "fizz" elsif (i % 5).zero? then "buzz" else i end}

ですかねえ。でもこれは Python 文化ならともかく、Ruby ではあり得ない気がする。同じところの例で

[x for inner_list in [[1, 3], [5], [7, 9]] for x in inner_list]

が二重ループの例として挙げられているのは、Ruby だと flat_map を使って敢て二重ループで

[[1, 3], [5], [7, 9]].flat_map {|i| i.map(&:itself)}

となりますね。まあこれは例がよくないので、じつは

[[1, 3], [5], [7, 9]].flat_map(&:itself)

でいいし、さらには [[1, 3], [5], [7, 9]].flatten でおしまいだけれど。


こうして見ると、内包表記はすごく便利なのだけれど、Ruby でもまあまあ簡潔に書けることがわかりますね。可読性もそんなに悪くないと思う。内包表記で Ruby ではうまく書けないとか、あるのかな。



ここでおもしろいもの見つけた。Python

[f(x) for x in lst if f_cond(f(x))]

とすると f(x) が二回呼び出されるのが気に喰わないというもの。解決策は

[f_x for f_x in [f(x) for x in lst] if f_cond(f_x)]

としておられる。なるほど。可読性がいまいちかな。Ruby だと素直に

lst.map {|i| f(i)}.select {|i| f_cond(i)}

だな。