-1

Is there a smarter way to sum all the values of keys in:

hash_1 = {
  41 => {"a" => {:a => 4, :b => 8, :c => 3}},
  56 => {"b" => {:a => 5, :b => 4, :c => 8}},
  57 => {"c" => {:a => 8, :b => 9, :c => 3}},
}

to get the result:

result_hash = {a: 17, b: 21, c: 14}

I can get the result above by iterating each hash, but I am looking for an alternative way to achieve it.

Holger Just
  • 49,152
  • 14
  • 106
  • 117

3 Answers3

3
a.
  values.
  flat_map(&:values).
  flat_map(&:to_a).
  each_with_object({}) { |(key, value), b| b[key] = b[key].to_i + value }
ndnenkov
  • 34,386
  • 9
  • 72
  • 100
3
hash_1.map { |_, v| v.to_a.last.last }.
       reduce { |e, acc| acc.merge(e) { |_, v1, v2| v1 + v2 } }
#⇒ {:a=>17, :b=>21, :c=>14}
Aleksei Matiushkin
  • 113,340
  • 9
  • 96
  • 151
1

You can improve Hash::each that it will allow you to easily perform your task:

module HashRecursive
    refine Hash do
        def each(recursive=false, &block)
            if recursive
                Enumerator.new do |yielder|
                    self.map do |key, value|
                        value.each(recursive=true).map{ |key_next, value_next|
                            yielder << [[key, key_next].flatten, value_next]
                        } if value.is_a?(Hash)
                        yielder << [[key], value]
                    end
                end.entries.each(&block)
            else
                super(&block)
            end
        end
        alias_method(:each_pair, :each)
    end
end
using HashRecursive

Here goes the solution for your task:

def sum_hashes(hash)        # This method will do the job
    result_hash = {}

    hash.each(recursive=true) do |keys, value|
        if keys.size == 2
            result_hash.merge!(value) do |key, value1, value2|
                [value1, value2].all? {|v| v.is_a?(Integer)} ? value1+value2 : value2
            end
        end
    end

    result_hash
end

# Here is your question's example

hash_1 = {
    41  =>  {"a" => {:a => 4, :b => 8, :c => 3}},
    56  =>  {"b" => {:a => 5, :b => 4, :c => 8}},
    57  =>  {"c" => {:a => 8, :b => 9, :c => 3}}
}
puts sum_hashes(hash_1)     # {:a=>17, :b=>21, :c=>14}

# This will work for anything that has similar pattern

hash_2 = {
    :a      =>  { "p"   =>  {:a =>  1, :b   =>  2, :c   =>  0,      :d  =>  5   }},
    3       =>  { "b"   =>  {:a =>  2, :b   =>  2, :c   =>  100,    :d  =>  0   }},
    404     =>  { "c"   =>  {:a =>  3, :b   =>  2, :c   =>  -100,   :d  =>  15  }},
    '24'    =>  { "2"   =>  {:a =>  4, :b   =>  2, :c   =>  300,    :d  =>  25  }},
    11      =>  { :h    =>  {:a =>  5, :b   =>  2, :c   =>  -350,   :d  =>  40  }},
    :x      =>  { "c"   =>  {:a =>  6, :b   =>  2, :c   =>  50,     :d  =>  5   }}
}
puts sum_hashes(hash_2)     # {:a=>21, :b=>12, :c=>0, :d=>90}

Take a look at this answer for more details.

MOPO3OB
  • 353
  • 3
  • 16
  • 1
    Thank you for the elaborate answer – Ferdinand Rosario Mar 08 '18 at 10:05
  • @FerdinandRosario your welcome. If you have any questions, I'd be glad to answer. – MOPO3OB Mar 08 '18 at 10:12
  • There are people reading SO from the smartphones/tablets. Please respect them by splitting very long lines to avoid the horizontal scrolling. – Aleksei Matiushkin Mar 08 '18 at 14:39
  • @mudasobwa I don't get your point. This sounds inessential. Most smartphones/tablets are completely okay with a bit of scrolling. And anyway that refinement assumes checking it out via computer, it won't be easier to understand it if it got more lines instead of long one. And... yes, I do respect others. Splitted long line into 3 specially for you. – MOPO3OB Mar 08 '18 at 16:53