【问题标题】:Python regex module not finding all matches even with overlapping = True即使重叠 = True,Python 正则表达式模块也找不到所有匹配项
【发布时间】:2021-01-21 18:31:44
【问题描述】:

我正在使用具有重叠匹配支持的 PyPy regex module

我有以下代码,其中有一个字符串 A,我正在寻找使用正则表达式在正则表达式中定义的 DNA 模式。我想找到与我的 RE 匹配的所有匹配项,包括重叠的匹配项。正则表达式缺少其中一个匹配项,我不知道如何修复它。

import regex as re
A = "GGGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG"
GQ_list = re.findall(r"[G]{3,6}[ACTG]{1,33}[G]{3,6}[ACTG]{1,33}[G]{3,6}[ACTG]{1,33}[G]{3,6}", A, overlapped=True)

GQ_list 返回:

['GGGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG',
 'GGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG',
 'GGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG',
 'GGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG',
 'GGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG',
 'GGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG']

这在我的字符串A 中缺少"GGGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGG" ,并且与正则表达式模式匹配。这里有什么问题?我应该进行哪些更改才能获得所有可能的匹配项,包括重叠的匹配项?

【问题讨论】:

  • 我在文档docs.python.org/3/library/re.html#re.findall 中没有看到overlapped 参数
  • 这是正则表达式而不是普通的 python re。普通 python re 没有重叠 = True 选项。
  • 获得其文档的链接?
  • 它在标题中提到,但也将其添加到代码中。

标签: python python-3.x regex


【解决方案1】:

tdlr:由于在同一个起始字符串索引处可能有多个正则表达式匹配,re.findall 或其他正则表达式方法将只能在每个起始索引处找到 1 个匹配项。你必须分解搜索才能找到它们...


您遇到的问题是正则表达式 findall 确实从每个索引中找到所有组合;它依次从每个索引中找到 一个 匹配项——通常是最长的匹配项。找到重叠匹配的技术仍然会错过单个字符串索引中可能出现的多个匹配。你需要修改你的方法。

如果你检查你的正则表达式:

([G]{3,6}[ACTG]{1,33}[G]{3,6}[ACTG]{1,33}[G]{3,6}[ACTG]{1,33}[G]{3,6})

您会注意到,匹配的序列必须以至少 3 个G 开头并以相同的序列结尾。 GGG[in_between_part]GGG 之间的序列短至 9 个字符,最长 84 个字符(并且可能包含相同的 'GGG' 的开始/结束序列)。

我们可以使用该信息来查找符合该描述的所有可能的字符串序列。然后我们使用您的正则表达式过滤识别的序列确实是我们想要的。

首先找到每个可能的'GGG' 的字符串索引,这是子字符串开始或结束的位置(根据定义):

s = "GGGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG"

offset=0
indicies=[]
while (s_idx:=s[offset:].find('GGG'))>-1:
    indicies.append(s_idx+offset)
    offset+=s_idx+1

>>> indicies
[0, 1, 8, 9, 10, 11, 21, 47, 66]
# these are the indicies of 'GGG' that might be that start or end
# of a sub string of interest.

现在我们有了字符串中每个'GGG' 的起始索引。我们现在可以使用正则表达式和 bisect 模块来过滤正则表达式字符串中所有可能的匹配项。

我们正在使用bisect 来查找候选结束锚点的结束位置,该位置与开始锚点相同。 bisect 模块允许我们构造一个形成子字符串的切片 a) 以'GGG' 开头(来自indicies 列表)和b)以'GGG' 结尾和c)在开始和结束之间有一个长度9 到 84 个字符的锚点。然后我们使用re.fullmatch 来确保候选子字符串完全匹配您的模式:

import re 
import bisect 

matches=[]  
min_len=3+9
max_len=3+84
pat=re.compile(r'([G]{3,6}[ACTG]{1,33}[G]{3,6}[ACTG]{1,33}[G]{3,6}[ACTG]{1,33}[G]{3,6})')
for x in indicies:
    min_offest=bisect.bisect(indicies,x+min_len)
    max_offset=bisect.bisect(indicies,x+max_len)
    for idx in indicies[min_offest:]+indicies[max_offset:]:
        candidate=s[x:idx+3]
        if pat.fullmatch(candidate):
            matches.append(candidate)

现在我们可以打印找到的所有匹配项,其索引为s,长度为:

