'How to merge two objects and keep count

I am building a Rails 5.2 app. In this app I am working with statistics.

I generate two objects:

{
    "total_project": {
        "website": 1,
        "google": 1,
        "instagram": 1
    }
}

And this:

{
    "total_leads": {
        "website": 1,
        "google": 2,
        "client_referral": 1
    }
}

I need to merge these two objects into one single objects that increases the count. The desired result is:

{
    "total_both": {
        "website": 2,
        "google": 3,
        "instagram": 1,
        "client_referral": 1
    }
}

I tried this and it technically works, it merges the objects but the count is not updated:

@total_project = array_projects.group_by { |d| d[:entity_type] }.transform_values(&:count).symbolize_keys
        @total_leads = array_leads.group_by { |d| d[:entity_type] }.transform_values(&:count).symbolize_keys
        @total_sources = merged.merge **@total_project, **@total_leads

Please note that the attributes (sources) are dynamic from the database so I cannot hard code anything. The user can add their own sources.



Solution 1:[1]

@total_sources = @total_project.merge(@total_leads) do |key, ts_value, tp_value|
  ts_value + tp_value
end

If there can be more than 2 sources, put everything in an array and do.

@total_sources = source_array.reduce do |accumulator, next_source|
  accumulator.merge(next_source) { |key, v1, v2| v1 + v2 }
end

Solution 2:[2]

You may compute the desired result as follows.

arr = [{ "total_project": { "website": 1, "google": 1, "instagram": 1 } },
       { "total_leads": { "website": 1, "google": 2, "client_referral": 1 } }]
{ "total_both" => arr.flat_map(&:values)
                     .reduce { |h,g| h.merge(g) { |_,o,n| o+n } } }
  #=> {"total_both"=>{:website=>2, :google=>3, :instagram=>1, :client_referral=>1}}

Note that

arr.flat_map(&:values)
  #=> [{:website=>1, :google=>1, :instagram=>1},
  #    {:website=>1, :google=>2, :client_referral=>1}]

Had I used Array#map this would have been

arr.map(&:values)
  #=> [[{:website=>1, :google=>1, :instagram=>1}],
  #    [{:website=>1, :google=>2, :client_referral=>1}]]

See Enumerable#flat_map, Enumerable#reduce and the form of Hash#merge that takes a block (here { |_,o,n| o+n }) which returns the values of keys that are present in both hashes being merged. See the doc for merge for definitions of the three block variables (here _, o and n). I have named the first block variable (holding the common key) _ to signal to the reader that it is not used in the block calculation (a common Ruby convention).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ben Garcia
Solution 2