How to Map/Collect with Index in Ruby

How to map/collect with index in Ruby?

If you're using ruby 1.8.7 or 1.9, you can use the fact that iterator methods like each_with_index, when called without a block, return an Enumerator object, which you can call Enumerable methods like map on. So you can do:

arr.each_with_index.map { |x,i| [x, i+2] }

In 1.8.6 you can do:

require 'enumerator'
arr.enum_for(:each_with_index).map { |x,i| [x, i+2] }

Ruby: Index as param in array map

I'm assuming that variable is actually just a hash, since that fits the code you're running and the results you're seeing the best, if not this answer needs some slight modification, but should as whole still be relevant

variable = { "Key1" => ["value1", "value2"], "Key2" => ["value1", "value2"] }

Usually when you call map, the element being passed in, where applicable can be deconstructed:

variable.values.map { |p1, p2| puts p1 }
# value1
# value1

However, when you call with_index on it, it changes the behavior so that the first parameter passed is the element in your Enumerable, and the second is the index:

variable.values.map.with_index { |p1, p2| puts p1.inspect }
# ["value1", "value2"]
# ["value1", "value2"]

So, since things get ambiguous if you start trying to deconstruct multiple parameters (for instance, what if that second item, which in this case is an integer index was actually another array; what parameter goes with what array; similarly if your first parameter had 5 elements and you supply 3 parameters to the block, what should the interpreter think you meant to be doing?), you need to use a special syntax. You simply need to wrap the parameter names that should go with the first element in parenthesis:

variable.values.map.with_index do |(param1, param2), index|
puts "p1: #{param1}; p2: #{param2}; index: #{index}"
end
# p1: value1; p2: value2; index: 0
# p1: value1; p2: value2; index: 1

and finally, if you are only interested in the first element of each value array, you can just assign the rest of the array to an unused variable, for instance:

variable.values.map.with_index do |(param1, *), index|
puts "p1: #{param1}; index: #{index}"
end
# p1: value1; index: 0
# p1: value1; index: 1

And naturally, since this isn't actually mapping the values to anything, this is all applicable to each.with_index as well.

Ruby hash iteration, index access and value mapping

Your code just assigns values to some local variables. You need to assign the new values to the hash itself:

hash.each_with_index do |(key, array), index|
element1, element2 = array
element1 = "new value"
element2 = "new value2"
hash[key] = [element1, element2]
end

Or shorter (depending on what you try to achieve):

hash.each do |key, array|
hash[key] = ["new value", "new value2"]
end

Or:

hash.update(hash) do |key, array|
["new value", "new value2"]
end

Ruby : Choosing between each, map, inject, each_with_index and each_with_object

A more tl;dr answer:

How to choose between each, map, inject, each_with_index and each_with_object?

  • Use #each when you want "generic" iteration and don't care about the result. Example - you have numbers, you want to print the absolute value of each individual number:

    numbers.each { |number| puts number.abs }
  • Use #map when you want a new list, where each element is somehow formed by transforming the original elements. Example - you have numbers, you want to get their squares:

    numbers.map { |number| number ** 2 }
  • Use #inject when you want to somehow reduce the entire list to one single value. Example - you have numbers, you want to get their sum:

    numbers.inject(&:+)
  • Use #each_with_index in the same situation as #each, except you also want the index with each element:

    numbers.each_with_index { |number, index| puts "Number #{number} is on #{index} position" }
  • Uses for #each_with_object are more limited. The most common case is if you need something similar to #inject, but want a new collection (as opposed to singular value), which is not a direct mapping of the original. Example - number histogram (frequencies):

    numbers.each_with_object({}) { |number, histogram| histogram[number] = histogram[number].to_i.next }

Difference between map and collect in Ruby?

There's no difference, in fact map is implemented in C as rb_ary_collect and enum_collect (eg. there is a difference between map on an array and on any other enum, but no difference between map and collect).


Why do both map and collect exist in Ruby? The map function has many naming conventions in different languages. Wikipedia provides an overview:

