7

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'
3
  • 1
    You're looking for something like Enumerable#find that returns what the block returned rather than which element caused the block to return the truthy value? Commented Mar 14, 2013 at 21:24
  • I think what you're doing is about all there is. You could pretty it up a bit and monkey patch it into Enumerable but that's about it. Commented Mar 14, 2013 at 23:20
  • possible duplicate of Functionally find mapping of first value that passes a test Commented Dec 4, 2013 at 0:37

4 Answers 4

2

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)}
Sign up to request clarification or add additional context in comments.

1 Comment

thanks. I need to return true in this case because this is the value that the block returns.
2

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"

Comments

1

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">

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.