Enumerable#select と #collect を同時に行いたい
せっかくなので小ネタを。
Python でのリスト内包表記は、for と if を同時に書ける。これはループを1回まわるだけで、選択 (select) と写像 (collect) を一度に行えることを意味する。
>>> L = ['foo', 'bar', 'baz'] >>> [ x.upper() for x in L if x.startswith('ba') ] ['BAR', 'BAZ']
これと同じことを Ruby でやると、Enumerable#collect と #select を使うわけだが、そうするとループを2回まわることになり、アルゴリズム的には動作効率はよくない。
irb> arr = ['foo', 'bar', 'baz'] => ["foo", "bar", "baz"] irb> arr.select {|x| x =~ /\Aba/ }.collect {|x| x.upcase } => ["BAR", "BAZ"]
ところで最近知ったのだが、 Enumerable#grep はブロックを取れるらしく、そのブロックはちょうど Enumerable#collect のような動作を行うらしい。
というわけで、select と collect を1回のループで同時に行うには grep を使えばいいみたい。
irb> arr = ['foo', 'bar', 'baz'] => ["foo", "bar", "baz"] irb> arr.grep(/\Aba/) {|x| x.upcase } => ["BAR", "BAZ"]
これはつまり、select {|x| x =~ /\Aba/ } を grep(/\Aba/) で代用したわけだが、残念ながら grep() は select {} ほど融通はきかない。grep() の引数は '===' 演算子をサポートしたものしか指定できないので、ブロックを指定できる select と比べれば、明らかに自由度は低い。
そこで、Proc に '===' 演算子を定義してみる。中身は、単に call するだけ。
class Proc def ===(arg) self.call(arg) end end ## 使い方 proc_obj = proc {|x| p x } proc_obj === 'FOO' #=> "FOO"
これを利用すると、任意の処理を grep() の引数に渡すことができるため、grep() で Enumerable#select 相当が可能になる。つまり、1回のループで collect と select を同時に行うことが可能になる。
irb> arr = ['foo', 'bar', 'baz'] => ["foo", "bar", "baz"] irb> arr.grep(proc {|x| x =~/\Aba/ }) {|x| x.upcase } => ["BAR", "BAZ"]
正規表現ではできない例をやってみる。
irb> arr = [1, 2, 3, 4, 5] => [1, 2, 3, 4, 5] irb> arr.grep(proc {|x| x % 2 == 1 }) {|x| x*x } => [1, 9, 25]
なかなかよろしいんじゃないでしょうか。
というわけで、Proc#=== の導入を提案したい。もし Ruby 本体はだめでも、Rails なら、きっと Rails ならなんとかしてくれる!