折叠并捕获单个正则表达式中的重复模式

CSᵠ*_*CSᵠ 32 regex language-agnostic

我不断遇到需要从字符串中捕获大量令牌的情况,经过无数次的尝试后,我找不到简化过程的方法.

所以我们说文本是:

启动:测试 - 测试 - LOREM-存有-SIR-doloret - 等 - 等 - 的东西:结束

这个例子里面有8个项目,但是说它可能有3到10个项目.

我理想上喜欢这样的东西:
start:(?:(\w+)-?){3,10}:end漂亮而干净但是它只能抓住最后一场比赛.看这里

我通常在简单的情况下使用这样的东西:

start:(\w+)-(\w+)-(\w+)-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?:end
Run Code Online (Sandbox Code Playgroud)

由于最大10限制,3组强制要求和另外7组可选,但这看起来并不"好",如果最大限制为100且匹配更复杂,编写和跟踪将是一件痛苦的事.演示

到目前为止我能做的最好:

start:(\w+)-((?1))-((?1))-?((?1))?-?((?1))?-?((?1))?-?((?1))?-?((?1))?:end
Run Code Online (Sandbox Code Playgroud)

特别是如果比赛很复杂但仍然很长.演示

有人设法让它作为一个没有编程的1正则表达式解决方案工作

我最感兴趣的是如何在PCRE中完成,但其他口味也可以.

更新:

目的是match 0通过RegEx单独验证匹配并捕获单个令牌,而不受任何OS /软件/编程语言限制

更新2(赏金):

在@nhahtdh的帮助下,我使用以下方法访问下面的RegExp \G:

(?:start:(?=(?:[\w]+(?:-|(?=:end))){3,10}:end)|(?!^)\G-)([\w]+)
Run Code Online (Sandbox Code Playgroud)

演示甚至更短,但无需重复代码即可进行描述

我也对ECMA的味道感兴趣,因为它不支持\G想知道是否有另一种方式,特别是不使用/g修饰符.

nha*_*tdh 36

先读一下!

这篇文章是为了展示可能性,而不是赞同"一切正则表达式"的问题方法.在达到当前解决方案之前,作者已经编写了3-4种变体,每种变体都有一些难以检测的细微错误.

对于您的具体示例,还有其他更易于维护的更好解决方案,例如匹配和拆分分隔符.

这篇文章涉及您的具体示例.我真的怀疑完全概括是可能的,但背后的想法可以重复使用类似的情况.

摘要

  • .NET支持使用CaptureCollection类捕获重复模式.
  • 对于支持\G和后视的语言,我们可以构建一个与全局匹配函数一起使用的正则表达式.写它完全正确并且容易编写一个巧妙的bug正则表达式并不容易.
  • 对于没有语言\G和查找背后的支持:有可能效仿\G^,由单一的比赛后咬食输入字符串.(此答案未涵盖).

此解决方案假设正则表达式引擎支持\G匹配边界,前瞻(?=pattern)和后瞻(?<=pattern).Java,Perl,PCRE,.NET,Ruby正则表达式支持上述所有这些高级功能.

但是,您可以使用.NET中的正则表达式.由于.NET支持捕获与通过CaptureCollection类重复的捕获组匹配的所有实例.

对于您的情况,它可以在一个正则表达式中完成,使用\G匹配边界,并预测以约束重复次数:

(?:start:(?=\w+(?:-\w+){2,9}:end)|(?<=-)\G)(\w+)(?:-|:end)
Run Code Online (Sandbox Code Playgroud)

演示.\w+-然后重复构造\w+:end.

(?:start:(?=\w+(?:-\w+){2,9}:end)|(?!^)\G-)(\w+)
Run Code Online (Sandbox Code Playgroud)

演示.构造是\w+第一个项目,然后-\w+重复.(感谢kaᵠ的建议).这种结构更容易推断其正确性,因为交替较少.

\G 匹配边界在您需要进行标记化时尤其有用,您需要确保引擎不会向前跳过并匹配应该无效的内容.

说明

让我们打破正则表达式:

(?:
  start:(?=\w+(?:-\w+){2,9}:end)
    |
  (?<=-)\G
)
(\w+)
(?:-|:end)
Run Code Online (Sandbox Code Playgroud)

最容易识别的部分是(\w+)在前一行之前,这是你要捕获的单词.

最后一行也很容易识别:要匹配的单词后面可能是-:end.

我允许正则表达式自由地开始匹配字符串中的任何位置.换句话说,start:...:end可以出现在字符串中的任何地方,并且可以出现任意次数; 正则表达式将简单地匹配所有单词.您只需处理返回的数组以分离匹配的标记实际来自的位置.

