Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

regex with all components optionals, how to avoid empty matches

Tags:

c++

regex

c++11

I have to process a comma separated string which contains triplets of values and translate them to runtime types,the input looks like:

"1x2y3z,80r160g255b,48h30m50s,1x3z,255b,1h,..."

So each substring should be transformed this way:

"1x2y3z"      should become Vector3 with x = 1,  y = 2,   z = 3
"80r160g255b" should become Color   with r = 80, g = 160, b = 255
"48h30m50s"   should become Time    with h = 48, m = 30,  s = 50

The problem I'm facing is that all the components are optional (but they preserve order) so the following strings are also valid Vector3, Color and Time values:

"1x3z" Vector3 x = 1, y = 0, z = 3
"255b" Color   r = 0, g = 0, b = 255
"1h"   Time    h = 1, m = 0, s = 0

What I have tried so far?

All components optional

((?:\d+A)?(?:\d+B)?(?:\d+C)?)

The A, B and C are replaced with the correct letter for each case, the expression works almost well but it gives twice the expected results (one match for the string and another match for an empty string just after the first match), for example:

"1h1m1s" two matches [1]: "1h1m1s" [2]: ""
"11x50z" two matches [1]: "11x50z" [2]: ""
"11111h" two matches [1]: "11111h" [2]: ""

This isn't unexpected... after all an empty string matches the expression when ALL of the components are empty; so in order to fix this issue I've tried the following:

1 to 3 quantifier

((?:\d+[ABC]){1,3})

But now, the expression matches strings with wrong ordering or even repeated components!:

"1s1m1h" one match, should not match at all! (wrong order)
"11z50z" one match, should not match at all! (repeated components)
"1r1r1b" one match, should not match at all! (repeated components)

As for my last attempt, I've tried this variant of my first expression:

Match from begin ^ to the end $

^((?:\d+A)?(?:\d+B)?(?:\d+C)?)$

And it works better than the first version but it still matches the empty string plus I should first tokenize the input and then pass each token to the expression in order to assure that the test string could match the begin (^) and end ($) operators.

EDIT: Lookahead attempt (thanks to Casimir et Hippolyte)

After reading and (try to) understanding the regex lookahead concept and with the help of Casimir et Hippolyte answer I've tried the suggested expression:

\b(?=[^,])(?=.)((?:\d+A)?(?:\d+B)?(?:\d+C)?)\b

Against the following test string:

"48h30m50s,1h,1h1m1s,11111h,1s1m1h,1h1h1h,1s,1m,1443s,adfank,12322134445688,48h"

And the results were amazing! it is able to detect complete valid matches flawlessly (other expressions gave me 3 matches on "1s1m1h" or "1h1h1h" which weren't intended to be matched at all). Unfortunately it captures emtpy matches everytime a unvalid match is found so a "" is detected just before "1s1m1h", "1h1h1h", "adfank" and "12322134445688", so I modified the Lookahead condition to get the expression below:

\b(?=(?:\d+[ABC]){1,3})(?=.)((?:\d+A)?(?:\d+B)?(?:\d+C)?)\b

It gets rid of the empty matches in any string which doesn't match (?:\d+[ABC]){1,3}) so the empty matches just before "adfank" and "12322134445688" are gone but the ones just before "1s1m1h", "1h1h1h" are stil detected.


So the question is: Is there any regular expression which matches three triplet values in a given order where all component is optional but should be composed of at least one component and doesn't match empty strings?

The regex tool I'm using is the C++11 one.

like image 911
PaperBirdMaster Avatar asked May 14 '15 10:05

PaperBirdMaster


People also ask

What is regex match all except a specific word?

Regex Match All Except a Specific Word, Character, or Pattern December 30, 2020 by Benjamin Regex is great for finding specific patterns, but can also be useful to match everything except an unwanted pattern. A regular expression that matches everything except a specific pattern or word makes use of a negative lookahead.

How do you match a regular expression that does not contain ignorethis?

For example, here’s an expression that will match any input that does not contain the text “ignoreThis”. /^(?!.*ignoreThis).*/ Note that you can replace the text ignoreThis above with just about any regular expression, including:

How to match everything except a specific character in a string?

To match everything except a specific character, simply insert the character inside the negative lookahead. This expression will ignore any string containing an a: /^(?!.*a).*/

What are regular expressions (regex)?

Regular expressions (regex or regexp) are extremely useful in extracting information from any textby searching for one or more matches of a specific search pattern (i.e. a specific sequence of ASCII or unicode characters).


2 Answers

Yes, you can add a lookahead at the begining to ensure there is at least one character:

^(?=.)((?:\d+A)?(?:\d+B)?(?:\d+C)?)$

If you need to find this kind of substring in a larger string (so without to tokenize before), you can remove the anchors and use a more explicit subpattern in a lookahead:

(?=\d+[ABC])((?:\d+A)?(?:\d+B)?(?:\d+C)?)

In this case, to avoid false positive (since you are looking for very small strings that can be a part of something else), you can add word-boundaries to the pattern:

\b(?=\d+[ABC])((?:\d+A)?(?:\d+B)?(?:\d+C)?)\b

Note: in a comma delimited string: (?=\d+[ABC]) can be replaced by (?=[^,])

like image 58
Casimir et Hippolyte Avatar answered Sep 19 '22 02:09

Casimir et Hippolyte


I think this might do the trick.

I am keying on either the beginning of the string to match ^ or the comma separator , for fix the start of each match: (?:^|,).

Example:

#include <regex>
#include <iostream>

const std::regex r(R"~((?:^|,)((?:\d+[xrh])?(?:\d+[ygm])?(?:\d+[zbs])?))~");

int main()
{
    std::string test = "1x2y3z,80r160g255b,48h30m50s,1x3z,255b";

    std::sregex_iterator iter(test.begin(), test.end(), r);
    std::sregex_iterator end_iter;

    for(; iter != end_iter; ++iter)
        std::cout << iter->str(1) << '\n';
}

Output:

1x2y3z
80r160g255b
48h30m50s
1x3z
255b

Is that what you are after?

EDIT:

If you really want to go to town and make empty expressions unmatched then as far as I can tell you have to put in every permutation like this:

const std::string A = "(?:\\d+[xrh])";
const std::string B = "(?:\\d+[ygm])";
const std::string C = "(?:\\d+[zbs])";

const std::regex r("(?:^|,)(" + A + B + C + "|" + A + B + "|" + A + C + "|" + B + C + "|" + A + "|" + B + "|" + C + ")");
like image 38
Galik Avatar answered Sep 20 '22 02:09

Galik