
The Problem
The Enumerable mixin’s #map/#collect methods are fantastic for mutating the values in an array but have an inconsistent behavior when applied to a hash. So since using #map/#collect on an array gives you this:
1 2 | |
you might expect the hash to behave something like this:
1 2 | |
but instead you get:
SyntaxError: (irb):1: syntax error, unexpected ':', expecting '}'
You can get around this by wrapping your key-value pair in curly braces
1 2 | |
but now you have an array of key-value pairs which still is not the behavior that we are shooting for.
The Solution
It’s not pretty but we can use the Enumerable#inject combined with the Hash#merge methods to hack our way toward our goal:
1 2 | |
Pretty straight forward if you have seen inject, right? The hash (creatively called hash inside the block) acts as a collector. Passing a key-value pair into the #merge method called on the collector hash essentially says ‘Hey, Ruby! Here’s a hash with one key-value pair to merge into #inject’s collector!’ Then the merged hash is passed back to #inject to be the collector for the next key-value pair.
But that’s odd…
The weird bit is between the pipes. What’s going on there?
1 2 | |
So the pair is passed into the block as an array containing the key and value as separate elements. By replacing pair with (k,v)
1
| |
we match up k to the first value in the array and v to the second value in the array through Multiple Assignment along the lines of:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
So, inside the block we tell Ruby to make
1 2 3 | |
then recreate the key-value pair with some sort of mapping executed on the key or value (or maybe even both) k => v*v and merge them into the collector hash hash.merge( k=> v*v ) and then let inject do its' thing and return the collector hash.
Vois là! You’ve built a more semantic Hash#map. Don’t go monkey-patching anything. Maybe try a refinement instead?
Sources:
Ruby Doc Enumerable#map Chris Holtz- Let’s Make a Ruby Hash Map Method That Returns a Hash Instead of an Array