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
Regex with Named Capture Groups Getting All Matches in Ruby
Rubymine: Rails Server Launcher Wasn't Found in the Project
Puts Doesn't Print Stuff to Console
Paginate Multiple Models in Kaminari
Ruby Merging Two Arrays into One
Rubymine - No Ruby Interpreter Configured for the Project
Ruby: How to Pass All Parameters and Blocks Received by One Method to Another
Rails 4.2 - Sidekiq Not Sending Emails in Development
Bundle Install: Error: Failed to Build Gem Native Extension. Nio4R Gem
How to Install Rvm Without Root Access
How to Use an Overridden Constant in an Inheritanced Class
Running Ruby Unit Tests with Rake
How to Test 'Create' Controller Actions
What Are the Things You Would Like Improved in the Ruby Language
Use Multiple Versions of Rubygems with Rvm