简体   繁体   中英

How to extract information from hashes of array

I trying to get a new hashes of array, it will list every :name in the hashes of array(e) which was provided only once, and the value should be the total score which should be the sum of scores under same :name in the array(e).

e = [
{:name => "Oyster", :score => 1000},
{:name => "Tiger", :score => 2000},
{:name => "Frog", :score => 2300},
{:name => "Frog", :score => 1220},
{:name => "Tiger", :score => 200},
{:name => "Frog", :score => 1000},
{:name => "Oyster", :score => 2000},
{:name => "Frog", :score => 300},
{:name => "Monkey", :score => 500},
{:name => "Oyster", :score => 3300}
]

I searched google for 「hash array ruby」, and ruby's documentation but haven't got any very useful information yet.

I tried to solve this problem by doing the following:

f = e.map{|t|t[:name]}.uniq
n = Hash[f.map{|v|[v,0]}]
result = n.map{|key, value|e.map{|t| if key == t[:name]; n[key] += t[:score].to_i end}}

but the result is

[[1000, nil, nil, nil, nil, nil, 3000, nil, nil, 6300], [nil, 2000, nil, nil, 2200, nil, nil, nil, nil, nil], [nil, nil, 2300, 3520, nil, 4520, nil, 4820, nil, nil], [nil, nil, nil, nil, nil, nil, nil, nil, 500, nil]]

it's totally different from my expectation. if we want to reach the very neat result what should we do.

You can use group_by to group the hashes by :name and then sum :score: values using reduce with merge , like this:

e.group_by { |h| h[:name] }.map do |_, v| 
  v.reduce do |acc, h| 
    acc.merge(h) { |k, ov, nv| k == :name ? ov : ov + nv }
  end
end

#=> [{:name=>"Oyster", :score=>6300}, {:name=>"Tiger", :score=>2200}, {:name=>"Frog", :score=>4820}, {:name=>"Monkey", :score=>500}]

Explanation:

  • group_by { |h| h[:name] } group_by { |h| h[:name] } groups all hashes by name, which yields something like this:

     { "Oyster"=>[ {:name=>"Oyster", :score=>1000}, {:name=>"Oyster", :score=>2000}, {:name=>"Oyster", :score=>3300} ], "Tiger"=>[...] } 
  • .map iterates the resulting hash, ignoring the grouping key ( _ variable) and uses only the value ( v variable, which contains the array of grouped hashes) to sum the scores of each hash (see next step).

  • reduce sums the values of score by merging all hashes from the array together ( acc is the accumulated value/hash and h is the current value/hash being iterated). Notice that merge is used with a block so, instead of replacing the value of a duplicated key (like a merge without a block), it will replace its values using the code within the block where:

    • k is the current key of the hash being merged.
    • ov is the old value (ie value of the acc hash) for the given key.
    • nv is the new value (ie value of the h hash) for the given key.

So, after grouping the hashes by name , it iterates each group of hashes and merges them into one, evaluating first if the current key is :name and, if it does, it keeps the old value (eg "Oyster" ), otherwise (eg :score ) it will sum the old vale with the new value and replace the value of score with that result, accumulating the value on each iteration.

I know this post already has an accepted answer, but if you dont want the result to be an array of hashes and just want the given score for the given name you could:

e.inject({}) do |totals, current| 
  totals[current[:name]] = (totals[current[:name]] || 0) + current[:score] 
  totals
end

#=> {"Oyster"=>6300, "Tiger"=>2200, "Frog"=>4820, "Monkey"=>500}

Try This:

new_array = []
e.each{|h| new_array << { h[:name] => h[:score]} }
new_array.inject{|name, score| name.merge( score ){|k, old_v, new_v| old_v + new_v}}


#=> {"Oyster"=>6300, "Tiger"=>2200, "Frog"=>4820, "Monkey"=>500}

You could build a hash created using Hash::new with a default value of zero (sometimes called a counting hash ), then convert the hash to an array of hashes. See the doc for details.

e.each_with_object(Hash.new(0)) { |g,h| h[g[:name]] += g[:score] }.
  map { |k,v| { name: k, score: v } }
  #=> [{:name=>"Oyster", :score=>6300},
  #    {:name=>"Tiger",  :score=>2200},
  #    {:name=>"Frog",   :score=>4820},
  #    {:name=>"Monkey", :score=>500}]

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