至于解释,正则表达式的开头检查是否存在字符串start:,并且以下前瞻检查单词的数量是否在指定的限制范围内并以它结束:end.或者,或者我们检查上一场比赛前的角色是否为a -,并从上一场比赛继续.

对于其他建筑:

(?:
  start:(?=\w+(?:-\w+){2,9}:end)
    |
  (?!^)\G-
)
(\w+)
Run Code Online (Sandbox Code Playgroud)

一切都差不多,除了我们start:\w+在匹配表格的重复之前先匹配-\w+.与第一个结构相反,我们start:\w+-首先匹配,重复实例\w+-(或\w+:end最后一次重复).

使这个正则表达式适用于字符串中间的匹配是非常棘手的:

  • 我们需要检查之间的单词数start::end(为原正则表达式的要求的一部分).

  • \G匹配字符串的开头也!(?!^)需要防止这种行为.如果不解决这个问题,正则表达式可能会在没有匹配时产生匹配start:.

    对于第一种结构,后视(?<=-)已经阻止了这种情况((?!^)暗示(?<=-)).

  • 对于第一个构造(?:start:(?=\w+(?:-\w+){2,9}:end)|(?<=-)\G)(\w+)(?:-|:end),我们需要确保我们不匹配任何有趣的事情:end.后视是为了这个目的:它防止:end匹配后的任何垃圾.

    第二个结构不会遇到这个问题,因为我们会卡死在:(中:end)后,我们之间不匹配的所有令牌.

验证版本

如果要验证输入字符串是否遵循格式(前后没有额外的东西),提取数据,则可以添加锚点:

(?:^start:(?=\w+(?:-\w+){2,9}:end$)|(?!^)\G-)(\w+)
(?:^start:(?=\w+(?:-\w+){2,9}:end$)|(?!^)\G)(\w+)(?:-|:end)
Run Code Online (Sandbox Code Playgroud)

(也不需要后视,但我们仍然需要(?!^)阻止\G匹配字符串的开头).

施工

对于想要捕获所有重复实例的所有问题,我认为不存在修改正则表达式的一般方法.转换的"硬"(或不可能?)情况的一个例子是当重复必须回溯一个或多个循环以满足某个匹配条件时.

当原始正则表达式描述整个输入字符串(验证类型)时,与尝试从字符串中间匹配的正则表达式(匹配类型)相比,它通常更容易转换.但是,您始终可以与原始正则表达式匹配,并将匹配类型问题转换回验证类型问题.

我们通过以下步骤构建这样的正则表达式:

  • 在重复之前写一个覆盖部分的正则表达式(例如start:).我们称这个前缀为正则表达式.
  • 匹配并捕获第一个实例.(例如(\w+))
    (此时,第一个实例和分隔符应该匹配)
  • 添加\G作为替换.通常还需要阻止它匹配字符串的开头.
  • 添加分隔符(如果有).(例如-)
    (在此步骤之后,其余的令牌也应该匹配,除了最后一个)
  • 重复后添加覆盖零件的零件(如有必要)(例如:end).让我们在重复后缀正则表达式之后调用该部分(无论我们将它添加到构造中都无关紧要).
  • 现在困难的部分.你需要检查:
    • 除了前缀正则表达式之外,没有其他方法可以开始匹配.注意\G分支.
    • 有没有办法启动任何比赛之后后缀正则表达式已匹配.记下\G分支如何开始匹配.
    • 对于第一种结构,如果在替换中混合后缀正则表达式(例如:end)和分隔符(例如-),请确保最终不允许将后缀正则表达式作为分隔符.


Ja͢*_*͢ck 6

虽然理论上可以编写单个表达式,但首先匹配外部边界然后在内部部分执行拆分更加实用.

在ECMAScript中,我会这样写:

'start:test-test-lorem-ipsum-sir-doloret-etc-etc-something:end'
    .match(/^start:([\w-]+):end$/)[1] // match the inner part
    .split('-') // split inner part (this could be a split regex as well)
Run Code Online (Sandbox Code Playgroud)

在PHP中:

$txt = 'start:test-test-lorem-ipsum-sir-doloret-etc-etc-something:end';
if (preg_match('/^start:([\w-]+):end$/', $txt, $matches)) {
    print_r(explode('-', $matches[1]));
}
Run Code Online (Sandbox Code Playgroud)

  • +1两步解决方案通常更具可读性和可维护性. (3认同)