Is it possible to prefer later matches using conditional regex?

Viewed 82

Suppose one would like to match an arbitrary number, that is either preceded or followed by a certain string. However, if possible, the pattern should always prefer to match the substring where the number is followed by the string.

For example:

  1. the string 1234 foo should match.
  2. the string foo 1234 should match.
  3. the string 1234 foo 1234 should produce the match 1234 foo.
  4. (most important) the string foo 1234 foo should produce the match 1234 foo and not foo 1234.

To avoid having the same expression twice, only inverted and separated by an or, I've been trying to implement it using a conditional pattern. Using the pattern below works for the cases 1-3, but fails for case 4 as it matches foo 1234.

(\d+)?(?:(?(1) ?)(foo) ??)(?(1)|(?:(\d+)))

Adding a negative lookahead to the conditional like below also doesn't help. The string foo 1234 foo now produces the two matches foo 123 and 4 foo.

(\d+)?(?:(?(1) ?)(foo) ??)(?(1)|(?:(\d+)(?! ?foo)))

Since the regex is greedy it always matches the non-preferred substring in case 4 first. Is there a way to make the regex expression prefer the later match with a conditional expression?

1 Answers

Okay, if you don't want to reuse foo unless it's in a Lookahead, I believe the following pattern would meet all your conditions:

(\d+)?(?(1) )(foo)(?(1)| (\d+)\b(?! foo))

Demo.

Breakdown:

(\d+)?          # An optional capturing group matching one or more digits.
(?(1) )         # If the previous group exists, match a space character.
(foo)           # A second capturing group to capture "foo".
(?              # If...
    (1)         # ..the first group exists, match nothing.
|               # Else...
    [ ]         # Match a space character.
    (\d+)       # A third capturing group matching one or more digits.
    \b          # Assert a word boundary to make sure no more digits following.
    (?! foo)    # A negative Lookahead to make sure " foo" is not following.
)               # End If.

An alternative approach for that last part if the digits don't have to end with a word boundary would be to get rid of \b and add \d* in the negative Lookahead:

(\d+)?(?(1) )(foo)(?(1)| (\d+)(?!\d* foo))

Demo.


Another solution is to wrap the third digit capturing group in an atomic group, to prevent backtracking in the following negative lookahead. This solves an issue for the first solution, such that there doesn't have to be a word boundary between the numerical substring and foo. It also solves an issue with the second solution, such that it allows for the numerical substring to be more complex (like including decimals \d+.?\d*).

(Credits go to the OP, Ferdinand Schlatt)

(\d+)?(?(1) )(foo)(?(1)| (?>(\d+))(?! foo))

Demo

Related