1

I was going to comment on the original question but I don't have the reputation to do so yet....

I too was wondering how to easily update all the values in a hash, or if there was some kind of equivalent .map! method for hashes. Someone put up this elegant solution:

hash.update(hash){|key,v1| expresion}

on this question: Ruby: What is the easiest method to update Hash values?

My questions is how does the block know to iterate over each element in the hash? For example, I'd have to call .each on a hash to access each element normally so why isn't it something like:

hash.update(hash.each) do |key ,value|
   value+=1
end

In the block with {|key, value| expression} I am accessing each individual hash element yet I don't have to explicitly tell the system this? Why not? Thank you very much.

Community
  • 1
  • 1
HectorOfTroy407
  • 1,737
  • 5
  • 21
  • 31

2 Answers2

0

Hash#update is an alias for Hash#merge! which is more descriptive.

When calling the method with a block, the following happens (excerpt from the docs):

If [a] block is specified, [...] the value of each duplicate key is determined by calling the block with the key [...]

So, the above code works like this:

The hash is merged with itself, and for each duplicate key the block is called. As we merge the hash with itself, every newly added key is a duplicate and therefore the block is invoked. The result is that every value in the hash gets replaced by expresion.

koffeinfrei
  • 1,985
  • 13
  • 19
0

Hash#update takes a hash as the first parameter, and an optional block as the second parameter. If the second parameter is left out, the method will internally loop on each key-value pair in the supplied hash and use them to merge into the original hash.

If the block (second parameter) is supplied, the method does exactly the same thing. It loops over each key-value in the supplied hash and merges it in. The only difference is where a collision is found (the original hash already has an entry for a specific key). In this case the block is called to help resolve the conflict.

Based on this understanding, simply passing the hash into itself will cause it to loop over every key-value because that's how update always works. Calling .each would be redundant.

To see this more clearly, take a look at the source code for the #update method, and note the internal call to rb_hash_foreach in either logic branch.

PinnyM
  • 35,165
  • 3
  • 73
  • 81
  • So in the event of a collision (duplicate key), the block is the rule by which the original hash is updated? So you could say something like {|key, value} value+=1}? Could you create a rule for a collision whereby you update the original hash's value with the average of the original hash's value and the second hash's value? Trying to get an idea of the scope of this method. Thank you very much for your response. – HectorOfTroy407 Aug 27 '14 at 20:54
  • If you check the documentation, you'll see that the block arity actually accepts 3 arguments: the colliding key, the original value, and the new value. So yes, taking the average of the 2nd and 3rd arguments would give you that result: `{|key, v1, v2| (v1 + v2) / 2}` – PinnyM Aug 28 '14 at 13:06