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

时间:2010-01-27 16:49:35

标签: python regex

我对正则表达式还是比较新的。我正在尝试找到与特定模式匹配的最短文本字符串,但如果最短模式是较大匹配的子字符串,则会遇到问题。例如:

import re
string = "A|B|A|B|C|D|E|F|G"
my_pattern = 'a.*?b.*?c'

my_regex = re.compile(my_pattern, re.DOTALL|re.IGNORECASE)
matches = my_regex.findall(string)

for match in matches:
    print match

打印:

A|B|A|B|C

但我想要它返回:

A|B|C

有没有办法做到这一点,而不必遍历每个匹配,看它是否包含匹配的子字符串?

9 个答案:

答案 0 :(得分:12)

与此处的大多数其他答案相反,此可以使用带有positive lookahead assertioncapturing group在单个正则表达式中完成:

>>> my_pattern = '(?=(a.*?b.*?c))'
>>> my_regex = re.compile(my_pattern, re.DOTALL|re.IGNORECASE)
>>> matches = my_regex.findall(string)
>>> print min(matches, key=len)
A|B|C

findall()会返回所有可能的匹配项,因此您需要min()才能获得最短的匹配项。

这是如何运作的:

  • 我们不匹配此正则表达式中的任何文本,只是匹配字符串中的位置(正则表达式引擎在匹配尝试期间逐步执行)。
  • 在每个位置,正则表达式引擎向前看,看看你的正则表达式是否会匹配此位置。
  • 如果是这样,捕获组将捕获它。
  • 如果没有,它不会。
  • 在任何一种情况下,正则表达式引擎然后前进一个字符并重复该过程直到字符串结束。
  • 由于前瞻断言不消耗任何字符,因此将找到所有重叠的匹配。

答案 1 :(得分:1)

没有。 Perl返回最长的最左边的匹配,同时服从你的非贪婪量词。你不得不循环,我害怕。

编辑:是的,我意识到我上面说的是Perl,但我相信Python也是如此。

答案 2 :(得分:1)

另一种正则表达式解决方案;它只找到。* a。* b。* c:

的最后一次出现
my_pattern = 'a(?!.*a.*b.*c).*b[^c]*c'

a(?!.*a.*?b.*?c)确保首先“A”后没有'a.*?b.*?c' A | A | B | C或A | B | A | B | C或A | B | C | A | B | C等字符串被删除

b[^c]*c确保'B'后只有一个'C' 结果中的字符串如A | B | C | B | C或A | B | C | C被消除

所以你有最小的匹配'a.*?b.*?c'

答案 3 :(得分:0)

正则表达式引擎从字符串的开头开始搜索,直到找到匹配然后退出。因此,如果它在考虑较小的匹配之前找到匹配,则无法强制它在同一运行中考虑以后的匹配 - 您必须在子字符串上重新运行正则表达式。

设置全局标志并选择最短的匹配字符串将无济于事,因为从您的示例中可以看出 - 较短的匹配可能是另一个匹配的子字符串(或部分包含在其中)。我相信你必须从(1 +前一场比赛的索引)开始后续搜索并继续这样做。

答案 4 :(得分:0)

我不认为这个任务可以通过一个正则表达式完成。我没有证据证明是这种情况,但是有很多事情无法通过正则表达式完成,我预计这个问题就是其中之一。正则表达式局限性的一些很好的例子在this blog post中给出。

答案 5 :(得分:0)

这可能是sexegers的有用应用。正则表达式匹配偏向最长,最左边的选择。使用非贪婪量词(例如.*?中的最长部分,并且反转输入和模式可以绕过最左边匹配的语义。

考虑以下根据需要输出A|B|C的程序:

#! /usr/bin/env python

import re

string = "A|B|A|B|C|D|E|F|G"
my_pattern = 'c.*?b.*?a'

my_regex = re.compile(my_pattern, re.DOTALL|re.IGNORECASE)
matches = my_regex.findall(string[::-1])

for match in matches:
    print match[::-1]

另一种方法是制定更严格的模式。假设您不希望重复已经看过的字符:

my_pattern = 'a[^a]*?b[^ab]*?c'

您的示例是通用的和做作的,但如果我们更好地了解您正在使用的输入,我们可以提供更好,更有帮助的建议。

答案 6 :(得分:0)

您可能能够以不包含较小匹配的方式编写正则表达式。

对于你的正则表达式:

a.*?b.*?c

我想你可以这样写:

a[^ab]*b[^c]*c

要弄清楚这一点很棘手,我没有看到任何更普遍或更明显正确的方法。 (编辑 - 早些时候我提出了一个负面的先行断言,但我没有办法让这种方法发挥作用。)

答案 7 :(得分:0)

一个Python循环来寻找最短的匹配,通过蛮力从左到右测试每个子字符串,选择最短的:

shortest = None
for i in range(len(string)):
    m = my_regex.match(string[i:])
    if m: 
        mstr = m.group()
        if shortest is None or len(mstr) < len(shortest):
            shortest = mstr

print shortest

另一个循环,这次让re.findall完成搜索所有可能匹配的艰苦工作,然后从右到左强力测试每个匹配,寻找更短的子字符串:

# find all matches using findall
matches = my_regex.findall(string)

# for each match, try to match right-hand substrings
shortest = None
for m in matches:
    for i in range(-1,-len(m),-1):
        mstr = m[i:]        
        if my_regex.match(mstr):
            break
    else:
        mstr = m

    if shortest is None or len(mstr) < len(shortest):
        shortest = mstr

print shortest

答案 8 :(得分:0)

不,Python正则表达式引擎中没有。

我对自定义功能的看法:

import re, itertools

# directly from itertools recipes
def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = itertools.tee(iterable)
    for elem in b:
        break
    return itertools.izip(a, b)

def find_matches(rex, text):
    "Find all matches, even overlapping ones"
    matches= list(rex.finditer(text))

    # first produce typical matches
    for match in matches:
        yield match.group(0)

    # next, run it for any patterns included in matches
    for match1, match2 in pairwise(matches):
        subtext= text[match1.start()+1:match2.end()+1]
        for result in find_matches(rex, subtext):
            yield result

    # also test the last match, if there was at least one
    if matches:
        subtext= text[matches[-1].start()+1:matches[-1].end()+1]
        # perhaps the previous "matches[-1].end()+1" can be omitted
        for result in find_matches(rex, subtext):
            yield result

def shortest_match(rex, text):
    "Find the shortest match"
    return min(find_matches(rex, text), key=len)

if __name__ == "__main__":
    pattern= re.compile('a.*?b.*?c', re.I)
    searched_text= "A|B|A|B|C|D|E|F|G"
    print (shortest_match(pattern, searched_text))