Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Regular expression returns only one match

Tags:

regex

ruby

I have a set of keywords. Any keyword can contain a space symbol ['one', 'one two']. I generate a regexp from these kyewords like this /\b(?i:one|one\ two|three)\b/. Full example below:

keywords = ['one', 'one two', 'three']
re = /\b(?i:#{ Regexp.union(keywords).source })\b/
text = 'Some word one and one two other word'
text.downcase.scan(re)

the result of this code is

=> ["one", "one"]

How to find match of the second keyword one two and get result like this?

=> ["one", "one two"]
like image 454
Edward Avatar asked Sep 14 '26 07:09

Edward


1 Answers

Regexes are eager to match. Once they find a match, they don't try to find another possibly longer one (with one important exception).

/\b(?i:one|one\ two|three)\b/ is never going to match one two because it will always match one first. You'd need /\b(?i:one two|one|three)\b/ so it tries one two first. Probably the simplest way to automate this is to sort by the longest keywords first.

keywords = ['one', 'one two', 'three']
re = Regexp.union(keywords.sort { |a,b| b.length <=> a.length }).source
re = /\b#{re}\b/i;
text = 'Some word one and one two other word'
puts text.scan(re)

Note that I set the whole regex to be case-insensitive, easier to read than (?:...), and that downcasing the string is redundant.


The exception is repetition like +, * and friends. They are greedy by default. .+ is going to match as many characters as it can. That's greedy. You can make it lazy, to match the first thing it sees, with a ?. .+? will match a single character.

"A foot of fools".match(/(.*foo)/);  # matches "A foot of foo"
"A foot of fools".match(/(.*?foo)/);  # matches "A foo"
like image 137
Schwern Avatar answered Sep 16 '26 22:09

Schwern



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!