The map function originated in functional programming languages but is today supported (or may be defined) in many procedural, object oriented, and multi-paradigm languages as well: In C++'s Standard Template Library, it is called transform, in C# (3.0)'s LINQ library, it is provided as an extension method called Select. Map is also a frequently used operation in high level languages such as Perl, Python and Ruby; the operation is called map in all three of these languages. A collect alias for map is also provided in Ruby (from Smalltalk) [emphasis mine]. Common Lisp provides a family of map-like functions; the one corresponding to the behavior described here is called mapcar (-car indicating access using the CAR operation).

Ruby provides an alias for programmers from the Smalltalk world to feel more at home.


Why is there a different implementation for arrays and enums? An enum is a generalized iteration structure, which means that there is no way in which Ruby can predict what the next element can be (you can define infinite enums, see Prime for an example). Therefore it must call a function to get each successive element (typically this will be the each method).

Arrays are the most common collection so it is reasonable to optimize their performance. Since Ruby knows a lot about how arrays work it doesn't have to call each but can only use simple pointer manipulation which is significantly faster.

Similar optimizations exist for a number of Array methods like zip or count.

How to map a non-zero index to each character in a string

This is one way to do that:

alphabet = "AABBBCCCCDDDDDEFGHIJKLMNOPQRSTUVWXYZZZZZZ"

h = alphabet.each_char.
with_index(1).
group_by(&:first).
transform_values { |a| a.map(&:last) }
#=> {"A"=>[1, 2], "B"=>[3, 4, 5], "C"=>[6, 7, 8, 9],
# "D"=>[10, 11, 12, 13, 14], "E"=>[15], "F"=>[16], "G"=>[17],
# "H"=>[18], "I"=>[19], "J"=>[20], "K"=>[21], "L"=>[22], "M"=>[23],
# "N"=>[24], "O"=>[25], "P"=>[26], "Q"=>[27], "R"=>[28], "S"=>[29],
# "T"=>[30], "U"=>[31], "V"=>[32], "W"=>[33], "X"=>[34], "Y"=>[35],
# "Z"=>[36, 37, 38, 39, 40, 41]}

c = 'Z'
puts "All the #{c}'s: #{h[c]}"
All the Z's: [36, 37, 38, 39, 40, 41]

The steps are as follows:

a = alphabet.each_char
#=> #<Enumerator:"AABBB...ZZZZZZ":each_char>
b = a.with_index(1)
#=> #<Enumerator: #<Enumerator: "AABBB...ZZZZZZ":each_char>:with_index(1)>
c = b.group_by(&:first)
#=> {"A"=>[["A", 1], ["A", 2]],
# "B"=>[["B", 3], ["B", 4], ["B", 5]],
# ...
# "Z"=>[["Z", 36], ["Z", 37], ["Z", 38], ["Z", 39], ["Z", 40],
# ["Z", 41]]}

b.group_by(&:first) is more-or-less shorthand for:

c = b.group_by { |c,i| c }

Lastly:

c.transform_values { |a| a.map(&:last) }
#=> <as above>

Note:

a.next #=> "A"
a.next #=> "A"
a.next #=> "A"
a.next #=> "B"
...

and

b.next #=> ["A", 1] 
b.next #=> ["A", 2]
b.next #=> ["B", 3]
b.next #=> ["B", 4]
...

The elements generated by the enumerator b are passed to group_by's block, { |c,i| c }, and the block variables are assigned to their values. For example:

b.rewind
c, i = b.next #=> ["A", 1]
c #=> "A"
i #=> 1

See Enumerator#with_index, Enumerable#group_by and Hash#transform_values, Enumerator#next and Enumerator#rewind.

In Ruby, is there an Array method that combines 'select' and 'map'?

I usually use map and compact together along with my selection criteria as a postfix if. compact gets rid of the nils.

jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}    
=> [3, 3, 3, nil, nil, nil]


jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}.compact
=> [3, 3, 3]


Related Topics



Leave a reply



Submit