用于查找最多n个连续模式的正则表达式

时间:2013-06-19 09:25:44

标签: ruby regex

让我们说我们的模式是大写字母的正则表达式(但我们可能有比搜索大写更复杂的模式)

要找到至少 n个连续模式(在这种情况下,我们正在寻找的模式只是一个大写字母),我们可以这样做:

(使用Ruby)

somestring = "ABC deFgHij kLmN pQrS XYZ abcdEf"

at_least_2_capitals = somestring.scan(/[A-Z][A-Z]+/)
=> ["ABC", "XYZ"]
at_least_3_capitals = somestring.scan(/[A-Z]{3}[A-Z]*/)
=> ["ABC", "XYZ"]

但是,如何搜索大多数 n个连续模式,例如,最多连续一个大写字母:

matches = somestring.scan(/ ??? /)
=> [" deFgHij kLmN pQrS ", " abcdEf"]

详细策略

我读到我需要否定“至少”正则表达式,将其转换为DFA,否定接受状态,(然后将其转换回NFA,尽管我们可以leave it as it is)以便编写它作为正则表达式。如果我们认为遇到我们的模式为接收'1'并且没有接收模式为接收'0',我们可以绘制一个简单的DFA图(其中n = 1,我们最多想要一个模式):

DFA_to_be_regexed

具体来说,我想知道这是如何成为一个正则表达式。一般来说,我希望找到如何使用正则表达式找到“最多”,因为我的正则表达式技能会因“至少”单独而感到发育不良。


旅行危险 - 精神上不是正确的解决方案

请注意,此问题重复this post,因为使用了可接受的方法:

somestring.scan(/[A-Z]{2}[A-Z]*(.*)[A-Z]{2}[A-Z]*/)
=> [[" deFgHij kLmN pQrS X"]]

这不是DFA所展示的,不仅仅是因为它错过了第二次寻求的匹配 - 更重要的是它包括'X',它不应该,因为'X'后面是另一个资本,而且来自DFA我们看到另一个资本所追随的资本不是接受国。

你可以建议

somestring.split(/[A-Z]{2}[A-Z]*/)
=> ["", " deFgHij kLmN pQrS ", " abcdEf"]

