Reputation: 29536
I need to iterate over an array and apply a supplied block to each element, and return the first true value returned by the block, which implies that I need to stop immediately as soon as I get a true value.
below is my code. I am a ruby newbie, and I am not sure if this code is reinventing the wheel. Maybe there is a library method or methods that can do that already? or may be this code can be simplified?
RS = {
:x => %w(\d+ a\d+ bb\d+ ccc\d+).map{|x| /^#{x}$/},
:y => %w(\w+ 1\w+ 22\w+ 333\w+).map{|x| /^#{x}$/}
}.freeze
def find s, t
r = RS[s]
if r
r.each do |p|
m = p.match t
return m if m
end
nil
end
end
p find :x, 'bb12345'
Upvotes: 7
Views: 3194
Reputation: 1495
This is duplicated with: Ruby - Array.find, but return the value the block
You want a lazy map:
[nil, 1, 2, 3].lazy.map{|i| i && i.to_s}.find{|i| i}
# => "1"
Upvotes: 2
Reputation: 42192
Hopefully still actual: here a solution using detect, i made it possible to verbose the output so you can see which expressions are evaluated before returning a hit.
def find_match symbol, string , verbose = false, match = nil
if verbose
RS.detect{|x,v|x==symbol;v.detect{|re|puts re;match=string.match(/#{re}/)}}
else
RS.detect{|x,v|x==symbol;v.detect{|re|match=string.match(/#{re}/)}}
end
match
end
p find_match :x, 'bb12345'
p find_match :x, 'ee12345' , true #verbose output
p find_match :x, '12345'
p find_match :y, '22abcd'
#<MatchData "bb12345">
(?-mix:^\d+$)
(?-mix:^a\d+$)
(?-mix:^bb\d+$)
(?-mix:^ccc\d+$)
(?-mix:^\w+$)
#<MatchData "ee12345">
#<MatchData "12345">
#<MatchData "22abcd">
Upvotes: 1
Reputation: 957
If your regex patterns are simple, you can just apply the regex again at the end, maybe.
Something like:
def find(s,t)
r = RS[s] and r.find{|p| p.match(t)}.try(:match, t)
end
Although it makes one redundant call to match, it is easier to understand.
First, find the pattern you want, then use that pattern.
Upvotes: 0
Reputation: 5563
If you want the result of the block you could do it this way. This will iterate over the whole array, but wont evaluate any matches after the first one.
def find(s,t)
RS[s].inject(nil) {|m, p| m || p.match(t)}
end
You can break out early doing something like this
RS[s].inject(nil) {|m, p| (m && (break m)) || p.match(t)}
Upvotes: 2