Merge hashes that have the same description field, summing the other fields I have an array of hashes of form:

{ description: 'string', b: float, c: float }


I need to get an array of merged hashes so that if hashes have same value at key description sum values of b and c, (of course keeping value of a unaltered), otherwise (if value of description is unique) just add hash to array.

Current solution:

array_of_hashes.inject([]) do |lines, line|
  if lines.none? { |l| l[:description] == line[:description] }
    lines &lt;&lt; line
  else
    lines
      .find { |l| l[:description] == line[:description] }
      .merge!(line) { |key, old_v, new_v| key == :description ? old_v : old_v + new_v }
    lines
  end
end


I am 100% sure there's a nicer solution.
Besides the code being a little more complex than it needs to be, I see two issues:


a performance problem: the .find { |l| l[:description] == line[:description] } is called for each element in the array of hashes, leading to an O(n^2) time algorithm (because the array lookup takes linear time)
a more subtle thing: the initial hashes are mutated by your code. When you do old_v + new_v that modifies the very hashes that have been provided as an input to your function/code. This might not be a problem for you, but I think it's good practice to avoid mutability where possible.


To solve these things, let's use a hash of hashes. We'll have a hash that has the :description values as key (thus allowing to look things up in constant time) and a hash containing the values to be cummulated.

Also, to save the verification of whether a certain key already exists or not, I will use the new {|hash, key| block } syntax when creating the outside hash.

Here's the code:

results = Hash.new { |h, k| h[k] = {b: 0, c: 0} }
array_of_hashes.each do |h|
  cummulative_hash = results[h[:description]]
  cummulative_hash[:b] += h[:b]
  cummulative_hash[:c] += h[:c]
end


At this point, results looks like this: 

{"string"=&gt;{:b=&gt;3, :c=&gt;6}, "other_string"=&gt;{:b=&gt;1, :c=&gt;2}}


This can be easily converted to your array format, like this:

results.map{|k, v| {description: k}.merge v}

=&gt; [{:description=&gt;"string", :b=&gt;3, :c=&gt;6},
    {:description=&gt;"other_string", :b=&gt;1, :c=&gt;2}]




In the example above I hardcoded the :b and :c keys.
For a more generic treatment of the keys to be cummulated, here's the (slightly adjusted) code:

results = Hash.new { |h, k| h[k] = {} }
array_of_hashes.each do |h|
  cummulative = results[h[:description]]
  h.each do |k, v|
    cummulative[k] = (cummulative[k] || 0) + h[k] if k != :description
  end
end
results.map{|k, v| {description: k}.merge v}