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