I am trying to sum values from a ruby hash but using either inject or reduce does not return the correct answer. It seems as though these methods are overwriting the current value being stored instead of summing them.
My hash look like this:
@test = [
{"total"=>18, "type"=>"buy", "date"=>Thu, 21 Nov 2013, "instrument_code"=>"food"},
{"total"=>92, "type"=>"buy", "date"=>Thu, 14 Nov 2013, "instrument_code"=>"food"},
{"total"=>12, "type"=>"buy", "date"=>Wed, 20 Nov 2013, "instrument_code"=>"drink"},
{"total"=>1, "type"=>"buy", "date"=>Mon, 11 Nov 2013, "instrument_code"=>"food"}
]
Here is my inject code that fails:
def additions_per_security
@test.group_by { |i| i.type }.each do |key, value|
if key == "buy"
value.group_by{ |i| i.date }.each do |key, value|
@sortable_additions[key] = value
end
@sorted_additions = @sortable_additions.sort_by { |key,value| key }
@sorted_additions.shift
@additions_per_security = Hash[@sorted_additions.map { |key, value|
[key, value]
}]
@additions_per_security.each do |key, value|
value.group_by { |i| i.instrument_code }.each do |key, value|
@additions_security[key] = value.inject(0){ |result, transaction|
(result += transaction.total)
}
end
end
end
end
return @additions_security
end
Here is my reduce code that fails:
def additions_per_security
@@test.group_by { |i| i.type }.each do |key, value|
if key == "buy"
value.group_by { |i| i.date }.each do |key, value|
@sortable_additions[key] = value
end
@sorted_additions = @sortable_additions.sort_by { |key,value| key }
@sorted_additions.shift
@additions_per_security = Hash[@sorted_additions.map { |key, value|
[key, value]
}]
@additions_per_security.each do |key, value|
value.group_by { |i| i.instrument_code }.each do |key, value|
@additions_security[key] = value.map { |p| p.total }.reduce(0,:+)
end
end
end
end
return @additions_security
end
I have a hash and I want to sum the totals for all keys except the first date.
I am currently getting the following:
{"drink"=>12.0, "food"=>92}
My expected result will look like this:
{"drink"=>12.0, "food"=>110}
Thanks in advance for any advice.
We can merge two hashes using the merge() method. When using the merge() method: Each new entry is added to the end. Each duplicate-key entry's value overwrites the previous value.
Ruby | Enumerable sum() function The sum() of enumerable is an inbuilt method in Ruby returns the sum of all the elements in the enumerable. If a block is given, the block is applied to the enumerable, then the sum is computed. If the enumerable is empty, it returns init.
If you have simple key/value hash
{1 => 42, 2 => 42}.values.sum
=> 84
I offer the following observations on your inject
code:
@
) would suffice;test.group_by {|i| i.type}...
should be test.group_by {|i| i["type"]}...
@sortable_additions[key]=value
should raise an exception because the hash has not been created;@sorted_additions.shift
removes the first element of the hash and returns that element, but there is no variable to receive it (e.g.,, h = @sorted_additions.shift
);@additions_per_security = Hash[@sorted_additions.map { |key, value|[key, value]}]
appears to convert @sorted_additions to an array and then back to the same hash. The following is one way to do what you you want to do.
Firstly, you will be passing date objects. To work with that we'll start by making those date objects for the dates you have in your example:
require 'date'
date1 = Date.parse("Thu, 21 Nov 2013") # => #<Date: 2013-11-21 ((2456618j,0s,0n),+0s,2299161j)>
date2 = Date.parse("Thu, 14 Nov 2013") # => #<Date: 2013-11-14 ((2456611j,0s,0n),+0s,2299161j)>
date3 = Date.parse("Thu, 20 Nov 2013") # => #<Date: 2013-11-20 ((2456617j,0s,0n),+0s,2299161j)>
date4 = Date.parse("Thu, 11 Nov 2013") # => #<Date: 2013-11-11 ((2456608j,0s,0n),+0s,2299161j)>
For testing:
test = [{"total"=>18, "type"=>"buy", "date"=>date1, "instrument_code"=>"food"},
{"total"=>92, "type"=>"buy", "date"=>date2, "instrument_code"=>"food"},
{"total"=>12, "type"=>"buy", "date"=>date3, "instrument_code"=>"drink"},
{"total"=> 1, "type"=>"buy", "date"=>date4, "instrument_code"=>"food"}]
Now we calculate what we need.
test_buy = test.select {|h| h["type"] == "buy"}
earliest = test_buy.min_by {|h| h["date"]}["date"]
# => #<Date: 2013-11-11 ((2456608j,0s,0n),+0s,2299161j)>
all_but_last = test.reject {|h| h["date"] == earliest}
# => [{"total"=>18, "type"=>"buy", "date"=>date1, "instrument_code"=>"food"},
{"total"=>92, "type"=>"buy", "date"=>date2, "instrument_code"=>"food"},
{"total"=>12, "type"=>"buy", "date"=>date3, "instrument_code"=>"drink"}]
or we could have used Enumerable#select
:
all_but_last = test.select {|h| h["date"] != earliest}
Note that here and below, the values of date1
, date2
and date3
will be displayed (e.g., #<Date: 2013-11-21 ((2456618j,0s,0n),+0s,2299161j)>
will be displayed for date1
); I've used the variable names here as placeholders to make this more readable. Also, all hashes h
with h["date"] = earliest
will be rejected (should there be more than one).
grouped = all_but_last.group_by {|h| h["instrument_code"]}
# => {"food" =>[{"total"=>18, "type"=>"buy", "date"=>date1, "instrument_code"=>"food"},
{"total"=>92, "type"=>"buy", "date"=>date2, "instrument_code"=>"food"}],
"drink"=>[{"total"=>12, "type"=>"buy", "date"=>date3, "instrument_code"=>"drink"}]}
keys = grouped.keys # => ["food", "drink"]
arr = keys.map {|k| [k, grouped[k].reduce(0) {|t,h| t + h["total"]}]}
# => [["food", 110], ["drink", 12]]
Hash[arr] # => {"food"=>110, "drink"=>12}
I have used a few temporary variables, including test_buy
, earliest
, all_but_last
, grouped
, keys
and arr
. You can eliminate some of these by "chaining". Here I'll show you how to get rid of some of them:
test_buy = test.select {|h| h["type"] == "buy"}
earliest = test_buy.min_by {|h| h["date"]}["date"]
grouped = test_buy.reject {|h| h["date"] == earliest}.group_by \
{|h| h["instrument_code"]}
Hash[grouped.keys.map {|k| [k, grouped[k].reduce(0) \
{|t,h| t + h["total"]}]}] # => {"food"=>110, "drink"=>12}
You may think this looks complicated, but after you gain experience with Ruby, it will look very natural and read easily. The extent to which you use chaining is a style preference, however.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With