在分析 DNA 核苷酸的长链时,我们需要找出以起始码 ‘AAA’ 开头并以终止码 ‘CCC’ 结尾的序列。由于核苷酸以三联体形式出现,因此在每个找到的序列的起始和终止之间若要找到的序列的核苷酸数必须为三的倍数。
例如,‘AAAGGGCCC’ 是一个有效的序列,而 ‘AAAGCCC’ 则不是。
除此之外,在每个终止码之前,我们希望找到相对于特定读码框最长的序列。
例如,如果 DNA 是 ‘AAAGGGAAACCC’,那么 ‘AAAGGGAAACCC’ 和 ‘AAACCC’ 在技术上都是有效的,但由于它们共享同一个终止码实例,因此我们只想要最长的 DNA 链 ‘AAAGGGAAACCC’。
另外,如果我们的链是 ‘AAAAGGCCCCC’,我们必须返回 ‘AAAAGGCCC’ 和 ‘AAAGGCCCC’,因为它们处于不同的读码框(一个读码框为模 3,另一个为模 1。)
虽然我们认为已经有了用于搜索满足 3 的倍数要求且不会重叠的字符串的代码,但对于如何实现保持相同读码框的第二个条件仍存有疑问。
下面的代码只返回那些不重叠的最长字符串,但并没有区分读码框,因此在上面的例子中,它会捕获 ‘AAAAGGCCC’ 但不会捕获 ‘AAAGGCCCC’:
match = re.finditer(r"AAA\w{3}{%d}BBB$"% (minNucleotide-6, math.ceil((minNucleotide-6)/3))
对于问题如何实现保持相同读码框的第二个条件仍存有疑问。
2、解决方案
方法 1:使用肯定先行断言。
肯定先行断言允许我们在字符串的每个字符处重新应用正则表达式,从而可以找到所有重叠的匹配项。这是因为先行断言不会像普通匹配那样消耗任何字符。
由于仍然需要匹配一些实际文本,因此可以使用捕获组来实现。
由于 re.findall() 返回捕获组的内容而不是完整的正则表达式匹配(它们全部都将是 ‘’),因此可以使用以下代码:
import re
def find_overlapping(sequence):
return re.findall(
"""(?= # Assert that the following regex could be matched here:
( # Start of capturing group number 1.
AAA # Match AAA.
(?: # Start of non-capturing group, matching...
[AGCT]{3} # a DNA triplet
)*? # repeated any number of times, as few as possible.
CCC # Match CCC.
) # End of capturing group number 1.
) # End of lookahead assertion.""",
sequence, re.VERBOSE)
方法 2:使用三个正则表达式来匹配每个读码框。
最简单的模式如下:
‘AAA(\w{3})*CCC’
^^^ 终止码
^ 零个或多个…
^ ^ 一个…的组
^^^^^ 三个字符
^^^ 启动码
如果对三个字符组的数量有更多要求,比如“至少有两个这样的组”,现在可以轻松地将正则表达式中的星号字符替换为你需要的字符。
至于最长匹配和不同的框架,现在还不确定。从技术上讲,星号字符已经是贪婪的,即它将匹配最长的可能字符串,因此应该满足你的要求。
但是,担心此功能和在单个帧中不共享子字符串的要求会产生不良的交互。
认为最清晰的方法是要求正则表达式引擎为你提供所有匹配项,无论长度和帧如何(只要内部部分的长度是 3 的倍数),然后在正则表达式之外解决这种情况。
如果真的想使用正则表达式引擎来实现,有一个方法可以想到——通过运行三个特定的正则表达式,每个帧一个。这些正则表达式如下:
^(?:\w{3})*AAA(\w{3})CCC
^(?:\w{3})\wAAA(\w{3})CCC
^(?:\w{3})\w\wAAA(\w{3})*CCC
如你所见,每个正则表达式首先匹配 3k、3k+1 或 3k+2 个字符——这样,AAA 启动码将在不同的帧中启动。要获取匹配部分,需要检查返回的匹配对象。