是否有可能有重叠的正则表达式匹配?

Posted

技术标签:

【中文标题】是否有可能有重叠的正则表达式匹配?【英文标题】:Is it possible to have overlapping regex matches? 【发布时间】:2021-08-30 00:28:27 【问题描述】:

以这个数据为例:

ID: JK546|Guitar: 0|Piano: 1|Violin: 0|Expiry: Aug14,2021

我想知道是否可以创建一个正则表达式来返回这组匹配项

ID: JK546|Guitar: 0|Expiry: Aug14,2021
ID: JK546|Piano: 1|Expiry: Aug14,2021
ID: JK546|Violin: 0|Expiry: Aug14,2021

我确实尝试在下面创建一个:

ID: (?<id>\w+).*\|(?<instrument>\w+):\s(?<count>\d).*Expiry:\s(?<expiry>[\w\d]+)

但它只返回带有小提琴乐器的那个。非常感谢您对此的见解。

【问题讨论】:

查看这个问题:***.com/questions/8020848/… ID: JK546||Violin: 0|Expiry: Aug14,20201 - | 之前的两个Violin,是不是搞错了? 有效期为 18180 年后。经久耐用! ;) @AKSingh 已经修复了它 @Wyck lol 看起来我还是个半生不熟的调试器 【参考方案1】:

我不会使用正则表达式。特别是由于字符串ID: JK546|Guitar: 0|Expiry: Aug14,2021 没有出现在字符串ID: JK546|Guitar: 0|Piano: 1|Violin: 0|Expiry: Aug14,2021 中,所以它并不是严格意义上的匹配,而是更多的替换。但是没有什么好方法可以从所有比赛中获得所有替补。

所以,我只需在| 上拆分输入字符串。

然后你想组成一个由第一个字段、一个中间字段和最后一个字段组成的结果字符串。对于存在的每个中间字段,您将获得一个结果。如果它拆分为 N 个字段,您将获得 N-2 个结果。例如:如果它分成 5 个字段,那么您将得到 3 个结果,每个“中间”字段一个。

string input = "ID: JK546|Guitar: 0|Piano: 1|Violin: 0|Expiry: Aug14,2021";
string[] fields = input.Split('|');
for( int i = 1; i < fields.Length - 1; ++i) 
    string result = string.Join("|", fields.First(), fields[i], fields.Last());
    Console.WriteLine(result);

输出:

编号:JK546|吉他:0|有效期:2021年8月14日 编号:JK546|钢琴:1|有效期:2021年8月14日 编号:JK546|小提琴:0|有效期:2021年8月14日

【讨论】:

【参考方案2】:

一个正则表达式返回多个匹配多次调用? 我想知道这是否可能。

我不熟悉如何在 C# 中进行正则表达式处理, 但是这个sed 命令会做你想做的事。 也许您可以了解它的工作原理并根据您的需要进行调整:

sed -n ':loop; h; s/^\([^|]*|[^|]*\).*\(|.*\)$/\1\2/p; g; s/^\([^|]*\)|[^|]*\(|.*\)$/\1\2/; t loop'

为简单起见,我们假设输入字符串是“A|B|C|D|E”。

它的作用:

-n 是告诉sed 不要自动打印任何内容的选项 (但只有在被告知时才使用p 命令打印)。 :loop 实际上是“goto”的标签。 所以使用while 循环结构。 h 将模式空间保存到保持空间中。 换句话说,复制你的字符串。 s/^\([^|]*|[^|]*\).*\(|.*\)$/\1\2/p 捕获前两个段 最后一个,并打印结果。 所以“A|B|C|D|E”变成了“A|B|E”(即你想要的第一个输出)。 g 将保存的字符串从保持空间恢复到模式空间。 换句话说,检索您保存的字符串的副本。 s/^\([^|]*\)|[^|]*\(|.*\)$/\1\2/ 捕获第一段, 跳过第二个,然后捕获其余部分。 所以“A|B|C|D|E”变成了“A|C|D|E”。 t loop 是“goto”命令。 它说要回到循环的开头 如果最近的替换成功。 换句话说,这是循环的结束, 以及循环条件的说明。

循环的第二次迭代会将“A|C|D|E”更改为“A|C|E” 并打印出来。 然后将“A|C|D|E”更改为“A|D|E”并迭代。 循环的第三次迭代会将“A|D|E”更改为“A|D|E”并打印出来。 (显然没有变化,因为正则表达式中间的.* 匹配“A|D”和“|E”之间的零长度字符串。) 最后的替换将“A|D|E”更改为“A|E”, 然后就什么也找不到了。

【讨论】:

【参考方案3】:

您可以利用 .NET Groups.Captures 属性来获取吉他、钢琴和小提琴的值。

(ID: \w+\|)(\w+: \d+\|)+(Expiry: \w+,\d+)

模式匹配:

(ID: \w+\|) 捕获 group 1 匹配 ID: 1+ word chars 和 | (\w+: \d+\|)+ 捕获组 2 重复 1+ 次匹配 1+ 单词字符 : 1+ 数字 | (Expiry: \w+,\d+) 捕获 第 3 组 匹配 Expiry: 1+ 个单词字符 , 和 1+ 个数字

查看.NET regex demo | C# demo

例如

var str = "ID: JK546|Guitar: 0|Piano: 1|Violin: 0|Expiry: Aug14,2021";
string pattern = @"(ID: \w+\|)(\w+: \d+\|)+(Expiry: \w+,\d+)";
Match m = Regex.Match(str, pattern);

foreach(Capture c in  m.Groups[2].Captures) 
    Console.WriteLine(m.Groups[1].Value + c.Value + m.Groups[3].Value);

输出

ID: JK546|Guitar: 0|Expiry: Aug14,2021
ID: JK546|Piano: 1|Expiry: Aug14,2021
ID: JK546|Violin: 0|Expiry: Aug14,2021

【讨论】:

【参考方案4】:

往后看应该可以:

string foo = @"ID: JK546 | Guitar: 0 | Piano: 1 | Violin: 0 | Expiry: Aug14,2021";

// First look at "Guitar: 0", "Piano: 1" and "Violin: 0". Then look behind "(?<= )" and search for the ID. Then look ahead "(?= )" and search for Expiry.

string pattern = @"(\w+: \d)(?<=(ID: [A-Z0-9]+).*?)(?=.*?(Expiry: \S+))";

foreach (var match in Regex.Matches(foo, pattern))

    ....                

幸运的是,c# 是少数可以处理可变长度查找的语言之一。

【讨论】:

以上是关于是否有可能有重叠的正则表达式匹配?的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式中的重叠匹配

在 C# 中获取重叠的正则表达式匹配

如何将重叠字符串与正则表达式匹配?

Vim 多行正则表达式给出重叠匹配

如何使用正则表达式查找重叠匹配?

如何使用正则表达式找到最短的重叠匹配?