Re: 大量のハッシュデータを簡潔に作成する
これもどこまでマジなのかよくわからんのだけど……
つ injectとだけ言わせていただく。
jijixi's diary - Re: Python での組み込み型をより自然な名前にする - kwatchの日記 , Re: 大量のハッシュデータを簡潔に作成する - kwatchの日記
inject()があればHash.create_with()はいらないというご指摘をいただきました。
検証してみましょう。
## Hash.create_with()を使う方法
data = Hash.create_with(:name, :gender, :role) {[
["Haruhi", 1, "Leader of SOS Brigade"],
["Mikuru", 1, "Time Traveler"],
["Yuki", 1, "Humanoid Interface"],
["Itsuki", 0, "ESPer Boy"],
["Kyon", 0, "Story Teller"],
]}
## inject()を使う方法
data = [
["Haruhi", 1, "Leader of SOS Brigade"],
["Mikuru", 1, "Time Traveler"],
["Yuki", 1, "Humanoid Interface"],
["Itsuki", 0, "ESPer Boy"],
["Kyon", 0, "Story Teller"],
].inject([]) {|rows, row|
rows << {:name=>row[0], :gender=>row[1], :role=>row[2]}
rows ## 「rows<<x」の結果は「rows」なのでこの行は省略可能
}・・・これならキー名が先に現れるHash.create_with()のほうがずっとわかりやすいや。
つうか、inject()すらいらなくね?collect()で十分やん。
## collect()バージョン
data = [
["Haruhi", 1, "Leader of SOS Brigade"],
["Mikuru", 1, "Time Traveler"],
["Yuki", 1, "Humanoid Interface"],
["Itsuki", 0, "ESPer Boy"],
["Kyon", 0, "Story Teller"],
].collect {|row| {:name=>row[0], :gender=>row[1], :role=>row[2]} }これも悪くないな。でもまだHash.create_with()のほうがわかりやすいかな。
Hash.create_with()がわかりやすいのは、キーがはじめにまとまった形で指定できるから。それに対し、inject()もcollect()もキーがブロックのなかに散らかってしまう。わかりやすさという点では、この違いは大きい。
ベンチマークをしてみましょう。
class Hash def self.create_with(*keys) return yield.collect {|row| hash = self.new keys.zip(row) {|k, v| hash[k] = v } hash } end end n = ($N || 10000).to_i lst = (1..n).collect { [rand(), rand(), rand(), rand(), rand()] } require 'benchmark' Benchmark.bmbm(30) do |x| data1 = data2 = data3 = nil GC.start x.report('Hash.create_with') do data1 = Hash.create_with(:a, :b, :c, :d, :e) { lst } end GC.start x.report('Enumelable#inject') do data2 = lst.inject([]) {|rows, row| rows << {:a=>row[0], :b=>row[1], :c=>row[2], :d=>row[3], :e=>row[4]} #rows } end GC.start x.report('Enumelable#collect') do data3 = lst.collect {|row| {:a=>row[0], :b=>row[1], :c=>row[2], :d=>row[3], :e=>row[4]} } end $stderr.puts "** error: data1 != data2" if data1 != data2 $stderr.puts "** error: data1 != data3" if data1 != data3 end
結果:
### ruby 1.8.7-p174
$ ruby -s hoge.rb -N=100000
Rehearsal -----------------------------------------------------------------
Hash.create_with 0.690000 0.020000 0.710000 ( 0.728824)
Enumelable#inject 0.460000 0.020000 0.480000 ( 0.489351)
Enumelable#collect 0.460000 0.020000 0.480000 ( 0.485701)
-------------------------------------------------------- total: 1.670000sec
user system total real
Hash.create_with 0.880000 0.020000 0.900000 ( 0.923916)
Enumelable#inject 0.630000 0.010000 0.640000 ( 0.702285)
Enumelable#collect 0.380000 0.000000 0.380000 ( 0.386911)
### ruby 1.9.1-p129
$ ruby -s hoge.rb -N=100000
Rehearsal -----------------------------------------------------------------
Hash.create_with 0.540000 0.050000 0.590000 ( 0.588904)
Enumelable#inject 0.330000 0.030000 0.360000 ( 0.363585)
Enumelable#collect 0.430000 0.020000 0.450000 ( 0.471614)
-------------------------------------------------------- total: 1.400000sec
user system total real
Hash.create_with 0.670000 0.040000 0.710000 ( 1.121027)
Enumelable#inject 0.400000 0.000000 0.400000 ( 0.415909)
Enumelable#collect 0.390000 0.010000 0.400000 ( 0.405874)collect() 速え〜! そして Hash.create_with() 遅い〜!
10万件のデータでコンマ数秒の違いしかないけど、それでも速さを求める場合はHash.create_with()はやめたほうがいいですな。
多少タイプ量は多くなるだろうけど、はるかに柔軟。
別に今の話では柔軟性を求めちゃいなんだけどね。求めているのはわかりやすさと簡潔さだから、そこが劣っていたらいくら柔軟だからといっても訴求力ないっす。わかりやすさや簡潔さより柔軟性が必要になったら初めてその方法を採用すればいいだけのこと。ただそれだけ。