您正在将一个由 4 个不同“数字”组成的字符串解释为一个数字,因此 base 4 表示法。如果你有一串实际数字,在 0-3 范围内,你可以让 int() 非常快地生成一个整数。
def seq_to_int(seq, _m=str.maketrans('ACGT', '0123')):
return int(seq.translate(_m), 4)
上述函数使用str.translate() 将4 个字符中的每一个替换为一个匹配的数字(我使用静态str.maketrans() function 创建翻译表)。然后将生成的数字字符串解释为以 4 为底的整数。
请注意,这会产生一个整数对象,而不是零和一个字符的二进制字符串:
>>> seq_to_int('TGTGAGAAGCACCATAAAAGGCGTTGTG')
67026852874722286
>>> format(seq_to_int('TGTGAGAAGCACCATAAAAGGCGTTGTG'), '016x')
'00ee20914c029bee'
>>> format(seq_to_int('TGTGAGAAGCACCATAAAAGGCGTTGTG'), '064b')
'0000000011101110001000001001000101001100000000101001101111101110'
这里不需要填充;只要您的输入序列是 32 个字母或更少,生成的整数将适合无符号 8 字节整数表示。在上面的输出示例中,我使用 format() 字符串将该整数值分别格式化为十六进制和二进制字符串,并将这些表示零填充为 64 位数字的正确位数。
为了衡量这是否更快,让我们抽取 100 万个随机生成的测试字符串(每个 28 个字符长):
>>> from random import choice
>>> testvalues = [''.join([choice('ATCG') for _ in range(28)]) for _ in range(10 ** 6)]
在我的 Macbook Pro 上使用 2.9 GHz Intel Core i7,在 Python 3.6.5 上,上述函数可以在不到 3/4 秒的时间内产生 100 万次转换:
>>> from timeit import timeit
>>> timeit('seq_to_int(next(tviter))', 'from __main__ import testvalues, seq_to_int; tviter=iter(testvalues)')
0.7316284350017668
所以每次调用需要 0.73 微秒。
(之前我提倡预计算版本,但经过实验后,我想到了 base-4 的想法)。
为了与迄今为止发布的其他方法进行比较,有些方法也需要进行调整以产生整数,并被包装到函数中:
def seq_to_int_alexhall_a(seq, mapping={'A': b'00', 'C': b'01', 'G': b'10', 'T': b'11'}):
return int(b''.join(map(mapping.__getitem__, seq)), 2)
def seq_to_int_alexhall_b(seq, mapping={'A': b'00', 'C': b'01', 'G': b'10', 'T': b'11'}):
return int(b''.join([mapping[c] for c in seq]), 2)
def seq_to_int_jonathan_may(seq, mapping={'A': 0b00, 'C': 0b01, 'G': 0b10, 'T': 0b11}):
result = 0
for char in seq:
result = result << 2
result = result | mapping[char]
return result
然后我们可以比较这些:
>>> testfunctions = {
... 'Alex Hall (A)': seq_to_int_alexhall_a,
... 'Alex Hall (B)': seq_to_int_alexhall_b,
... 'Jonathan May': seq_to_int_jonathan_may,
... # base_decode as defined in https://stackoverflow.com/a/50239330
... 'martineau': base_decode,
... 'Martijn Pieters': seq_to_int,
... }
>>> setup = """\
... from __main__ import testvalues, {} as testfunction
... tviter = iter(testvalues)
... """
>>> for name, f in testfunctions.items():
... res = timeit('testfunction(next(tviter))', setup.format(f.__name__))
... print(f'{name:>15}: {res:8.5f}')
...
Alex Hall (A): 2.17879
Alex Hall (B): 2.40771
Jonathan May: 3.30303
martineau: 16.60615
Martijn Pieters: 0.73452
我建议的 base-4 方法很容易在这个比较中获胜。