如何(快速)根据python中的特定模式获取子字符串?

时间:2018-03-22 07:51:18

标签: string python-3.x cython

我正在尝试在python中编写不匹配内核。我想提取给定一个numpy布尔数组掩码的字符串的所有子串,其中提取模式不一定是连续的(例如mask = [False,True,False,True],这样从'ABCD'我提取'BD' )。在根据这种模式提取子串之后,我可以计算我的两个序列之间的所有常见子串。 关于提取步骤string[theta]不能提取这样的子字符串。我现在有以下代码块可用:

def function(s1, s2, k, theta):
 l1 = []
 l2 = []

 # substrings of s1
 substrk_itr1 = (s1[i:i+k] for i in range(len(s1) - k + 1))
 l1 = [''.join(substr[i] for i, b in enumerate(theta) if b)
       for substr in substrk_itr1]

 # substrings of s2
 substrk_itr2 = (s2[i:i+k] for i in range(len(s2) - k + 1))
 l2 = [''.join(substr[i] for i, b in enumerate(theta) if b)
       for substr in substrk_itr2]

 L = l1 + l2
 C = Counter(L)
 c1 = Counter(l1)
 c2 = Counter(l2)
 x = sum([c1[w] * c2[w] for w in C if w])
 return x

其中(s1,s2)是字符串我想通过首先考虑长度为k的所有子字符串来提取所有子字符串,然后根据布尔模式theta重新提取子字符串。您可以使用以下值进行测试,理论上应该得到2。

k = 5
theta = np.array([False,True, True, True, False])
X = 'AAATCGGGT'
Y = 'AAATTGGGT'

问题是这段代码太慢了(我用它来计算内核,所以我运行了数千次)。我分析了代码,瓶颈主要是由于连接功能。

有没有办法用python代码或更pythonic方式更快地执行提取步骤?如果我在cython中编写这样的代码可能会更快吗?关于他们说的文件:

  

在许多用例中,C字符串(a.k.a.字符指针)速度慢且繁琐。首先,它们通常需要以某种方式进行手动内存管理,这样就更有可能在代码中引入错误。

感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

只需将字符串转换为numpy数组(dtype=np.int8与字符大小相同)并将''.join(...)替换为布尔数组索引,就可以非常轻松地提高35%的速度: substr[theta]

def function(s1,k,theta):
    s1 = np.fromstring(s1,np.int8)

    substrk_itr1 = (s1[i:i+k] for i in range(len(s1) - k + 1))
    l1 = [substr[theta] for substr in substrk_itr1]

    l1 = [ x.tostring() for x in l1 ]

    # etc for s2

你几乎可以做到更多,但这是最明显的快速改进。

答案 1 :(得分:0)

除了利用@DavidW建议的类型之外,使用适当的数据结构也很重要。像list这样的Python容器对象很慢,因为它们在内存中不是连续的,在编写具有性能意识的cython代码时应该避免使用它们。

在这种情况下,我们也可以保存一些for循环。我们可以同时处理两个字符串,而不是对thetas1子列表重复s2两次。我们也可以逐个比较这些字符,然后在我们点击不匹配的第一个字符/核苷酸时立即打破比较。

下面是我的代码的cython版本,它应该比问题的代码运行得快一个数量级(对于100万次迭代而言大约需要1秒而不是40秒左右)。我添加了一些希望有用的评论。至于你对C字符串管理的关注,至少对于像这样的简单一次性函数,只要你为free的每次调用调用适当的malloc,你应该没问题。由于不需要通过这种方式直接在C级动态分配char*,因此不需要担心这里的内存管理。此外,索引到char*而不是str,特别是在像这样的紧密循环中,可以避免一些轻微的python开销。

from libc.stdint cimport int8_t
cimport cython

"""
I have included three @cython decorators here to save some checks.
The first one, boundscheck is the only useful one in this scenario, actually.
You can see their effect if you generate cython annotations!
Read more about it here: 
http://cython.readthedocs.io/en/latest/src/reference/compilation.html
"""
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.initializedcheck(False)
def fast_count_substr_matches(str s1, str s2, int k, int8_t[:] theta):
    cdef int i, j, m#you used k, unfortunately, so stuck with m...
    cdef bytes b1 = s1.encode("utf-8")
    #alternatively, could just pass in bytes strings instead
    #by prefixing your string literals like b'AAATCGGGT'
    cdef bytes b2 = s2.encode("utf-8")
    cdef char* c1 = b1
    cdef char* c2 = b2
    #python str objects have minor overhead when accessing them with str[index]
    #this is why I bother converting them to char* at the start
    cdef int count = 0
    cdef bint comp#A C-type int that can be treated as python bool nicely

    for i in range(len(s1) - k + 1):
        for j in range(len(s2) - k + 1):
            comp = True
            for m in range(k):
                if theta[m] == True and c1[i + m] != c2[j + m]:
                    comp = False
                    break         
            if comp:
                count += 1
    return count

如果此答案中有任何内容可以清除,请告知我们。希望这有帮助!