Performance 找到到任何子串的最小汉明距离的最快方法?

Performance 找到到任何子串的最小汉明距离的最快方法?,performance,algorithm,string,Performance,Algorithm,String,给定一个长字符串L和一个短字符串S(约束条件是L。长度必须>=S。长度),我想找到S和L的任何子字符串之间的最小汉明距离,长度等于S。让我们为这个minHamming()调用函数。比如说, minHamming(ABCDEFGHIJ,CDEFGG)==1 minHamming(ABCDEFGHIJ,BCDGHI)==3 显然,这样做(枚举L的每个子字符串)需要O(S.length*L.length)时间。在次线性时间里有什么聪明的方法可以做到这一点吗?我用几个不同的S字符串搜索相同的L,因此对L

给定一个长字符串
L
和一个短字符串
S
(约束条件是
L
。长度必须>=
S
。长度),我想找到
S
L
的任何子字符串之间的最小汉明距离,长度等于
S
。让我们为这个
minHamming()
调用函数。比如说,

minHamming(ABCDEFGHIJ,CDEFGG)==1

minHamming(ABCDEFGHIJ,BCDGHI)==3

显然,这样做(枚举L的每个子字符串)需要O(
S
.length*
L
.length)时间。在次线性时间里有什么聪明的方法可以做到这一点吗?我用几个不同的
S
字符串搜索相同的
L
,因此对
L
进行一次复杂的预处理是可以接受的


编辑:修改后的Boyer Moore是个好主意,除了我的字母表只有4个字母(DNA)。

就big-O而言,你被卡住了。。在基本级别上,您需要测试目标中的每个字母是否与子字符串中的每个合格字母匹配

幸运的是,这很容易并行化


您可以应用的一个优化是为当前位置保留不匹配的运行计数。如果它大于目前为止的最低汉明距离,那么显然你可以跳到下一种可能性。

就大O而言,你被卡住了。。在基本级别上,您需要测试目标中的每个字母是否与子字符串中的每个合格字母匹配

幸运的是,这很容易并行化

您可以应用的一个优化是为当前位置保留不匹配的运行计数。如果它大于目前为止的最低汉明距离,那么显然你可以跳到下一种可能性。

修改过的Boyer-Moore 我刚刚挖掘了一些我曾经使用过的旧Python实现,并修改了匹配循环(其中文本与模式进行比较)。不要在发现两个字符串之间的第一个不匹配时立即中断,只需计算不匹配的数量,但是记住第一个不匹配

current_dist = 0
while pattern_pos >= 0:
    if pattern[pattern_pos] != text[text_pos]:
        if first_mismatch == -1:
            first_mismatch = pattern_pos
            tp = text_pos
        current_dist += 1
        if current_dist == smallest_dist:
           break

     pattern_pos -= 1
     text_pos -= 1

 smallest_dist = min(current_dist, smallest_dist)
 # if the distance is 0, we've had a match and can quit
 if current_dist == 0:
     return 0
 else: # shift
     pattern_pos = first_mismatch
     text_pos = tp
     ...
如果此时字符串未完全匹配,请通过恢复值返回到第一次不匹配的点。这样可以确保实际找到最小的距离

整个实现相当长(~150LOC),但我可以根据请求发布它。核心思想如上所述,其他一切都是标准的Boyer Moore

文本预处理 另一种加速的方法是对文本进行预处理,以便在字符位置上建立索引。您只希望在两个字符串之间至少发生一次匹配的位置开始比较,否则汉明距离很小

import sys
from collections import defaultdict
import bisect

def char_positions(t):
    pos = defaultdict(list)
    for idx, c in enumerate(t):
        pos[c].append(idx)
    return dict(pos)
此方法仅创建一个字典,将文本中的每个字符映射到其出现的排序列表

比较循环或多或少与naive
O(mn)
方法相同,除了我们没有将每次开始比较的位置增加1,而是基于角色位置:

def min_hamming(text, pattern):
    best = len(pattern)
    pos = char_positions(text)

    i = find_next_pos(pattern, pos, 0)

    while i < len(text) - len(pattern):
        dist = 0
        for c in range(len(pattern)):
            if text[i+c] != pattern[c]:
                dist += 1
                if dist == best:
                    break
            c += 1
        else:
            if dist == 0:
                return 0
        best = min(dist, best)
        i = find_next_pos(pattern, pos, i + 1)

    return best
对于每个新位置,我们都会找到L中出现S中字符的最低索引。如果不再有此类索引,算法将终止

find_next_pos
肯定很复杂,可以尝试通过仅使用模式的前几个字符来改进它,或者使用一个集合来确保模式中的字符不会被检查两次

讨论 哪种方法更快很大程度上取决于您的数据集。你的字母表越多样化,跳跃就越大。如果你有一个很长的L,那么第二个预处理方法可能会更快。对于非常非常短的字符串(如您的问题中),天真的方法肯定是最快的

脱氧核糖核酸 如果您的字母表非常小,您可以尝试获取字符双字母表(或更大)的字符位置,而不是单字母表。

Modified Boyer Moore 我刚刚挖掘了一些我曾经使用过的旧Python实现,并修改了匹配循环(其中文本与模式进行比较)。不要在发现两个字符串之间的第一个不匹配时立即中断,只需计算不匹配的数量,但是记住第一个不匹配

current_dist = 0
while pattern_pos >= 0:
    if pattern[pattern_pos] != text[text_pos]:
        if first_mismatch == -1:
            first_mismatch = pattern_pos
            tp = text_pos
        current_dist += 1
        if current_dist == smallest_dist:
           break

     pattern_pos -= 1
     text_pos -= 1

 smallest_dist = min(current_dist, smallest_dist)
 # if the distance is 0, we've had a match and can quit
 if current_dist == 0:
     return 0
 else: # shift
     pattern_pos = first_mismatch
     text_pos = tp
     ...
如果此时字符串未完全匹配,请通过恢复值返回到第一次不匹配的点。这样可以确保实际找到最小的距离

整个实现相当长(~150LOC),但我可以根据请求发布它。核心思想如上所述,其他一切都是标准的Boyer Moore

文本预处理 另一种加速的方法是对文本进行预处理,以便在字符位置上建立索引。您只希望在两个字符串之间至少发生一次匹配的位置开始比较,否则汉明距离很小

import sys
from collections import defaultdict
import bisect

def char_positions(t):
    pos = defaultdict(list)
    for idx, c in enumerate(t):
        pos[c].append(idx)
    return dict(pos)
此方法仅创建一个字典,将文本中的每个字符映射到其出现的排序列表

比较循环或多或少与naive
O(mn)
方法相同,除了我们没有将每次开始比较的位置增加1,而是基于角色位置:

def min_hamming(text, pattern):
    best = len(pattern)
    pos = char_positions(text)

    i = find_next_pos(pattern, pos, 0)

    while i < len(text) - len(pattern):
        dist = 0
        for c in range(len(pattern)):
            if text[i+c] != pattern[c]:
                dist += 1
                if dist == best:
                    break
            c += 1
        else:
            if dist == 0:
                return 0
        best = min(dist, best)
        i = find_next_pos(pattern, pos, i + 1)

    return best
对于每个新位置,我们都会找到L中出现S中字符的最低索引。如果不再有此类索引,算法将终止

find_next_pos
肯定很复杂,可以尝试通过仅使用模式的前几个字符来改进它,或者使用一个集合来确保模式中的字符不会被检查两次

讨论 哪种方法更快很大程度上取决于您的数据集。你的字母表越多样化,跳跃就越大。如果你有