在匹配的正则表达式模式中查找特定区域?

Finding specific regions in a matched regex pattern?

提问人:SJP 提问时间:11/13/2023 最后编辑:VLAZSJP 更新时间:11/15/2023 访问量:155

问:

我有一个正则表达式,它与我的数据中的特定模式匹配,然后我使用字符串操作对其进行处理。这些模式由停顿和句号组成:

((((((.))))))(...(((((..).).)))).

我目前的正则表达式:

(?:\([\(.]+\()(?:[^()]*\.+[^()]*)\)[\).]+\)

找到可能的最长匹配项,然后根据左括号和右括号(匹配)的最小值和点的最大值进行检查。

但是,我发现我遇到了一个问题 - 有时最长的匹配项符合括号最小数量的标准,但点太多,因此不会提取模式,即使模式中有一个拉伸确实符合条件。

我不知道如何为此编码!这是一个嵌套模式问题,还是可以作为常规字符串处理?

这是我的代码中处理此问题的部分。

def function_for_processing(file_name, dots, brackets):

    with open(file_name, 'r') as file:
        for line in file:
            if line.startswith('(') or line.startswith('.') or line.startswith(')'):
                # Operate only on lines with bracket/dot annotation
                matches = re.finditer(r'(?:\([\(.]+\()(?:[^()]*\.+[^()]*)\)[\).]+\)', line)
                for match in matches:
                    loop_dots = re.findall(r'(?<=\()([^()]*\.{2}[^()]*)(?=\))', match.group(0))
                    internal_dots_removed = re.sub(r'(?<=\()([^()]*\.{2}[^()]*)(?=\))', '', match.group(0))

                    loop_count = sum([char == '.' for char in str(loop_dots)])

                    dot_count = internal_dots_removed.count(".")
                    opening_count = internal_dots_removed.count("(")
                    closing_count = internal_dots_removed.count(")")
                    if dot_count <= dots and opening_count == closing_count >= brackets and loop_count >= 3:
#Further processing continues...

举个例子:

如果点 = 20 且方括号 = 19

此数据点:

(((((((((....(((.((..(((((.((((((((.(.(((((((((((((((.((((((..((......)))))))).))))))))))).)))).).))))....)))).)).)))....)))))...))).))))))

其中有 51 个左括号、51 个右括号和 31 个点(不包括内部点,不计入最大值)被传递,但它包含一个想法匹配:

(((((.((((((((.(.(((((((((((((((.((((((..((......)))))))).))))))))))).)))).).))))....)))).)).)))

在相等的内部点的两侧具有最小数量的括号,低于最大点,并以括号开头和结尾。

Python 正则表达式 字符串

评论

