regex negative look-ahead in ruby 1.9.3 vs 2.0.0
Asked Answered
N

3

6

I need to match members of an array that end with "bar" but do not start with "foo", and put the result in a new array.

Looking at the docs for 1.9.3 and 2.0.0, they seem to support negative look-ahead with the same syntax. Negative look-ahead works as I expect in ruby 2.0.0 but doesn't seem to work in ruby 1.9.3:

["foo a.bar", "b.bar"].grep(/(?!^foo\s).*\.bar$/)
# => ["b.bar"]              (ruby 2.0.0)
# => ["foo a.bar", "b.bar"] (ruby 1.9.3)

The ruby version on this infrastructure will be upgraded in 4 months, but changing the version sooner isn't an option. How can I make this work in 1.9.3 and preferably continue to work in 2.0?

Neurologist answered 11/4, 2014 at 5:53 Comment(2)
I think this is Ruby 2.0's bug rather than Ruby 1.9.3's.Bethannbethanne
I just reported this bug to Ruby Core.Bethannbethanne
P
8

Better use this, which looks more convincing:

matched = array.grep(/^(?!foo\s).*\.bar$/)

NOT starting with foo

this will work in both 2.1.1 and 1.9.3

only if you want to see what I did:

# ruby-1.9.3-p362
array = ["foo a.bar", "b.bar"] 
# => ["foo a.bar", "b.bar"] 
matched = array.grep(/(?!^foo\s).*\.bar$/)
# => ["foo a.bar", "b.bar"] 
matched = array.grep(/^(?!foo\s).*\.bar$/)
# => ["b.bar"] 
matched = array.grep(/(?!^foo\s).*\.bar$/)
# => ["foo a.bar", "b.bar"]

# ruby-2.1.1
array = ["foo a.bar", "b.bar"] 
# => ["foo a.bar", "b.bar"] 
matched = array.grep(/(?!^foo\s).*\.bar$/)
# => ["b.bar"] 
matched = array.grep(/^(?!foo\s).*\.bar$/)
# => ["b.bar"] 
Pauiie answered 11/4, 2014 at 6:11 Comment(0)
P
4

Simple solution would be not to use negative look-ahead, if it seems problematic in ruby version you are bound to on production servers. If your example is particular enough you could use select and convenient String methods:

array.select {|str| !str.starts_with?('foo') && str.ends_with?('bar') }
Parmenter answered 11/4, 2014 at 5:58 Comment(1)
I would also expect this to be faster (especially if you optimise to have lowest-frequency match first). Although OP may have simplified the question and in reality have pattern-matches instead of "foo" and "bar".Chansoo
O
3

It's your regex that's faulty, not Ruby. Ruby 2 seems to be a little more forgiving, is all.

The start anchor (^) should be before the lookahead, not in it. When it fails to match foo at the beginning of the line, the regex engine bumps forward one position and tries again. It's not at the start of the string any more, so ^ doesn't match, the negative lookahead reports success, and the regex matches oo a.bar. (This is a very common mistake.)

Opportina answered 11/4, 2014 at 6:19 Comment(2)
I think you are right, and that Ruby 2.0 is wrong rather than Ruby 1.9.3.Bethannbethanne
Genius explanation on why this fails too!Oeildeboeuf

© 2022 - 2024 — McMap. All rights reserved.