-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
  • 52,918
  • 14
  • 115
  • 123
Ferdy
  • 666
  • 7
  • 25
  • 3
    What does your attempt look like? Just so I know I'm not posting the same answer. – Sagar Pandya Mar 08 '18 at 07:43
  • 3
    Have to say, the fact that `a` and `b` mean 3 different things here doesn't help with readability. – ndnenkov Mar 08 '18 at 07:50
  • (at)ndn thanks for the answer, and i have corrected it. @sagar-pandya I trying to sum individual total to get grand total fo the above hash. – Ferdy Mar 08 '18 at 07:55

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
  • 35,425
  • 9
  • 72
  • 104
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
  • 119,336
  • 10
  • 100
  • 160
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
  • 383
  • 3
  • 16
  • 1
    Thank you for the elaborate answer – Ferdy 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