简体   繁体   中英

Ruby on Rails - Ruby, How to add the values from two hashes with same key, without overwriting the values?

first of all thank you for helping me with my SQL question, at this point.

Now I'm struggling with another thing, makes me think if I should quit being a programmer to be honest.

Anyway, my problem is : I have an array of hashes( and inside that hash another ) like this:

[
 {"A1"=>{:month=>1.0, :balance=>"0.0000", :price=>"9.0000"}}, 
 {"A1"=>{:month=>7.0, :balance=>"34030.0000", :price=>"34030.0000"}},
 {"A3"=>{:month=>4.0, :balance=>"34030.0000", :price=>"34030.0000"}},
 ...
]

What I'm trying to accomplish is that, if there are two values with the same key, ie "A1" add those values into one whole hash, without overwriting the old values and having the month as a key desired output:

[
 {"A1"=> { 1 => { :balance=> "0.0000", :price=>"9.0000"} },
         { 7 => { :balance => "34030.0000", :price => "34030.0000" } }},
  and so on... 
]

Is this posible?

Due the current format of the data you have, you'll need more than a couple of transformations. Most of them based on transforming the values of the resulting hash, after grouping the hashes in the array by their only key:

data
  .group_by { |hash| hash.keys.first }                                     # (1)
  .transform_values { |value| value.flat_map(&:values) }                   # (2)
  .transform_values { |values| values.index_by { |value| value[:month] } } # (3)

The first transformation is to group the current object, holding an array of hashes, by its only hash key, hence the keys.first , resulting in:

{
  "A1"=>[
    {"A1"=>{:month=>1.0, :balance=>"0.0000", :price=>"9.0000"}}, 
    {"A1"=>{:month=>7.0, :balance=>"34030.0000", :price=>"34030.0000"}}
  ],
  "A3"=>[{"A3"=>{:month=>4.0, :balance=>"34030.0000", :price=>"34030.0000"}}]
}

The second, is to extract only the values from each hash, in the resulting hash, with arrays of hashes:

{
  "A1"=>[
    {:month=>1.0, :balance=>"0.0000", :price=>"9.0000"}, 
    {:month=>7.0, :balance=>"34030.0000", :price=>"34030.0000"}
  ],
  "A3"=>[{:month=>4.0, :balance=>"34030.0000", :price=>"34030.0000"}]
}

Then, it just lacks to transform the array of hashes, to simply an hash, whose keys are the value of month :

{
  "A1"=>{
    1.0=>{:month=>1.0, :balance=>"0.0000", :price=>"9.0000"}, 
    7.0=>{:month=>7.0, :balance=>"34030.0000", :price=>"34030.0000"}
  },
  "A3"=>{4.0=>{:month=>4.0, :balance=>"34030.0000", :price=>"34030.0000"}}
}

@Sebastian's answer is excellent. For variety, let's also consider an iterative approach. Not sure if it's more efficient or easier to understand, but it's always good to understand multiple perspectives.

Setting up the input data you gave us:

arr = [
 {"A1"=>{:month=>1.0, :balance=>"0.0000", :price=>"9.0000"}}, 
 {"A1"=>{:month=>7.0, :balance=>"34030.0000", :price=>"34030.0000"}},
 {"A3"=>{:month=>4.0, :balance=>"34030.0000", :price=>"34030.0000"}}]

We create a new empty hash for our results.

new_hash = {}

And now iterating over the original data. We're going to make some assumptions about the form of the data.

# We know each thing is going to be a hash.
arr.each do |hsh|
    # Set up some convenient variables for keys and 
    # values we'll need later.
    key = hsh.keys.first
    value = hsh.values.first
    month = value[:month]
    
    # If the output hash doesn't yet have the key,
    # give it the key and assign an empty hash to it.
    new_hash[key] ||= {}
    # Assign the value to the hash, keyed to the current month.
    new_hash[key][month] = value
    # ... and get rid of the month key that's now redundant.
    new_hash[key][month].delete(:month)
end

And the result is:

{"A1"=>{1.0=>{:balance=>"0.0000", :price=>"9.0000"}, 
        7.0=>{:balance=>"34030.0000", :price=>"34030.0000"}}, 
 "A3"=>{4.0=>{:balance=>"34030.0000", :price=>"34030.0000"}}}

Arguably it would be more useful for the desired return value to be a hash:

h = {"A1"=>{1=>{:balance=>    "0.0000", :price=>    "9.0000"},
            7=>{:balance=>"34030.0000", :price=>"34030.0000"}},
     "A3"=>{4=>{:balance=>"34030.0000", :price=>"34030.0000"}}}

That way you could write, for example:

require 'bigdecimal'

BigDecimal(h['A1'][7][:price]) 
  #=> 0.3403e5

See BigDecimal . BigDecimal is generally used in financial calculations because it avoids round-off errors.

This result can be obtained by changing the values of :month to integers in arr :

arr = [
  {"A1"=>{:month=>1, :balance=>    "0.0000", :price=>    "9.0000"}}, 
  {"A1"=>{:month=>7, :balance=>"34030.0000", :price=>"34030.0000"}},
  {"A3"=>{:month=>4, :balance=>"34030.0000", :price=>"34030.0000"}}
]

and by computing:

h = arr.each_with_object({}) do |g,h|
  k,v = g.flatten
  (h[k] ||= {}).update(v[:month]=>v.reject { |k,_| k == :month })
end

See Hash#flatten , Hash#update (aka merge! ) and Hash#reject .

One could alternatively write:

h = arr.each_with_object(Hash.new { |h,k| h[k] = {} }) do |g,h|
  k,v = g.flatten
  h[k].update(v[:month]=>v.reject { |k,_| k == :month })
end

See the form of Hash::new that takes a block.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM