简体   繁体   中英

How to find the largest value of a hash in an array of hashes

In my array, I'm trying to retrieve the key with the largest value of "value_2", so in this case, "B":

myArray = [
  "A" => {
    "value_1" => 30,
    "value_2" => 240
  },
  "B" => {
    "value_1" => 40,
    "value_2" => 250
  },
  "C" => {
    "value_1" => 18,
    "value_2" => 60
  }
]

myArray.each do |array_hash|
 array_hash.each do |key, value|
  if value["value_2"] == array_hash.values.max
   puts key
  end
 end
end

I get the error:

"comparison of Hash with Hash failed (ArgumentError)".  

What am I missing?

Though equivalent, the array given in the question is generally written:

arr = [{ "A" => { "value_1" => 30, "value_2" => 240 } },
       { "B" => { "value_1" => 40, "value_2" => 250 } },
       { "C" => { "value_1" => 18, "value_2" =>  60 } }]

We can find the desired key as follows:

arr.max_by { |h| h.values.first["value_2"] }.keys.first
  #=> "B"

SeeEnumerable#max_by . The steps are:

g = arr.max_by { |h| h.values.first["value_2"] }
  #=> {"B"=>{"value_1"=>40, "value_2"=>250}} 
a = g.keys
  #=> ["B"] 
a.first
  #=> "B" 

In calculating g , for

h = arr[0]
  #=> {"A"=>{"value_1"=>30, "value_2"=>240}}

the block calculation is

a = h.values
  #=> [{"value_1"=>30, "value_2"=>240}] 
b = a.first
  #=> {"value_1"=>30, "value_2"=>240} 
b["value_2"]   
  #=> 240 

Suppose now arr is as follows:

arr << { "D" => { "value_1" => 23, "value_2" => 250 } }
  #=> [{"A"=>{"value_1"=>30, "value_2"=>240}},
  #    {"B"=>{"value_1"=>40, "value_2"=>250}},
  #    {"C"=>{"value_1"=>18, "value_2"=>60}},
  #    {"D"=>{"value_1"=>23, "value_2"=>250}}] 

and we wish to return an array of all keys for which the value of "value_2" is maximum ( ["B", "D"] ). We can obtain that as follows.

max_val = arr.map { |h| h.values.first["value_2"] }.max
  #=> 250
arr.select { |h| h.values.first["value_2"] == max_val }.flat_map(&:keys)
  #=> ["B", "D"]

flat_map(&:keys) is shorthand for:

flat_map { |h| h.keys }

which returns the same array as:

map { |h| h.keys.first }

See Enumerable#flat_map .

Code

p myArray.pop.max_by{|k,v|v["value_2"]}.first

Output

"B"

I guess the idea of your solution is looping through each hash element and compare the found minimum value with hash["value_2"] .

But you are getting an error at

if value["value_2"] == array_hash.values.max

Because the array_hash.values is still a hash

{"A"=>{"value_1"=>30, "value_2"=>240}}.values.max 
#=> {"value_1"=>30, "value_2"=>240}

It should be like this:

max = nil
max_key = ""
myArray.each do |array_hash|
  array_hash.each do |key, value|
    if max.nil? || value.values.max > max
      max = value.values.max
      max_key = key
    end
  end
end

# max_key #=> "B"

Another solution:

myArray.map{ |h| h.transform_values{ |v| v["value_2"] } }.max_by{ |k| k.values }.keys.first

I'd use:

my_array = [
  "A" => {
    "value_1" => 30,
    "value_2" => 240
  },
  "B" => {
    "value_1" => 40,
    "value_2" => 250
  },
  "C" => {
    "value_1" => 18,
    "value_2" => 60
  }
]

h = Hash[*my_array] 
# => {"A"=>{"value_1"=>30, "value_2"=>240},
#     "B"=>{"value_1"=>40, "value_2"=>250},
#     "C"=>{"value_1"=>18, "value_2"=>60}}

k = h.max_by { |k, v| v['value_2'] }.first # => "B"

Hash[*my_array] takes the array of hashes and turns it into a single hash. Thenmax_by will iterate each key/value pair, returning an array containing the key value "B" and the sub-hash, making it easy to grab the key using first :

k = h.max_by { |k, v| v['value_2'] } # => ["B", {"value_1"=>40, "value_2"=>250}]

You asked "What am I missing?".

I think you are missing a proper understanding of the data structures that you are using. I suggest that you try printing the data structures and take a careful look at the results.

The simplest way is p myArray which gives:

[{"A"=>{"value_1"=>30, "value_2"=>240}, "B"=>{"value_1"=>40, "value_2"=>250}, "C"=>{"value_1"=>18, "value_2"=>60}}]

You can get prettier results using pp :

require 'pp'
pp myArray

yields:

[{"A"=>{"value_1"=>30, "value_2"=>240},
  "B"=>{"value_1"=>40, "value_2"=>250},
  "C"=>{"value_1"=>18, "value_2"=>60}}]

This helps you to see that myArray has only one element, a Hash.

You could also look at the expression array_hash.values.max inside the loop:

myArray.each do |array_hash|
  p array_hash.values
end

gives:

[{"value_1"=>30, "value_2"=>240}, {"value_1"=>40, "value_2"=>250}, {"value_1"=>18, "value_2"=>60}]

Not what you expected? :-)

Given this, what would you expect to be returned by array_hash.values.max in the above loop?

Use p and/or pp liberally in your ruby code to help understand what's going on.

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