0赞 rioV8 11/13/2023
有什么用,这个也匹配[^()]a
0赞 rioV8 11/13/2023
\([\(.]+\(意味着是非法的模式吗?无需逃生:(.)[]\([(.]+\(
0赞 sln 11/14/2023
为什么所有额外的捕获括号。喜欢((((((.))))))(...(((((..).).)))). -> (.)(...(((..).).)).
0赞 sln 11/14/2023
最长的匹配需要测试可能与所需结果不匹配的所有替代路径。
0赞 SJP 11/14/2023
这就是数据的样子,目标是找到具有尽可能少点的对称模式

答:

0赞 Nick 11/14/2023 #1

解决此问题的一种方法是使用正则表达式模块,该模块支持递归匹配和重叠匹配,这两者都对此问题很有用。您可以使用以下正则表达式在输入中查找嵌套字符串:(xxx)

\((?:\.|(?R))*\)

这匹配:

  • \(:一个(
  • (?:\.|(?R))*:一个或整个模式,若干次.
  • \):一个)

regex101 上的正则表达式演示

我们可以找到输入字符串的所有重叠匹配项,然后将 和 的数量与所需的最小值和最大值进行比较,以找到所有匹配的解决方案:(.

import regex

def find_longest_match(text, dots, brackets):
    pattern = regex.compile(r'\((?:\.|(?R))*\)')
    
    # don't count dots in the very middle of the pattern
    internal_dots = len(regex.search(r'(?<=\()\.*(?=\))', text).group())
    dots += internal_dots
    
    # search through matches, starting with shortest
    longest = None
    for match in pattern.findall(text, overlapped=True)[::-1]:
        if match.count('(') < brackets:
            continue
        # found match with minimal number of brackets, does it have too many dots?
        if match.count('.') > dots:
            break
        longest = match
    
    return longest

dots = 20
brackets = 19

texts = [
    '(((((((((....(((.((..(((((.((((((((.(.(((((((((((((((.((((((..((......)))))))).))))))))))).)))).).))))....)))).)).)))....)))))...))).))))))',
    ' .............((((.......(((...)))...(((((((...........)))))))((((.(((.(((((......((((......(((((((.((......))..))))))).....)))).....)))))))).))))))))'
]

for text in texts:
    result = find_longest_match(text, dots, brackets)
    if result is not None:
        print(f'{result.count("("):2d} brackets, {result.count("."):2d} dots : {result}')
    else:
        print('No matching string found')

输出(注意点数包括内部点数,不计入最大值):

37 brackets, 22 dots : (((((.((((((((.(.(((((((((((((((.((((((..((......)))))))).))))))))))).)))).).))))....)))).)).)))
No matching string found

评论

0赞 SJP 11/14/2023
这太好了,我不知道这个。如果将有很多行包含该模式,那么这种方法是否仍然合适?可能有数百万行需要检查最佳匹配。此外,是否可以简单地隔离满足括号和点的最小值和最大值的单个最长匹配项?
0赞 Nick 11/14/2023
@SJP正则表达式永远不会很快。通过找到字符串的中间并向外迭代,您可能会获得更好的结果(嗯......可能会将其作为另一个答案发布)。在查找最长匹配项方面,我会向与问题中的代码相反的方向进行迭代,直到找到符合条件的第一个匹配项,因为这将是最长的匹配项。
0赞 SJP 11/14/2023
如果您可以作为单独的答案发布,那就太好了!感谢您的帮助,非常感谢
0赞 Nick 11/14/2023
@SJP我发布了另一个答案......
0赞 Nick 11/15/2023
@SJP 为了完整起见,我修改了这个答案,只返回最长的匹配项。它现在给出与其他函数相同的结果。
1赞 Nick 11/14/2023 #2

查找匹配项的一种可能更有效的方法是找到满足最小括号要求的最短匹配项,然后在该匹配项仍满足最大点数要求时向外扩展该匹配项:

import re

def find_matches(text, dots, brackets):
    # find shortest match with the required number of brackets
    match = re.search(rf'\((?:\.*\(){{{brackets-1}}}(?:\.*\)){{{brackets-1}}}\)', text)
    if match is None:
        return None
    
    # don't count dots in the very middle of the pattern
    internal_dots = len(re.search(r'(?<=\()\.*(?=\))', text).group())
    dots += internal_dots
    
    # iterate outwards until we reach the end of the string
    start, end = match.span()
    longest = match.group()
    
    if longest.count('.') > dots:
        return None
    
    while start > 0:
        start = text.rindex('(', 0, start)
        end = text.index(')', end+1)
        if start < 0 or end < 0:
            break
        next_longest = text[start:end+1]
        if next_longest.count('.') > dots:
            break
        longest = next_longest
    
    return longest

dots = 20
brackets = 19

texts = [
    '(((((((((....(((.((..(((((.((((((((.(.(((((((((((((((.((((((..((......)))))))).))))))))))).)))).).))))....)))).)).)))....)))))...))).))))))',
    ' .............((((.......(((...)))...(((((((...........)))))))((((.(((.(((((......((((......(((((((.((......))..))))))).....)))).....)))))))).))))))))'
]

for text in texts:
    result = find_matches(text, dots, brackets)
    if result is not None:
        print(f'{result.count("("):2d} brackets, {result.count("."):2d} dots : {result}')
    else:
        print('No matching string found')

输出:

37 brackets, 22 dots : (((((.((((((((.(.(((((((((((((((.((((((..((......)))))))).))))))))))).)))).).))))....)))).)).)))
No matching string found

评论

0赞 SJP 11/14/2023
非常感谢尼克!这太棒了。我还有最后一个问题 - 当每行可能有多个匹配项时,这可以处理吗?我假设我可以将 match 变量更改为 matches,然后以相同的方式遍历每个匹配项。
0赞 Nick 11/14/2023
@SJP是的,如果每行可以有多个匹配项,则需要使用 to 代替,然后按照我在函数中所做的方式迭代每个匹配项。re.finditerre.search
0赞 Nick 11/14/2023
请注意,will 的计算也是 on 而不是 。事实上,您也可以将当前代码更改为该代码,并且会稍微好一些。internal_dotsmatch.group()text
0赞 SJP 11/14/2023
我发现这段代码也没有应用点最大值,例如:.............((((.......(((...)))...(((((((...........)))))))((((.(((.(((((......((((......(((((((.((......))..))))))).....)))).....)))))))).)))))))).这是提取的部分(有 32 个点)(.(((((......((((......(((((((.((......))..))))))).....)))).....))))))此外,无论更新到哪个点,输出都是相同的!你知道为什么会这样吗?我还认为与初始正则表达式找到的最短匹配也不一定少于 20 个点,所以这可能是问题所在。
0赞 Nick 11/14/2023
@SJP抱歉,我忘了检查初始匹配是否通过了测试。我已经更正了代码,请参阅我的编辑。dots