Multiple zero or one, but at least one occurrence in regex

Viewed 64

I want to validate strings that have the form: One underscore _, a group of letters in a, b, c in alphabetical order and another underscore _.

Examples of valid strings are _a_, _b_, _ac_, _abc_.

I can achieve the correct validation for most cases using the regex _a?b?c?_, but that is still matched by __, which I don't want to consider valid. How can I adapt this regex so that among my zero or one characters a?b?c?, at least one of them must be present?

4 Answers

You can add a (?!_) lookahead after the first _:

_(?!_)a?b?c?_

Details:

  • _ - an underscore
  • (?!_) - the next char cannot be a _
  • a? - an optional a
  • b? - an optional b
  • c? - an optional c
  • _ - an underscore.

See the regex demo.

You may use this regex with a positive lookahead:

_(?=[abc])a?b?c?_

RegEx Demo

RegEx Demo:

  • _: Match a _
  • (?=[abc]): Positive lookahead to assert that there is a letter a or b or c
  • a?b?c?: Match optional a followed by b followed by c
  • _: Match a _

PS: Positive lookahead assertions are usually more efficient than negative lookahead (as evident from steps taken on regex101 webste).

Thought I'd chip in an alternative if one uses Python for this with PyPi's regex module which support what is called approximate “fuzzy” matching:

^_(abc){d<=2}_$

The pattern means:

  • ^_ - Match start-line anchor and leading underscore;
  • (abc){d<=2} - Match 'abc' in order and allow for up to just two deletions;
  • _$ - Match trailing undescore and end-line anchor.

import regex as re
l = ["_a_", "_b_", "_ac_",  "_abc_", "", "__", "_ca_"]
print([bool(re.search(r'^_(abc){d<=2}_$', s)) for s in l])

Prints:

[True, True, True, True, False, False, False]

Another option if a lookbehind is supported is looking back after the match, asserting not __

_a?b?c?_(?<!__)

Explanation

  • _ Match literally
  • a?b?c? Match an optional a or b or c
  • _ Match literally
  • (?<!__) Negative lookbehind, assert not __ directly to the left

Regex demo

If supported using SKIP FAIL getting __ out of the way:

__(*SKIP)(*FAIL)|_a?b?c?_

Regex demo

Related