简体   繁体   中英

search for matching key-value pair in an array of hashes and return true if match found

Suppose I have this array of hashes:

[ {"nutrient"=>"protein", "value"=>12, "calories"=>48, "unit"=>"g"},
  {"nutrient"=>"fat", "value"=>5, "calories"=>45, "unit"=>"g"},
  {"nutrient"=>"fibre", "value"=>1, "calories"=>nil, "unit"=>"g"},
  {"nutrient"=>"carbohydrates", "value"=>67, "calories"=>268, "unit"=>"g"},
  {"nutrient"=>"calcium", "value"=>42, "calories"=>nil, "unit"=>"mg"}]

How can I return boolean true if (nutrient value is equal to 'carbohydrates' and it's calories are equal to 268 ) and (if nutrient value is equal to 'protein' and it's calories are equal to 48)

That is, in short I want to return true for above array of hash.

a.count do |hash|
  (hash['nutrient'] == 'carbohydrates' && hash['calories'] == 268) || (hash['nutrient'] == 'protein' && hash['calories'] == 48)
end == 2

What this does, is it counts any element in the collection, that passes EITHER this condition:

hash['nutrient'] == 'carbohydrates' && hash['calories'] == 268

OR this one

hash['nutrient'] == 'protein' && hash['calories'] == 48

And it returns true if there are exactly two matches.

Perhaps this:

a.any? {|h| h['nutrient'] == 'carbohydrates' && h['calories'] == 268} && 
a.any? {|h| h['nutrient'] == 'proteins'      && h['calories'] == 48}
# => true

Or as a nice finder method:

a = [ {"nutrient"=>"protein", "value"=>12, "calories"=>48, "unit"=>"g"}, {"nutrient"=>"fat", "value"=>5, "calories"=>45, "unit"=>"g"}, {"nutrient"=>"fibre", "value"=>1, "calories"=>nil, "unit"=>"g"}, {"nutrient"=>"carbohydrates", "value"=>67, "calories"=>268, "unit"=>"g"}, {"nutrient"=>"calcium", "value"=>42, "calories"=>nil, "unit"=>"mg"}]

def all?(array, *finders)
  finders.all? do |finder|
    array.any? { |hash| finder.all? { |k,v| hash[k] == v } }
  end
end

puts all?(
  a, 
  {'nutrient' => 'protein', 'value' => 12}, 
  {'nutrient' => 'fat', 'calories' => 45}
).inspect

puts all?(
  a, 
  {'nutrient' => 'protein', 'value' => 12},
  {'nutrient' => 'fat', 'calories' => 46}
).inspect

Outputs:

true
false

The method will return false if a matching hash is not found from any of the hashes inside the array.

Assuming that the nutrient values are unique, you could build a calories hash via:

calories = a.each_with_object({}) { |e, h| h[e['nutrient']] = e['calories'] }
#=> {"protein"=>48, "fat"=>45, "fibre"=>nil, "carbohydrates"=>268, "calcium"=>nil}

And check for the values via:

calories['carbohydrates'] == 268 && calories['protein'] == 48
#=> true

Below, arr is the given array of hashes (as in the example) and target is a second array of hashes such as the following.

target = [{ "nutrient"=>"carbohydrates", "calories"=>268 },
          { "nutrient"=>"protein", "calories"=>48 }]

We wish to determine if, for all hashes h in target , there is a hash in arr having the same key-value pairs as those that comprise h . We can do that with the following method.

Code

def all_match?(arr, target)
  target.all? { |h| arr.any? { |g| g.merge(h) == g } }
end

Examples

For the above value of target , we obtain

all_match?(arr, target)
  #=> true

Now let's modify target so there is no match in arr .

target[1]["calories"] = 50
  #=> 50
target
  #=> [{"nutrient"=>"carbohydrates", "calories"=>268},
  #    {"nutrient"=>"protein", "calories"=>50}]
all_match?(arr, target)
  #=> false

Explanation

In the second example the steps are as follows. The first element of target is passed to the block, resulting in

h = target[0]
  #=> {"nutrient"=>"carbohydrates", "calories"=>268}

and the block calculation is performed.

arr.any? { |g| g.merge(h) == g }
  #=> true

As it happens, the matching hash is

g = arr[3]
  #=>{"nutrient"=>"carbohydrates", "value"=>67, "calories"=>268, "unit"=>"g"},
g.merge(h) == g
  #=> true

What I've done is merge h into each hash in arr until/if one is found, g , that returns g unchanged by the merge. For that to happen all key-value pairs in h must be present in g .

Because true was returned, all? passes the second element of target to the block and the block calculation is performed.

h = target[1]
  #=> {"nutrient"=>"protein", "calories"=>50} 
arr.any? { |g| g.merge(h) == g }
  #=> false  

Having found a false , all? returns false .

a=[ {"nutrient"=>"protein", "value"=>12, "calories"=>48, "unit"=>"g"}, {"nutrient"=>"fat", "value"=>5, "calories"=>45, "unit"=>"g"}, {"nutrient"=>"fibre", "value"=>1, "calories"=>nil, "unit"=>"g"}, {"nutrient"=>"carbohydrates", "value"=>67, "calories"=>268, "unit"=>"g"}, {"nutrient"=>"calcium", "value"=>42, "calories"=>nil, "unit"=>"mg"}]

    a.map! do |value|
      if value["nutrient"].eql?'protein' and value["calories"].eql?268
        true
      elsif  value["nutrient"].eql?'protein' and value["calories"].eql?48
        true
      else
        false
      end
    end

puts a.inspect  

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