Ruby 'Each_With_Object' with Index

Ruby `each_with_object` with index

Instead of

|arr, hash|

you can do

|(v, i), hash|

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 }

Each with index with object in Ruby

From ruby docs for each_with_object

Note that you can’t use immutable objects like numbers, true or false
as the memo. You would think the following returns 120, but since the
memo is never changed, it does not.

(1..5).each_with_object(1) { |value, memo| memo *= value } # => 1

So each_with_object does not work on immutable objects like integer.

How should I use each_with_object on Hashes?

Use ():

hash.each_with_object([]) { |(k, v), array| array << k }

Why map {}.compact is faster than each_with_object([])?

Array#<< needs to reallocate memory if the original memory space doesn't have enough room to hold the new item. See the implementation, especially this line

VALUE target_ary = ary_ensure_room_for_push(ary, 1);

While Array#map doesn't have to reallocate memory from time to time because it already knows the size of the result array. See the implementation, especially

collect = rb_ary_new2(RARRAY_LEN(ary));

which allocates the same size of memory as the original array.

Accumulator for each_with_object keeps re-initializing

Each iteration gets the same object, so you either need to mutate the object inside the block, or use reduce.

def gcp(dims)
first = dims.shift
dims.reduce((0...first).to_a) do |v, dim|
puts "\nv: #{v}, dim: #{dim}"
p v.product((0...dim).to_a)
end
end

gcp([3,2,4])

Results in:

v: [0, 1, 2], dim: 2
[[0, 0], [0, 1], [1, 0], [1, 1], [2, 0], [2, 1]]

v: [[0, 0], [0, 1], [1, 0], [1, 1], [2, 0], [2, 1]], dim: 4
[[[0, 0], 0], [[0, 0], 1], [[0, 0], 2], [[0, 0], 3], [[0, 1], 0], [[0, 1], 1], [[0, 1], 2], [[0, 1], 3], [[1, 0], 0], [[1, 0], 1], [[1, 0], 2], [[1, 0], 3], [[1, 1], 0], [[1, 1], 1], [[1, 1], 2], [[1, 1], 3], [[2, 0], 0], [[2, 0], 1], [[2, 0], 2], [[2, 0], 3], [[2, 1], 0], [[2, 1], 1], [[2, 1], 2], [[2, 1], 3]]

Analysis of .inject vs .each_with_object

Why is the first printing abc in reverse?

The arguments are backwards. See Enumerable#inject.

Printing i and str shows the problem.

$ ruby -e '("a".."c").inject("") {|i,str| puts "i   = #{i}"; puts "str = #{str}"; ret = str += i; puts "ret = #{ret}\n\n"; ret }'
i =
str = a
ret = a

i = a
str = b
ret = ba

i = ba
str = c
ret = cba

i is the accumulated sum returned the last time through the block. str is the next value. You're adding the previous accumulated value to then end of the next one. You have the arguments backwards. The first one is the sum, the second is the next element.

When that's corrected it works fine.

$ ruby -e 'puts ("a".."c").inject("") {|sum,i| sum += i}'
abc

Furthermore, it's the return value of the block that's used. Altering str has no effect, it just confuses things further.

$ ruby -e 'puts ("a".."c").inject("") {|sum,i| sum + i}'
abc

And why is the second an error?

It isn't, not in Ruby 2.0.0 nor 2.2.4 anyway. But it has the same problem of backwards args.

$ ruby -e 'puts ("a".."c").inject("") {|i,str| str << i} # => ERROR'
cba

$ ruby -v
ruby 2.2.4p230 (2015-12-16 revision 53155) [x86_64-darwin15]

Ruby2.3; refactor each_with_object

body = line_item_body_elements.map do |element|
send("fetch_invoice_#{element}#{'_amount' unless element == 'products'}")
end

Ruby inject with initial being a hash

Your block needs to return the accumulating hash:

['a', 'b'].inject({}) {|m,e| m[e] = e; m }

Instead, it's returning the string 'a' after the first pass, which becomes m in the next pass and you end up calling the string's []= method.



Related Topics



Leave a reply



Submit