Enumerable#injectって分かりやすいのかな
CSVって、RFC4180とかに仕様がまとまってるんだとマイルドに驚きつつ、RubyのCSVクラスをのぞいてみた。FasterCSVと呼ばれていたものが正式にRubyに入り、1.9系のRubyではインターフェースが変わっているという話。CSVライブラリのソースコードには、冗長といっていいほどコメントが豊富にあるし、Rubyでライブラリを実装するお手本のような感じもある気がする。つらつら読んでいて気になったのは、CSV::Row#to_hashにある、次のようなコード。
def to_hash # flatten just one level of the internal Array Hash[*@arr.inject(Array.new) { |ary, pair| ary.push(*pair) }] end # CSV.new([["a", 2], ["b", 3], ["c", 5]]).to_hash # => {"a"=>2, "b"=>3, "c"=>5}
もともとCSV::RowクラスがArrayとHashの両方の性質を備えているとはいえ、この節操のないArrayとHashの混ざり具合は、どうなんだろうか。スプラッタ演算子の濫用も気になる。というか、パッと見て意味が分からなかった。
これは、
def to_hash hash = { } @arr.each {|pair| hash[pair.first] = pair.last} hash end
と書くといいんじゃないかしら。injectを使うにしても、injectすべきなのは、hashのほう。
def to_hash @arr.inject(Hash.new) {|h, pair| h[pair.first] = pair.last; h} end
しかし、こうやって他人のソースコードのスタイルにケチを付けるのって、ケチな話だよなと思ったりもする。どうでもいいっちゃどうでもいいし、それより、役立つものを作れよという話なのかも。
そういえば、Hash["a", 2, "b", 3] => {"a"=>2, "b"=>3}というHashのコンストラクタも知らなかったけど、結構使われてるのだろうか。なんとなくRubyっぽくない。前から順番にハッシュ作るのか。引数の個数が偶数だとArgumentErrorで落ちる。
hash.cには、
static VALUE rb_hash_s_create(int argc, VALUE *argv, VALUE klass) { : : if (argc % 2 != 0) { rb_raise(rb_eArgError, "odd number of arguments for Hash"); } hash = hash_alloc(klass); for (i=0; i<argc; i+=2) { rb_hash_aset(hash, argv[i], argv[i + 1]); }
とある。なるほど。
Hash[k1, v1, k2, v2...]というのがどのぐらい使わてるのか、Ruby本体で調べてみた。
find hoge/lib/ruby/1.9.1 -iname '*rb' |xargs grep 'Hash *\['
とやった結果は以下のとおり。スプラッタ演算子と相性は良さそう。しかし、ほとんどがtk関連かな。そしてよく見ると、resolv.rbは違うな。drb.rbの例もえぐいなあ。
.//csv.rb: Hash[*@row.inject(Array.new) { |ary, pair| ary.push(*pair) }] .//csv.rb: meta = Hash[*csv.shift] .//drb/drb.rb: families = Hash[*infos.collect { |af, *_| af }.uniq.zip([]).flatten] .//multi-tk.rb: Hash[*_lst2ary(ip._eval("::safe::interpConfigure " + .//psych/visitors/to_ruby.rb: members = Hash[*o.children.map { |c| accept c }] .//psych/visitors/to_ruby.rb: h = Hash[*members] .//psych/visitors/to_ruby.rb: h = Hash[*o.children.map { |c| accept c }] .//psych/visitors/to_ruby.rb: h = Hash[*o.children.map { |c| accept c }] .//psych/visitors/to_ruby.rb: h = Hash[*o.children.map { |c| accept c }] .//psych/visitors/to_ruby.rb: h = Hash[*o.children.map { |c| accept c }] .//psych/visitors/to_ruby.rb: h = Hash[*node.children.map { |c| accept c }] .//resolv.rb: return ClassHash[[type_value, class_value]] || .//resolv.rb: ClassHash[[type_value, class_value]] = c .//resolv.rb: ClassHash[[s::TypeValue, ClassValue]] = c .//resolv.rb: ClassHash[[TypeValue, ClassValue]] = self # :nodoc: .//resolv.rb: ClassHash[[TypeValue, ClassValue]] = self # :nodoc: .//resolv.rb: ClassHash[[TypeValue, ClassValue]] = self # :nodoc: .//resolv.rb: ClassHash[[TypeValue, ClassValue]] = self # :nodoc: .//rubygems/specification.rb: # Hash[*spec.authors.zip(spec.emails).flatten] .//rubygems/specification.rb: # Hash[*spec.authors.zip(spec.emails).flatten] .//syck/types.rb: seq.add( Hash[ *v ] ) .//syck/types.rb: seq.add( Hash[ *v ] ) .//test/unit.rb: @workers_hash = Hash[@workers.map {|w| [w.io,w] }] # out-IO => worker .//tk/font.rb: Hash[TkFont.actual(fnt, option)] .//tk/font.rb: Hash[TkFont.actual_displayof(fnt, option)] .//tk/font.rb: h = Hash[TkFont.metrics(fnt)] .//tk/font.rb: h = Hash[TkFont.metrics_displayof(fnt, win, option)] .//tk/font.rb: Hash[*tk_split_simplelist(tk_call('font', 'configure', .//tk/font.rb: Hash[*(tk_split_simplelist(tk_call('font', 'configure', .//tk/font.rb: Hash[*(tk_split_simplelist(tk_call('font', 'configure', .//tk/font.rb: Hash[actual(option)] .//tk/font.rb: Hash[actual_displayof(win, option)] .//tk/font.rb: Hash[latin_actual(option)] .//tk/font.rb: Hash[latin_actual_displayof(win, option)] .//tk/font.rb: Hash[kanji_actual(option)] .//tk/font.rb: Hash[kanji_actual_displayof(win, option)] .//tk/font.rb: Hash[latin_configinfo(slot)] .//tk/font.rb: Hash[kanji_configinfo(slot)] .//tk/font.rb: h = Hash[metrics(option)] .//tk/font.rb: h = Hash[metrics_displayof(win, option)] .//tk/font.rb: h = Hash[latin_metrics(option)] .//tk/font.rb: h = Hash[latin_metrics_displayof(win, option)] .//tk/font.rb: h = Hash[kanji_metrics(option)] .//tk/font.rb: h = Hash[kanji_metrics_displayof(win, option)] .//tk/variable.rb: #Hash[*tk_split_simplelist(INTERP._eval("global #{@id}; array get #{@id}"))] .//tk/variable.rb: Hash[*tk_split_simplelist(INTERP._invoke('array', 'get', @id))] .//tk/variable.rb: Hash[*tk_split_simplelist(INTERP._eval(Kernel.format('global %s; array get %s', @id, @id)))] .//tk/variable.rb: #Hash[*tk_split_simplelist(_fromUTF8(INTERP._invoke_without_enc('array', 'get', @id)))] .//tk.rb: Hash[*tk_split_simplelist(INTERP._invoke_without_enc('array', 'get', .//tk.rb: Hash[*tk_split_simplelist(INTERP._invoke('array', 'get', 'env'))] .//tk.rb: Hash[*tk_split_simplelist(INTERP._invoke('array', 'get', 'auto_index'))] .//tk.rb: Hash[*tk_split_simplelist(INTERP._invoke('array', 'get', .//tkextlib/blt/container.rb: Hash[*simplelist(tk_send_without_enc('find', '-command', pat))] .//tkextlib/blt/container.rb: Hash[*simplelist(tk_send_without_enc('find', '-name', pat))] .//tkextlib/blt/tree.rb: Hash[*simplelist(tk_call('::blt::tree', 'get', tagid(node)))] .//tkextlib/blt/watch.rb: Hash[*(info.flatten)] .//tkextlib/blt/winop.rb: Hash[*list(tk_call('::blt::winop', 'colormap', win))] .//tkextlib/tile/style.rb: Hash[*(simplelist(tk_call(TkCommandNames[0], 'map', style)))].each{|k, v| .//tkextlib/tile/treeview.rb: ret = Hash[*(tk_split_simplelist(tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)))), false, false))].to_a.collect{|conf| .//tkextlib/tile/treeview.rb: ret = Hash[*(tk_split_simplelist(tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)))), false, false))].to_a.collect{|conf| .//tkextlib/tile.rb: images = Hash[*TkComm.simplelist(Tk.tk_call(cmd, imgdir, pat))] .//tkextlib/tile.rb: images = Hash[*TkComm.simplelist(Tk.tk_call('array', 'get', 'images'))] .//tkextlib/treectrl/tktreectrl.rb: Hash[*(TkComm.list(val))].each{|k, v| .//tkextlib/treectrl/tktreectrl.rb: Hash[*list(tk_send('item', 'dump', item))] .//tkextlib/treectrl/tktreectrl.rb: Hash[*simplelist(tk_send('style', 'layout', style, elem))].each{|k, v| .//tkextlib/winico/winico.rb: h = Hash[*list(inf)] .//uri/common.rb: # p Hash[ary] # => {"a"=>"2", "b"=>"3"}
Railsはどうかと思ったら、結構使われてる。中でごにょごにょとmapした結果からHashを作るというケースが多いらしい。
~/src/rails (master) find ./ -iname '*rb' |xargs grep 'Hash *\[' |wc -l 73 ~/src/rails (master) find ./ -iname '*rb' |xargs grep 'Hash.new' |wc -l 111