简体   繁体   中英

Iterating through an array to find a hash value

I have two arrays. One that stores a strings name and one that stores hashes people . I'd like to filter the people array and find the one hash that either has a close/full match with one of the strings in my name array. So for instance John from names and John Doe would be consider a match because the both have John in them.

names = ["John", "Jane"]

people = [{name: "John Doe"}, {name: "Bill Bradley"}]

I tried matching the two using the select method but it returned an empty array?

people.select{|person| person["name"] == names.any?}

Try this:

matches = people.select do |person|
  !(names & person[:name].split(' ')).empty?
end

p matches #=> [{:name=>"John Doe"}]

Basically, you're first separating the hash value into individual words with property[:name].split(' ') . The result of this will be array like ['John', 'Doe'] . You then check if this newly created array has any common elements with the names array using the & operator. Thus, you get names & property[:name].split(' ') . If no matches are produced, you'll have an empty array which is NOT what you want, thus the ! (not) operator. You could also do this if it's more intuitive:

matches = people.reject do |person|
  (names & person[:name].split(' ')).empty?
end

Also, if you'd like to get the hash itself rather than the hash wrapped in an array (or hashes, if there are multiple results), replace select with detect .

This would be one way:

names = ["John", "Jane"]    
people = [{name: "Johnny Jones"}, {name: "John Doe"}, {name: "Bill Bradley"}]

people.select { |h| hname = h[:name]; names.any? { |n| hname[/\b#{n}\b/i] } }
  #=> [{:name=>"John Doe"}] 

The steps:

enum = people.select
  #=> #<Enumerator: [{:name=>"Johnny Jones"}, {:name=>"John Doe"},
  #                  {:name=>"Bill Bradley"}]:select>

The first element of the enumerator is passed into the block and assigned to the block variable h :

h = enum.next
  #=> {:name=>"Johnny Jones"} 

Next I create a temporary variable so we don't have to retieve h[:name] for each element of enum passed into the block:

hname = h[:name]
  #=> "Johnny Jones"

Then see if any element of names matches "Johnny Jones" :

names.any? { |n| hname[/\b#{n}\b/i] }
  #=> ["John", "Jane"].any? { |n| "Johnny Jones"[/\b#{n}\b/i] }
  #=> false

In the last expression, "John" is passed into the block and assigned to the block variable n . We then have:

"Johnny Jones"[/\bJohn\b/i]
  #=> nil

The first word break ( \\b ) requires that "John" be immediately preceded by a non-word character (or be the first character of the string); the second word break requires that "John" be immediately followed by a non-word character (or be the last character in the string). Therefore, "John" does not match "Johnny Jones" .

The next (and last) element of names ( "Jane" is then passed into the block and assigned to n . "Johnny Jones" doesn't match "Jane" either, so any? returns false and poor Johnny doesn't get selected.

In a similar manner, John Doe is selected and Bill Bradley is sent home.

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