akonsu
akonsu

Reputation: 29536

find first element in array for which block returns true and return the block's return value

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

Answers (4)

ribamar
ribamar

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

peter
peter

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

haejeong87
haejeong87

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

Alex.Bullard
Alex.Bullard

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

Related Questions