1

I have an array that looks like so:

f = [["Wed, 12-31", 120.0],["Thu, 01-01", 120.0], ["Thu, 01-01", 120.0]]

I can convert it to a hash and remove the duplicate keys:

h = Hash[ *f.collect { |v| [v] }.flatten ]
# => {"Wed, 12-31"=>120.0, "Thu, 01-01"=>120.0}

which is almost there, but I'd like to sum the value for elements with the identical date strings, the desired result from the above array would be this:

{"Wed, 12-31"=>120.0, "Thu, 01-01"=>240.0}

How can I accomplish this?

3
  • 2
    Note, you can convert your array to a hash discarding duplicate keys without the .collect.flatten nonsense. Just use Hash[array]. f.collect { |v| [v] } is just mapping the array to itself. It doesn't do anything. .flatten on an already flat array is equally useless. Commented Jan 1, 2015 at 22:50
  • @KennyMeyer err, google? google.ca/… Commented Jan 1, 2015 at 22:51
  • Thanks for the tip on .collect and .flatten Commented Jan 1, 2015 at 23:01

3 Answers 3

2

This works:

result = Hash.new(0)
f = [["Wed, 12-31", 120.0],["Thu, 01-01", 120.0], ["Thu, 01-01", 120.0]]
f.each { |subarray| result[subarray[0]] += subarray[1] }
puts result

If you would like to be fancy you could use .inject()

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

1 Comment

If you'd like to be fancier, and lazier, use each_with_object instead of inject. It works more cleanly.
2

I'd use each_with_object:

ary = [["Wed, 12-31", 120.0], ["Thu, 01-01", 120.0], ["Thu, 01-01", 120.0]]

hash = ary.each_with_object(Hash.new(0)){ |(k,v), h| h[k] += v }
=> {"Wed, 12-31"=>120.0, "Thu, 01-01"=>240.0}

2 Comments

I would just like to add that while I accepted @kennymeyer's answer, I later switched to this solution.
Just to reiterate something you'll see occasionally on Stack Overflow: Don't pick the first answer that comes along and answers your question. There are many ways to do something, and people aren't poised at their computers waiting to pounce on questions. Answers sometimes take a while to formulate; I often wait to answer hoping someone will respond with what I consider to be the right way to do something, and then later will add mine if nobody has done it or answered as thoroughly as I think the answer should be. So, ask, then wait a day and see what shows up.
2

The other standard approach to this type of problem is to use Enumerable#group_by:

Hash[ary.group_by(&:first).map { |d,a| [d, a.reduce(0) { |t,(_,n)| t+n }] }]
  #=> {"Wed, 12-31"=>120.0, "Thu, 01-01"=>240.0}

We have:

a = ary.group_by(&:first)
  #=> {"Wed, 12-31"=>[["Wed, 12-31", 120.0]],
  #    "Thu, 01-01"=>[["Thu, 01-01", 120.0], ["Thu, 01-01", 120.0]]} 
b = a.map { |d,a| [d, a.reduce(0) { |t,(_,n)| t+n }] }
  #=> [["Wed, 12-31", 120.0], ["Thu, 01-01", 240.0]] 
Hash[b] 
  #=> {"Wed, 12-31"=>120.0, "Thu, 01-01"=>240.0} 

or with Ruby 2.0+:

b.to_h
  #=> {"Wed, 12-31"=>120.0, "Thu, 01-01"=>240.0} 

A variant of this is:

ary.group_by(&:first).map { |d,a| [d, a.transpose.last.reduce(:+) ] }.to_h

1 Comment

Thanks for Ruby's 2.0+ to_h approach! That's rad.

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.