4

I have an array of hashes in Ruby:

array = [
  {:date => Wed, 04 May 2011 00:00:00 PDT -07:00,
   :value => 200}
  {:date => Wed, 04 May 2011 01:00:00 PDT -07:00,
   :value => 100}
  {:date => Tue, 03 May 2011 01:00:00 PDT -07:00,
   :value => 300}
  {:date => Tue, 03 May 2011 01:00:00 PDT -07:00,
   :value => 150}
]

I'd like to be able to combine the values within each day so that I have a new array like this:

array = [
  {:date => Wed, 04 May 2011 00:00:00 PDT -07:00,
   :value => 300}
  {:date => Tue, 03 May 2011 00:00:00 PDT -07:00,
   :value => 450}
]

What's the most elegant way to search the array by day and sum up the values for each day?

This is what I initially tried:

entries = [
  {:date => Wed, 04 May 2011 00:00:00 PDT -07:00,
   :value => 200}
  {:date => Wed, 04 May 2011 01:00:00 PDT -07:00,
   :value => 100}
  {:date => Tue, 03 May 2011 01:00:00 PDT -07:00,
   :value => 300}
  {:date => Tue, 03 May 2011 01:00:00 PDT -07:00,
   :value => 150}
]

first_day = 29.days.ago.beginning_of_day
total_days = 30

day_totals = (0...total_days).inject([]) do |array, num|
    startat = first_day + num.day
    endat = startat.end_of_day

    total_value_in_day = entries.where("date >= ? and date <= ?", startat, endat).sum(:value)

    array << {:date => startat, :value => total_value_in_day}
end

I realized my mistake was with the where method which is a Rails method for searching objects, and can't be used on arrays. So my main question, is there a way to search arrays or hashes with conditions.

Chanpory
  • 3,015
  • 6
  • 37
  • 49
  • The most elegant way really is for you to [write](http://stackoverflow.com/questions/4158947/how-do-i-sum-up-records-within-rails-3) [some code](http://stackoverflow.com/questions/4382543/how-do-i-add-values-from-two-different-arrays-of-hashes-together) yourself, if you don't mind my being _elegantly_ blunt. Elegance in code is a value, and it is good to reach for it, but you cannot reach mastery without (painful) exercise. Perhaps [read a good book](http://programmingzen.com/ruby-and-rails-recommended-books/) – sehe May 04 '11 at 20:44
  • edited the post to show the code I tried on my own, and where I was stuck. – Chanpory May 04 '11 at 20:55
  • Where does the combined value 450 for Tue 03 May come from? – sawa May 04 '11 at 20:58
  • Aha, much better question now ! – sehe May 04 '11 at 20:58
  • @sawa, oops that was a typo, just fixed – Chanpory May 04 '11 at 20:59

2 Answers2

5

You can iterate over the entries to create a new array:

totals = Hash.new(0)
array.each do |entry|
  totals[entry[:date]] += entry[:value]
end

# Now totals will be something like this:
# => {"Wed, 04 May 2011" => 300, "Tue, 03 May 2011" => 450...}

# If you then want this in the same array format you started with:
new_array = totals.collect{ |key, value| {:date => key, :value => value} }
# => [{:date => "Wed, 04 May 2011", :value => 300}, {....}]
Dylan Markow
  • 123,080
  • 26
  • 284
  • 201
  • This worked great! I also had to use `entry[:date].beginning_of_day` to add up entries within varying times of the day. Thanks! – Chanpory May 04 '11 at 21:57
3

For 1.9.2:

>> array.each_with_object(Hash.new(0)) { |el, hash| hash[el[:date]] += el[:value] } 
#=> {"Wed, 04 May 2011 00:00:00 PDT -07:00"=>200, "Wed, 04 May 2011 01:00:00 PDT -07:00"=>100, "Tue, 03 May 2011 01:00:00 PDT -07:00"=>450}

Also works with 1.8:

>> array.inject(Hash.new(0)) { |hash, el| hash[el[:date]] += el[:value] ; hash } 
#=> {"Wed, 04 May 2011 00:00:00 PDT -07:00"=>200, "Wed, 04 May 2011 01:00:00 PDT -07:00"=>100, "Tue, 03 May 2011 01:00:00 PDT -07:00"=>450}
Michael Kohl
  • 66,324
  • 14
  • 138
  • 158