Map with Index

Notes: Working with ruby 1.8.7 but 1.9 solution presented at bottom.

While working on my main contract today I needed to create a new array based off mutated values of an existing array. Simple enough I’ll just use Enumerable::map right? Almost immediatley I realized I needed to make a reference to an array via the loops index. I first checked to see if a map_with_index existed similar to Enumerable::each_with_index.

It doesn’t.

I took a quick read through the docs and realized that each_with_index actually returns an Enumerable. This means I can use each_with_index and map to achieve the desired effect. Here’s an example:

arr = [:cat, :dog, :moose] # Canada eh?
new_arr = arr.each_with_index.map { |value,index| "The #{value} is at position #{index}" }
=> ["The cat is at position 0", "The dog is at position 1", "The moose is at position 2"]

A closer look at the Enumerable returned by each_with_index shows us exactly what each_with_index returns that lets map play so nicely:

arr = [:cat, :dog, :moose]
arr.each_with_index.to_a
=> [[:cat, 0], [:dog, 1], [:moose, 2]]

From here the array of arrays will get mapped. I had never thought of passing multiple values into a map block but it works wonders. The map block takes each value of the inner array and offers it as a value inside the block.

The actual reason I needed this code was to parse and alter a string into keys with multiple possible matches. I plan to write a more in-depth post regarding this topic later but until then here’s the snippet I used:

phase = 2 # Dynamic
string = 'awards.about.marquee.title' # A key for a yaml translation

a = string.split('.')
possibilities = a.each_with_index.map do |v,i|
  b = a.clone
  b[i] = "p#{phase}_#{v}"
  b.join('.')
end

=> ["p2_awards.about.marquee.title", "awards.p2_about.marquee.title", "awards.about.p2_marquee.title", "awards.about.marquee.p2_title"]

This output the different possibilites where one of the potential keys could be prefixed with ‘p2’.

Ruby 1.9
While writing this post and sifting through documentation I also found the Enumberable#with_index method which essentially does the same thing but can make the syntax easier to read:

arr = [:cat, :dog, :moose]
new_arr = arr.map.with_index {|value,index| "The #{value} is at position #{index}" }
=> ["The cat is at position 0", "The dog is at position 1", "The moose is at position 2"]

So there we have it: Map with index for ruby 1.8 and 1.9.