(感谢Rubber Duck

但我仍然想知道如何仅使用正则表达式查找最多n次出现。 (知识!)

3 个答案:

答案 0 :(得分:2)

为什么你的尝试不起作用

您目前的尝试存在一些问题。

  1. X是匹配的一部分的原因是.*贪婪且消耗尽可能多 - 因此,只留下所需的两个大写字母与尾随位匹配。这可以用非贪婪的量词来解决。
  2. 你没有得到第二场比赛的原因是双重的。首先,你需要要求两个尾随大写字母,而是字符串的结尾。其次,比赛不能重叠。第一场比赛包括至少两个尾随大写字母,但第二场比赛需要在开始时再次匹配,这是不可能的。
  3. 还有更多隐藏的问题:尝试输入四个连续的大写字母 - 它可以给你一个空的匹配(前提是你使用非贪婪的量词 - 贪婪的问题甚至更糟)。
  4. 使用当前的方法修复所有这些很难(我尝试过但失败了 - 如果你想看到我的尝试,请检查这篇文章的编辑历史,直到我决定完全废弃这种方法)。所以让我们尝试别的东西!

    寻找另一种解决方案

    我们想要匹配的是什么?忽略边缘情况,匹配从字符串的开头开始或在字符串的结尾处结束,我们想要匹配:

      

    (非上限)1个上限(非上限)1个上限(非上限)......

    这是Jeffrey Friedl的unrolling-the-loop的理想选择。看起来像

    [^A-Z]+(?:[A-Z][^A-Z]+)*
    

    现在边缘情况怎么样?我们可以这样说:

    1. 我们希望在比赛开始时允许一个大写字母,只要它在字符串的开头。
    2. 我们希望在比赛结束时允许一个大写字母,只要它在字符串的末尾。
    3. 要将这些添加到我们的模式中,我们只需将大写字母与适当的锚分组,并将它们标记为可选:

      (?:^[A-Z])?[^A-Z]+(?:[A-Z][^A-Z]+)*(?:[A-Z]$)?
      

      Now it's really working.更好的是,我们不再需要捕捉了!

      推广解决方案

      此解决方案很容易推广到“最多 n 连续大写字母”的情况,方法是将每个[A-Z]更改为[A-Z]{1,n},从而允许最多{{1到目前为止只有一个允许的大写字母。

      See the demo for n = 2.

答案 1 :(得分:2)

TL;博士

要匹配最多包含N PATTERN字词,请使用正则表达式

/\b(?:\w(?:(?<!PATTERN)|(?!(?:PATTERN){N,})))+\b/

例如,要匹配包含最多1个大写字母的单词

/\b(?:\w(?:(?<![A-Z])|(?!(?:[A-Z]){1,})))+\b/

这也适用于多字符模式。


需要澄清

我担心你的例子可能引起混淆。我们加几句话:

somestring = "ABC deFgHij kLmN pQrS XYZ abcdEf mixedCaps mixeDCaps mIxedCaps mIxeDCaps T TT t tt"
                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

现在,重新运行 at-least-2-capitals regex返回

at_least_2_capitals = somestring.scan(/[A-Z][A-Z]+/)
=> ["ABC", "XYZ", "DC", "DC", "TT"]

请注意捕获的完整单词!你确定这是你想要的吗?当然,我问,因为在后面的例子中,你的 at-most-1-capital 正则表达式返回完整的单词,而不仅仅是捕获的大写字母。


解决方案

以下是解决方案。

首先,为了匹配只是模式(而不是整个单词,与初始示例一致),这里是最多的正则表达式 N - PATTERN 取值

/(?<!PATTERN)(?!(?:PATTERN){N+1,})(?:PATTERN)+/

例如, at-most-1-capitals 正则表达式

/(?<![A-Z])(?!(?:[A-Z]){2,})(?:[A-Z])+/

并返回

=> ["F", "H", "L", "N", "Q", "S", "E", "C", "DC", "I", "C", "I", "DC", "T", "TT"]

进一步举例说明,最多2个大写正则表达式返回

=> 

最后,如果你想匹配包含的整个单词最多一定数量的连续模式,那么这是一个完全不同的方法:

/\b(?:\w(?:(?<![A-Z])|(?![A-Z]{1,})))+\b/

返回

["deFgHij", "kLmN", "pQrS", "abcdEf", "mixedCaps", "mIxedCaps", "T", "t", "tt"]

一般表格是

/\b(?:\w(?:(?<!PATTERN)|(?!(?:PATTERN){N,})))+\b/

您可以在http://ideone.com/hImmZr找到所有这些示例。

答案 2 :(得分:1)

使用正则表达式找到“最多”,你使用后缀{1,n}(可能前面带有负面的lookbehind,然后是正向前瞻),所以看起来你想要的是:

irb(main):006:0> somestring.scan(/[A-Z]{1,2}/)
=> ["AB", "C", "F", "H", "L", "N", "Q", "S", "XY", "Z", "E"]

irb(main):007:0> somestring.scan(/(?<![A-Z])[A-Z]{1,2}(?![A-Z])/)
=> ["F", "H", "L", "N", "Q", "S", "E"]

编辑:如果OP仍然想要“最长的字符串不包括两个以上的大写字母”,它可以使用:

irb(main):025:0> somestring.scan(/[^A-Z]+(?:[A-Z]{1,2}[^A-Z]+)*/)                                                                                    
=> [" deFgHij kLmN pQrS ", " abcdEf"]

(但 正则表达式可能在字符串的开头和结尾不匹配)

似乎

irb(main):026:0> somestring.split(/[A-Z]{3,}/)                                                                                                       
=> ["", " deFgHij kLmN pQrS ", " abcdEf"]

会更好。