正则表达式与所有组件可选,如何避免空匹配

regex with all components optionals, how to avoid empty matches

本文关键字:何避免 组件 正则表达式      更新时间:2023-10-16

我必须处理一个逗号分隔的字符串,它包含值的三元组,并将它们转换为运行时类型,输入看起来像:

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

所以每个子字符串应该这样转换:

"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

我面临的问题是所有组件都是可选的(但它们保持顺序),所以以下字符串也是有效的Vector3, ColorTime值:

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

我已经尝试了什么?

所有组件可选

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

A, BC为每种情况替换为正确的字母,表达式工作得几乎很好,但它给出了预期结果的两倍(一个匹配字符串,另一个匹配在第一个匹配之后的空字符串),例如:

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

这并不意外…毕竟,当所有组件都为空时,一个空字符串匹配表达式;所以为了解决这个问题,我尝试了以下方法:

1到3量词

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

但是现在,表达式匹配的字符串顺序错误,甚至是重复的组件!:

"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)

对于我的最后一次尝试,我尝试了第一个表达式的这个变体:

匹配从开始^到结束$

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

它比第一个版本工作得更好,但它仍然匹配空字符串,并且我应该首先对输入进行标记,然后将每个标记传递给表达式,以确保测试字符串可以匹配开始(^)和结束($)操作符。

编辑:展望尝试(感谢Casimir et Hippolyte)

在阅读和(尝试)理解regex向前看的概念,并在Casimir et Hippolyte答案的帮助下,我尝试了建议的表达式:

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

对应以下测试字符串:

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

结果是惊人的!它能够完美地检测完整的有效匹配(其他表达式在"1s1m1h""1h1h1h"上给了我3个匹配,这些匹配根本不打算匹配)。不幸的是,每次发现无效匹配时,它都会捕获空匹配,因此在"1s1m1h", "1h1h1h", "adfank""12322134445688"之前检测到"",因此我修改了Lookahead条件以获得下面的表达式:

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

它去除任何不匹配(?:d+[ABC]){1,3})的字符串中的空匹配,因此"adfank""12322134445688"之前的空匹配将消失,但"1s1m1h", "1h1h1h"之前的空匹配仍然被检测到。


所以问题是:是否有任何正则表达式匹配三个三元组值在给定的顺序,其中所有组件是可选的,但应至少由一个组件组成,不匹配空字符串?

我使用的正则表达式工具是c++ 11。

是的,您可以在开始处添加前瞻性,以确保至少有一个字符:

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

如果您需要在更大的字符串中找到这种子字符串(因此之前不需要标记),您可以删除锚并在forward中使用更显式的子模式:

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

在这种情况下,为了避免误报(因为您正在寻找可能是其他内容一部分的非常小的字符串),您可以向模式添加单词边界:

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

注意:在逗号分隔的字符串中:(?=d+[ABC])可以用(?=[^,])代替

我想这可能会奏效。

我键入字符串的开头以匹配^或逗号分隔符,以修复每个匹配的开始:(?:^|,) .

的例子:

#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';
}
输出:

1x2y3z
80r160g255b
48h30m50s
1x3z
255b

这是你想要的吗?

编辑:

如果你真的想让空表达式不匹配,那么据我所知,你必须像这样输入每一个排列:

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 + ")");