>>> for ss in matches: print((s.index(ss), len(ss)),ss)
# This is only a primitive shortcut. If you want the actual
# index, save it when 'candidate' matches the regex

打印所有八个唯一匹配项,包括来自相同起始索引的匹配项:

(0, 50) GGGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGG
(0, 69) GGGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG
(1, 49) GGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGG
(1, 68) GGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG
(8, 61) GGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG
(9, 60) GGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG
(10, 59) GGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG
(11, 58) GGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG

注意:

如 cmets 中所述,regex module确实支持可变宽度的后视。

因此,您可能会想这样做:

m1=regex.findall(r'([G]{3,6}[ACTG]{1,33}[G]{3,6}[ACTG]{1,33}[G]{3,6}[ACTG]{1,33}[G]{3,6})', s, overlapped=True)     
# produces 6 unique matches 
m2=regex.findall(r'(?<=([G]{3,6}[ACTG]{1,33}[G]{3,6}[ACTG]{1,33}[G]{3,6}[ACTG]{1,33}[G]{3,6}))', s, overlapped=True)
# produces 2 matches, but one is a duplicate from m1

虽然此组合找到了 1 个额外的字符串,即您正在寻找的那个,但它并没有找到所有 8 个唯一匹配项。索引 1 处的字符串 GGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGG 丢失。

【讨论】:

    【解决方案2】:

    编辑:改进的答案

    这个问题的原因在于“RegEx positive look behind”的完成方式

    一般情况下,从pos 1匹配一个字符串后:

    patern = r"[G]{3,6}[ACTG]{1,33}[G]{3,6}[ACTG]{1,33}[G]{3,6}[ACTG]{1,33}[G]{3,6}"
    
    GGGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG
    ^
    

    正则表达式前进一位:

    GGGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG
     ^
    

    然后从那里匹配。
    那么,这将不匹配 GGGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGG(因为它与第一场比赛的开始位置相同)

    而且它不会匹配 GGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGG 因为它从与第二场比赛相同的位置开始)

    使用正面向后看,也可以从相同的起始位置(重新)匹配。
    正则表达式:

    (?<=([paterntomatch]))
    
    //  ?<=  indicates positive look behind
    

    警告:下面的示例可能会产生双重结果(发现一次出现两次

    所以...我在 python 中使用递归函数尝试重新匹配 match.substring(0,match.Length-1)

    
    import re
    
    def regexRecursive(patern,subject):
        master_results = []
        results = re.findall(patern, subject)
        for result in results:
            master_results.append(result)
            length = (len(result)-1)
            subresults = regexRecursive(patern,result[0:length])
            for subresult in subresults:
                master_results.append(subresult)
        return master_results
    
    patern = r"(?=([G]{3,6}[ACTG]{1,33}[G]{3,6}[ACTG]{1,33}[G]{3,6}[ACTG]{1,33}[G]{3,6}))"
    
    given = "GGGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG"
    
    expected_yield = ['GGGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG',
     'GGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG',
     'GGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG',
     'GGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG',
     'GGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG',
     'GGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGGTCCACAGCCACGGTTTGGG',
     'GGGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGG',
     'GGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGG']
    
    results = regexRecursive(patern, given)
    
    for result in results:
        if result not in expected_yield:
            print ("\033[91m","Found Unexpected: ",result,"\033[0m")
    
    for expected in expected_yield:
        if expected not in results:
            print ("\033[91m","Missing Expected: ",expected,"\033[0m")
        else:
            print ("\033[92m","Found Expected: ",expected,"\033[0m")
    
    
    
    
    
    

    产生 8 个子串的完整补码

    【讨论】:

    • 我的代码也输出 6 个字符串,但您的代码中也缺少“GGGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGG”。
    • 对不起,我算错了。我还发现“GGGAGAAGGGGGGCCTTCCTGGGTCCCCGAGAGTGCAGACATGCCTGGG”使预期字符串总数达到 8 个
    • 是的。包裹可能有问题。
    • 连续 3 Gs (GGG) 是否应该总是打破 [ACTG]{1,33} 模式?因为 [ACTG]{1,33} 也可以匹配 GGG(不是您最初的问题的一部分,但我正在尝试解决一些问题)
    • @KamyarYazdani:我发布的解决方案应该更快。看看...
    猜你喜欢
    • 1970-01-01
    • 2015-04-12
    • 1970-01-01
    • 2015-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-12
    相关资源
    最近更新 更多