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)]
(0...10).map {|i| (i % 2).zero? ? i : i.to_s}
かな。
[('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)}
だな。