55

Say:

h = { 1 => 10, 2 => 20, 5 => 70, 8 => 90, 4 => 34 }

I would like to change each value v to foo(v), such that h will be:

h = { 1 => foo(10), 2 => foo(20), 5 => foo(70), 8 => foo(90), 4 => foo(34) }

What is the most elegant way to achieve this ?

Misha Moroshko
  • 166,356
  • 226
  • 505
  • 746

5 Answers5

118

You can use update (alias of merge!) to update each value using a block:

hash.update(hash) { |key, value| value * 2 }

Note that we're effectively merging hash with itself. This is needed because Ruby will call the block to resolve the merge for any keys that collide, setting the value with the return value of the block.

coreyward
  • 77,547
  • 20
  • 137
  • 166
Gareve
  • 3,372
  • 2
  • 21
  • 23
  • 2
    In my quick benchmark this was even faster than the `each` method by about 20%! – glenn mcdonald Mar 07 '11 at 14:38
  • 3
    Be careful when you are trying to use it in Rails, where the Hash is really the HashWithIndifferentAccess and has different behavior when it comes to handling blocks passed to `update` and `merge`. About a month ago someone proposed change to fix it: https://github.com/rails/rails/pull/7519 – siefca Oct 13 '12 at 12:44
  • 2
    `Hash#update` is an alias for `Hash#merge!`. [These docs](http://ruby-doc.org/core-2.2.0/Hash.html#method-i-update) mention that most docs use `merge!`. I personally prefer to use merge!, as my mind stays in hash mode, whereas when I see "update" I can't help but think of ActiveRecord. – Tyler Collier Mar 23 '15 at 21:39
  • 1
    Tyler Collier - .update() is cleaner to read. It has nothing to do with ActiveRecord, it existed way before active record existed. – shevy Aug 20 '17 at 10:55
25

Rails (and Ruby 2.4+ natively) have Hash#transform_values, so you can now do {a:1, b:2, c:3}.transform_values{|v| foo(v)}

https://ruby-doc.org/core-2.4.0/Hash.html#method-i-transform_values

If you need it to work in nested hashes as well, Rails now has deep_transform_values(source):

hash = { person: { name: 'Rob', age: '28' } }

hash.deep_transform_values{ |value| value.to_s.upcase }
# => {person: {name: "ROB", age: "28"}}
sandre89
  • 5,218
  • 2
  • 43
  • 64
MaximusDominus
  • 2,017
  • 18
  • 11
  • Probably didn't get the "answer" because its Rails and not only Ruby. – Alex Mar 02 '17 at 21:35
  • That is only one reason - the other is that the hash.update() variant is pure ruby and will work ALWAYS whereas rails specifically requires add-ons. The less dependencies, the better. – shevy Aug 20 '17 at 10:54
  • 4
    @Alex ruby 2.4.0 add it the core. https://ruby-doc.org/core-2.4.0/Hash.html#method-i-transform_values – Oshan Wisumperuma Jan 20 '19 at 14:12
12

This will do:

h.each {|k, v| h[k] = foo(v)}
rubyprince
  • 17,559
  • 11
  • 64
  • 104
6

The following is slightly faster than @Dan Cheail's for large hashes, and is slightly more functional-programming style:

new_hash = Hash[old_hash.map {|key, value| key, foo(value)}]

Hash#map creates an array of key value pairs, and Hash.[] converts the array of pairs into a hash.

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
  • 1
    Indeed; this is the 'pro' way to do it, although the syntax may be a bit mind-bending if you're not used to Ruby's specifics. – dnch Mar 07 '11 at 04:33
  • 2
    Also, in Ruby 1.8, you may need to explicitly surround `key, foo(value)` with `[]` - I was getting syntax errors. – dnch Mar 07 '11 at 04:36
0

There's a couple of ways to do it; the most straight-forward way would be to use Hash#each to update a new hash:

new_hash = {}
old_hash.each do |key, value|
  new_hash[key] = foo(value)
end
dnch
  • 9,565
  • 2
  • 38
  • 41