5

假设我们的模式是大写字母的正则表达式(但我们可以有一个比搜索大写字母更复杂的模式)

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

(使用红宝石)

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,尽管我们可以保持原样)所以将其写为正则表达式. 如果我们认为遇到我们的模式时接收到“1”,而没有接收到模式时接收到“0”,我们可以绘制一个简单的 DFA 图(其中 n=1,我们最多需要一个模式):

DFA_to_be_regexed

具体来说,我想知道这如何成为一个正则表达式。一般来说,我希望找到如何用正则表达式找到“至多”,因为我的正则表达式技能只用“至少”就会受到阻碍。


绊倒危险 - 精神上不太正确的解决方案

请注意,这个问题不是这篇文章的重复,因为使用那里公认的方法会给出:

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

(感谢橡皮鸭

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

4

3 回答 3

2

为什么您的尝试不起作用

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

  1. X作为匹配的一部分的原因.*是贪婪并且尽可能多地消耗 - 因此,只留下所需的两个大写字母与尾随位匹配。这可以用非贪婪的量词来解决。
  2. 你没有得到第二场比赛的原因是双重的。首先,您需要两个尾随大写字母,而是字符串的结尾。其次,匹配不能重叠。第一个匹配包括至少两个尾随大写字母,但第二个需要在开始时再次匹配这些字母,这是不可能的。
  3. 还有更多隐藏的问题:尝试使用四个连续大写字母的输入 - 它可以给你一个空匹配(假设你使用非贪婪量词 - 贪婪的量词更糟糕)。

用当前的方法修复所有这些是很困难的(我尝试过但失败了——如果你想查看我的尝试,请查看这篇文章的编辑历史,直到我决定完全放弃这种方法)。所以让我们试试别的吧!

寻找另一种解决方案

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

(非大写) 1 个(非大写) 1 个(非大写) ... .

这是 Jeffrey Friedl 的展开循环的理想选择。看起来像

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

现在边缘情况呢?我们可以这样表述它们:

  1. 我们希望在匹配的开头允许单个大写字母,前提是它位于字符串的开头。
  2. 我们希望在匹配结束时允许单个大写字母,前提是它位于字符串的末尾。

要将这些添加到我们的模式中,我们只需将大写字母与适当的锚组合在一起,并将它们一起标记为可选:

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

现在它真的起作用了。更妙的是,我们不再需要捕捉!

推广解决方案

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

请参阅演示n = 2

于 2013-06-19T10:27:23.753 回答
2

tl;博士

要匹配最多包含s 的单词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正则表达式返回

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

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


解决方案

无论哪种方式,这都是解决方案。

首先,为了只匹配模式(而不是整个单词,与您最初的示例一致),这里是最多- - s 的NPATTERN表达式

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

例如,最多 1 个大写的正则表达式是

/(?<![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查看所有这些示例。

于 2013-06-19T10:29:42.113 回答
1

要使用正则表达式查找“​​至多”,请使用后缀{1,n}(可能前面有一个否定的lookbehind,然后是一个肯定的lookahead),所以看起来你想要的是:

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

会更好。

于 2013-06-19T10:16:48.163 回答