Hash#map は Hash を返すと思っていた方 (自分含) へ

Hash#map は Enumerable モジュールから

以下引用

各要素に対してブロックを評価した結果を全て含む配列を返します。

試す

h = {:one => 1, :two => 2, :three => 3}
=> {:one=>1, :two=>2, :three=>3}
h.map { |k,v| [k, v*2] }
=> [[:one, 2], [:two, 4], [:three, 6]]

配列の配列が返ってくる、 Hash でなくて残念。

案1:上記配列から Hash を作成する
Hash[h.map { |k,v| [k, v*2] }]
=> {:one=>2, :two=>4, :three=>6}
案2:空っぽの Hash に突っ込んでいく
x = {}; h.each { |k,v| x[k] = v*2 }; x
=> {:one=>2, :two=>4, :three=>6}

う〜、もう少しだけスマートにしたい

案3:案2の欠点解消、 inject を使う
h.inject({}) { |x,kv| k,v=kv; x[k] = v*2; x }
=> {:one=>2, :two=>4, :three=>6}

変数使わずに済んだ

案4:案3を少し変更、可読性イラネ
h.inject({}) { |x,kv| x[kv[0]] = kv[1]*2; x }
=> {:one=>2, :two=>4, :three=>6}

ベンチマークとった

$ cat test.rb

require "benchmark"

N = 1000000

h = { :one => 1, :two => 2, :three => 3, :four => 4 }

puts Benchmark.measure { N.times {
  Hash[h.map { |k,v| [k,v*2] }]
}}
puts Benchmark.measure { N.times {
  r = {}; h.each { |k,v| r[k] = v*2 }; r
}}
puts Benchmark.measure { N.times {
  h.inject({}) { |x,kv| k,v=kv; x[k] = v*2; x }
}}
puts Benchmark.measure { N.times {
  h.inject({}) { |x,kv| x[kv[0]] = kv[1]*2; x }
}}
$ ruby test.rb
  3.860000   0.000000   3.860000 (  3.860018)
  2.290000   0.000000   2.290000 (  2.291663)
  2.790000   0.000000   2.790000 (  2.797183)
  2.710000   0.000000   2.710000 (  2.702222)

破壊的でもOKならば

こんな手もアリですね

h = {:one => 1, :two => 2, :three => 3}
 => {:one=>1, :two=>2, :three=>3}
h
 => {:one=>1, :two=>2, :three=>3}
h.each { |k,v| h[k] = v*2 }
 => {:one=>2, :two=>4, :three=>6}
h
 => {:one=>2, :two=>4, :three=>6}

キーが変更されない前提で

こんなのどうだろうか

class Hash
  def map_to_hash &block
    ret = {}
    each { |k,v| ret[k] = block.call(v) }
    ret
  end
end
 => nil
h = {:one => 1, :two => 2, :three => 3}
 => {:one=>1, :two=>2, :three=>3}
h.map_to_hash { |v| v*2 }
 => {:one=>2, :two=>4, :three=>6}