Как отмечает Алексей, Hash # update () работает медленнее, чем Hash # store (), но это заставило меня задуматься об общей эффективности #inject () по сравнению с прямым циклом #each. Итак, я проверил несколько вещей:
(ПРИМЕЧАНИЕ. Обновлено 19 сентября 2012 года и теперь включает #each_with_object)
(ПРИМЕЧАНИЕ. Обновлено 31 марта 2014 года и теперь включает #by_initialization, благодаря предложению https://stackoverflow.com/users/244969/pablo)
тесты
require 'benchmark'
module HashInject
extend self
PAIRS = 1000.times.map {|i| [sprintf("s%05d",i).to_sym, i]}
def inject_store
PAIRS.inject({}) {|hash, sym, val| hash[sym] = val ; hash }
end
def inject_update
PAIRS.inject({}) {|hash, sym, val| hash.update(val => hash) }
end
def each_store
hash = {}
PAIRS.each {|sym, val| hash[sym] = val }
hash
end
def each_update
hash = {}
PAIRS.each {|sym, val| hash.update(val => hash) }
hash
end
def each_with_object_store
PAIRS.each_with_object({}) {|pair, hash| hash[pair[0]] = pair[1]}
end
def each_with_object_update
PAIRS.each_with_object({}) {|pair, hash| hash.update(pair[0] => pair[1])}
end
def by_initialization
Hash[PAIRS]
end
def tap_store
{}.tap {|hash| PAIRS.each {|sym, val| hash[sym] = val}}
end
def tap_update
{}.tap {|hash| PAIRS.each {|sym, val| hash.update(sym => val)}}
end
N = 10000
Benchmark.bmbm do |x|
x.report("inject_store") { N.times { inject_store }}
x.report("inject_update") { N.times { inject_update }}
x.report("each_store") { N.times {each_store }}
x.report("each_update") { N.times {each_update }}
x.report("each_with_object_store") { N.times {each_with_object_store }}
x.report("each_with_object_update") { N.times {each_with_object_update }}
x.report("by_initialization") { N.times {by_initialization}}
x.report("tap_store") { N.times {tap_store }}
x.report("tap_update") { N.times {tap_update }}
end
end
результаты
Rehearsal -----------------------------------------------------------
inject_store 10.510000 0.120000 10.630000 ( 10.659169)
inject_update 8.490000 0.190000 8.680000 ( 8.696176)
each_store 4.290000 0.110000 4.400000 ( 4.414936)
each_update 12.800000 0.340000 13.140000 ( 13.188187)
each_with_object_store 5.250000 0.110000 5.360000 ( 5.369417)
each_with_object_update 13.770000 0.340000 14.110000 ( 14.166009)
by_initialization 3.040000 0.110000 3.150000 ( 3.166201)
tap_store 4.470000 0.110000 4.580000 ( 4.594880)
tap_update 12.750000 0.340000 13.090000 ( 13.114379)
------------------------------------------------- total: 77.140000sec
user system total real
inject_store 10.540000 0.110000 10.650000 ( 10.674739)
inject_update 8.620000 0.190000 8.810000 ( 8.826045)
each_store 4.610000 0.110000 4.720000 ( 4.732155)
each_update 12.630000 0.330000 12.960000 ( 13.016104)
each_with_object_store 5.220000 0.110000 5.330000 ( 5.338678)
each_with_object_update 13.730000 0.340000 14.070000 ( 14.102297)
by_initialization 3.010000 0.100000 3.110000 ( 3.123804)
tap_store 4.430000 0.110000 4.540000 ( 4.552919)
tap_update 12.850000 0.330000 13.180000 ( 13.217637)
=> true
заключение
Enumerable # каждый быстрее, чем Enumerable # inject, а Hash # store быстрее, чем Hash # update. Но быстрее всего передать массив во время инициализации:
Hash[PAIRS]
Если вы добавляете элементы после того, как хеш создан, выигрышная версия - это именно то, что предлагал ОП:
hash = {}
PAIRS.each {|sym, val| hash[sym] = val }
hash
Но в этом случае, если вы пурист, которому нужна единая лексическая форма, вы можете использовать #tap и #each и получить одинаковую скорость:
{}.tap {|hash| PAIRS.each {|sym, val| hash[sym] = val}}
Для тех, кто не знаком с tap, он создает привязку получателя (новый хеш) внутри тела и, наконец, возвращает получателя (тот же хеш). Если вы знаете Lisp, подумайте о версии Ruby с привязкой LET.
-whew-. Спасибо за прослушивание.
приписка
Так как люди спрашивают, вот среда тестирования:
# Ruby version ruby 2.0.0p247 (2013-06-27) [x86_64-darwin12.4.0]
# OS Mac OS X 10.9.2
# Processor/RAM 2.6GHz Intel Core i7 / 8GB 1067 MHz DDR3