1

Given the following Hash hash with keys as symbols and values as arrays:

hash
#=> {:stage_item=>[:stage_batch_id, :potential_item_id], :item=>[:id, :size, :color, :status, :price_sold, :sold_at], :style=>[:wholesale_price, :retail_price, :type, :name]}

How do I get an array with of just the values (arrays) added together?

I know I can use #each_with_object and #flatten:

hash.each_with_object([]) { |(k, v), array| array << v }.flatten
#=> [:stage_batch_id, :potential_item_id, :id, :size, :color, :status, :price_sold, :sold_at, :wholesale_price, :retail_price, :type, :name]

But I was expecting just #each_with_object to work:

hash.each_with_object([]) { |(k, v), array| array += v }
#=> []

I though the point of each with object is it keeps track of the accumulator (named array in this case), so I can += it like in the following example:

arr = [1,2,3]
#=> [1, 2, 3]
arr += [4]
#=> [1, 2, 3, 4]

What am I missing?

2 Answers 2

4

Array << .. changes the original array in-place:

irb(main):014:0> a = original = []
=> []
irb(main):015:0> a << [1]
=> [[1]]
irb(main):016:0> a
=> [[1]]
irb(main):017:0> original
=> [[1]]
irb(main):018:0> a.equal? original  # identity check
=> true

while, Array += .. returns a new array without changing the original array:

irb(main):019:0 a = original = []
=> []
irb(main):020:0> a += [1]
=> [1]
irb(main):021:0> a
=> [1]
irb(main):022:0> original
=> []
irb(main):023:0> a.equal? original
=> false

According to Enumerable#each_with_object documentation,

Iterates the given block for each element with an arbitrary object given, and returns the initially given object.

If no block is given, returns an enumerator.

so, in case of +=, returns the initial empty array which is not modified.


BTW, instead of using each_with_object, you can simply use Hash#values method which returns a new array populated with the values from the hash:

hash.values.flatten
Sign up to request clarification or add additional context in comments.

Comments

2

This is actually a case where inject would be a better fit if you insist on using Array#+:

hash.inject([]) { |a, (_, v)| a + v }

The reason you generally use each_with_object rather than inject is if you want to accumulate results in-place rather than using the block's return value as the accumulator. In this case array1 + array2 returns the concatenation so you don't need to use an operator (or method) that modifies the memo object because Array#+ returns what you want to feed into the next cycle.

While we're here, these would be simpler:

hash.values.flatten
hash.values.inject(:+)
h.values.flat_map(&:itself)

and the second even uses Array#+ if you really like calling that method.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.