Sum 2 hashes attributes with the same key
a_hash = {'a' => 30, 'b' => 14}
b_hash = {'a' => 4, 'b' => 23, 'c' => 7}
a_hash.merge(b_hash){ |k, a_value, b_value| a_value + b_value }
=> {"a"=>34, "b"=>37, "c"=>7}
b_hash.merge(a_hash){ |k, b_value, a_value| a_value + b_value }
=> {"a"=>34, "b"=>37, "c"=>7}
Group hashes by keys and sum the values
ar = [{"Vegetable"=>10}, {"Vegetable"=>5}, {"Dry Goods"=>3}, {"Dry Goods"=>2}]
p ar.inject{|memo, el| memo.merge( el ){|k, old_v, new_v| old_v + new_v}}
#=> {"Vegetable"=>15, "Dry Goods"=>5}
Hash.merge
with a block runs the block when it finds a duplicate; inject
without a initial memo
treats the first element of the array as memo
, which is fine here.
how do I merge array of hashes summing by the value?
Let's group by id and map each result to a structure where the pages are the sum of all the pages for that id
array.
group_by { |item| item[:id] }.
map do |id, items|
page_sum = items.sum { |i| i[:pages] }
Hash[:id, id, :book, items.first[:book], :pages, page_sum]
end
Get sum of key values in deep array of hashes
Regarding ruby-on-rails
tag, you can use Ruby method #inject
with Rails method #deep_merge
:
array.inject do |hash1, hash2|
hash1.deep_merge(hash2) do |key, hash1_val, hash2_val|
hash1_val + hash2_val
end
end
=> {:john=>{:win=>30, :lose=>60}, :mike=>{:win=>15, :lose=>15}}
compute hash value of same key ruby
If you just want to compute equal keys in the hash what you are looking for is the merge
method in the Hash class.
https://ruby-doc.org/core-2.2.1/Hash.html#method-i-merge
Returns a new hash containing the contents of other_hash and the
contents of hsh. If no block is specified, the value for entries with
duplicate keys will be that of other_hash. Otherwise the value for
each duplicate key is determined by calling the block with the key,
its value in hsh and its value in other_hash.
When you pass a block to the merge
method it will yield both old value and new value, and the you can do your computation there.
For instance:
hash = {"a"=>2, "b"=>3}
hash2 = {"c"=>4, "a"=>5}
result = hash.merge(hash2) { |key, old_val, new_val| old_val + new_val }
p result #=> {"a"=>7, "b"=>3, "c"=>4}
Merge or sum 2 arrays on keys in ruby
You could do it thusly:
(a + b).group_by(&:first).map { |k, v| [k, v.map(&:last).inject(:+)] }
First you put the arrays together with +
since you don't care about a
and b
, you just care about their elements. Then the group_by
partitions the combined array by the first element so that the inner arrays can easily be worked with. Then you just have to pull out the second (or last) elements of the inner arrays with v.map(&:last)
and sum them with inject(:+)
.
For example:
>> a = [[1,10],[2,20],[3,30]]
>> b = [[1,50],[3,70]]
>> (a + b).group_by(&:first).map { |k,v| [k, v.map(&:last).inject(:+)] }
=> [[1, 60], [2, 20], [3, 100]]
Sum ruby hash values
I offer the following observations on your inject
code:
- none of the variables need be instance variables; local variables (no
@
) would suffice; test.group_by {|i| i.type}...
should betest.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.
Merge hashes containing same key & value pair
You can compute the desired result as follows.
def doit(arr)
arr.each_with_object({}) do |g,h|
h.update(g.reject { |k,_| k==:entity_ids }=>g) do |_,o,n|
o.merge(entity_ids: o[:entity_ids] + n[:entity_ids])
end
end.values
end
doit(arr1)
#=> [{:entity_type=>"Mac", :entity_ids=>[3, 2], :cascade_id=>2, :location_id=>1},
# {:entity_type=>"Mac", :entity_ids=>[9, 10], :cascade_id=>4, :location_id=>1}]
doit(arr2)
#=> [{:entity_type=>"Sub", :entity_ids=>[7, 10], :mac_id=>5, :cascade_id=>1,
# :location_id=>1},
# {:entity_type=>"Sub", :entity_ids=>[4], :mac_id=>2, :cascade_id=>1,
# :location_id=>1},
# {:entity_type=>"Sub", :entity_ids=>[11], :mac_id=>7, :cascade_id=>2,
# :location_id=>2}]
This uses the form of Hash#update (aka merge!
) that employs a block to determine the values of keys that are present in both hashes being merged. See the doc for an explanation of the block variables k
, o
and n
.
If doit
's argument is arr1
, the steps are as follows.
arr = arr1
e = arr.each_with_object({})
#=> #<Enumerator: [{:entity_type=>"Mac", :entity_ids=>[3], :cascade_id=>2,
# :location_id=>1},
# {:entity_type=>"Mac", :entity_ids=>[2], :cascade_id=>2,
# :location_id=>1},
# {:entity_type=>"Mac", :entity_ids=>[9], :cascade_id=>4,
# :location_id=>1},
# {:entity_type=>"Mac", :entity_ids=>[10], :cascade_id=>4,
# :location_id=>1}
# ]:each_with_object({})>
The first element of the enumerator is passed to the block and values are assigned to the block variables.
g, h = e.next
#=> [{:entity_type=>"Mac", :entity_ids=>[3], :cascade_id=>2, :location_id=>1}, {}]
g #=> {:entity_type=>"Mac", :entity_ids=>[3], :cascade_id=>2, :location_id=>1}
h #=> {}
Compute the (only) key for the hash to be merged with h
.
a = g.reject { |k,_| k==:entity_ids }
#=> {:entity_type=>"Mac", :cascade_id=>2, :location_id=>1}
Perform the update operation.
h.update(a=>g)
#=> {{:entity_type=>"Mac", :cascade_id=>2, :location_id=>1}=>
# {:entity_type=>"Mac", :entity_ids=>[3], :cascade_id=>2, :location_id=>1}}
This is the new value of h
. As h
(which was empty) did not have the key
{:entity_type=>"Mac", :cascade_id=>2, :location_id=>1}
the block was not used to determine the value of this key in the merged hash.
Now generate the next value of the enumerator e
, pass it to the block, assign values to the block variables and perform the block calculation.
g, h = e.next
#=> [{:entity_type=>"Mac", :entity_ids=>[2], :cascade_id=>2, :location_id=>1},
# {{:entity_type=>"Mac", :cascade_id=>2, :location_id=>1}=>
# {:entity_type=>"Mac", :entity_ids=>[3], :cascade_id=>2, :location_id=>1}}]
g #=> {:entity_type=>"Mac", :entity_ids=>[2], :cascade_id=>2, :location_id=>1}
h #=> {{:entity_type=>"Mac", :cascade_id=>2, :location_id=>1}=>
# {:entity_type=>"Mac", :entity_ids=>[3, 2], :cascade_id=>2, :location_id=>1}}
a = g.reject { |k,_| k==:entity_ids }
#=> {:entity_type=>"Mac", :cascade_id=>2, :location_id=>1}
h.update(a=>g) do |_,o,n|
puts "_=#{_}, o=#{o}, n=#{n}"
o.merge(entity_ids: o[:entity_ids] + n[:entity_ids])
end
#=> {{:entity_type=>"Mac", :cascade_id=>2, :location_id=>1}=>
# {:entity_type=>"Mac", :entity_ids=>[3, 2], :cascade_id=>2, :location_id=>1}}
This is the new value of h
. As both g
and h
have the key a
the block is consulted to obtain the value of that key in the merged hash (new h
). The values of that block variables are printed.
_={:entity_type=>"Mac", :cascade_id=>2, :location_id=>1},
o={:entity_type=>"Mac", :entity_ids=>[3], :cascade_id=>2, :location_id=>1},
n={:entity_type=>"Mac", :entity_ids=>[2], :cascade_id=>2, :location_id=>1}
h[:entity_ids]
is therefore replaced with
o[:entity_ids] + o[:entity_ids]
#=> [3] + [2] => [3, 2]
The calculations for the two remaining elements of e
are similar, at which time
h #=> {{ :entity_type=>"Mac", :cascade_id=>2, :location_id=>1 }=>
# { :entity_type=>"Mac", :entity_ids=>[3, 2], :cascade_id=>2, :location_id=>1 },
# { :entity_type=>"Mac", :cascade_id=>4, :location_id=>1 }=>
# { :entity_type=>"Mac", :entity_ids=>[9, 10], :cascade_id=>4, :location_id=>1 }}
The final step is to return the values of this hash.
h.values
#=> <as shown above>
Note that some of the block variables are underscores (_
). Though they are valid local variables, they are commonly used to indicate that they are not used in the block calculation. An alternative convention is to have the unused block variable begin with an underscore, such as _key
.
Related Topics
How to Get a Stack Trace Object in Ruby
Rails 3 Additional Session Configuration Options (Key, Expires_After, Secure)
How to Remove Ruby from Ubuntu
How to Use Watir::Waiter::Wait_Until to Force Chrome to Wait
Differences Between Proc and Lambda
Millisecond Resolution of Datetime in Ruby
Ruby - Dynamically Add Property to Class (At Runtime)
Ruby: Conditional Matrix? Case with Multiple Conditions
Devise: How to Override Devise Error Messages on Password Change
Rbenv: Surviving Without Gemsets
Homebrew Install: Failed During: Git Fetch Origin Master:Refs/Remotes/Origin/Master -N --Depth=1
Test Whether a Variable Equals Either One of Two Values
What Does :: (Double Colon) Mean in Ruby
How to Add MAC-Specific Gems to Bundle on MAC But Not on Linux
Rails "Undefined Method for Activerecord_Associations_Collectionproxy"