找到两个字符串之间的公共子串

Nor*_*ide 54 python string algorithm dynamic-programming time-complexity

我想比较2个字符串并保持匹配,在比较失败的地方分开.

所以,如果我有2个字符串 -

string1 = apples
string2 = appleses

answer = apples
Run Code Online (Sandbox Code Playgroud)

另一个例子,因为字符串可能有多个单词.

string1 = apple pie available
string2 = apple pies

answer = apple pie
Run Code Online (Sandbox Code Playgroud)

我确信有一种简单的Python方法可以做到这一点,但我无法解决,任何帮助和解释都表示赞赏.

Ric*_*ren 136

为了完整起见,difflib在标准库中提供了大量序列比较实用程序.例如find_longest_match,当在字符串上使用时,它找到最长的公共子字符串.使用示例:

from difflib import SequenceMatcher

string1 = "apple pie available"
string2 = "come have some apple pies"

match = SequenceMatcher(None, string1, string2).find_longest_match(0, len(string1), 0, len(string2))

print(match)  # -> Match(a=0, b=15, size=9)
print(string1[match.a: match.a + match.size])  # -> apple pie
print(string2[match.b: match.b + match.size])  # -> apple pie
Run Code Online (Sandbox Code Playgroud)

  • @NorthSide这应该是公认的答案 (22认同)
  • 老兄,那个 API 是哪个火鸡写的。迫使您每次都输入字符串的长度,而不是仅仅假设它是完整的字符串,并且 SequenceMatcher 的第一个参数几乎总是 None :@ (9认同)
  • **警告:这个答案没有找到最长的公共子串!** 尽管它的名字(和方法的文档),`find_longest_match()` 并不像它的名字暗示的那样做。`SequenceMatcher` 的类文档确实暗示了这一点,但是,它说:`这不会产生最小的编辑序列`。例如,在某些情况下,`find_longest_match()` 会声称在两个长度为 1000 的字符串中没有 * 匹配,即使存在长度 > 500 的匹配子字符串。 (4认同)
  • 对于那些在较长的字符串上使用此参数的用户,在创建SequenceMatcher实例时,可能需要将kwarg“ autojunk”设置为False。 (2认同)
  • 我会注意到 difflib 中存在一些突出的错误,这些错误应该会阻止它在实际场景中使用。例如,众所周知的“启发式”似乎会干扰“get_matching_blocks”等方法的完整性。 (2认同)

Eri*_*ric 34

def common_start(sa, sb):
    """ returns the longest common substring from the beginning of sa and sb """
    def _iter():
        for a, b in zip(sa, sb):
            if a == b:
                yield a
            else:
                return

    return ''.join(_iter())
Run Code Online (Sandbox Code Playgroud)
>>> common_start("apple pie available", "apple pies")
'apple pie'
Run Code Online (Sandbox Code Playgroud)

或者有点奇怪的方式:

def stop_iter():
    """An easy way to break out of a generator"""
    raise StopIteration

def common_start(sa, sb):
    return ''.join(a if a == b else stop_iter() for a, b in zip(sa, sb))
Run Code Online (Sandbox Code Playgroud)

哪个可能更具可读性

def terminating(cond):
    """An easy way to break out of a generator"""
    if cond:
        return True
    raise StopIteration

def common_start(sa, sb):
    return ''.join(a for a, b in zip(sa, sb) if terminating(a == b))
Run Code Online (Sandbox Code Playgroud)

  • 到目前为止,该解决方案尚未完成.它只比较第0个位置的两个字符串.例如:>>> common_start("XXXXXapple pie available","apple pies")返回一个空字符串. (7认同)
  • @NitinNain:在原始问题中从未澄清过.但是,是的,这个解决方案只找到常见的_start_字符串 (2认同)

the*_*eye 15

它被称为最长公共子串问题.在这里,我提出了一个简单易懂但效率低下的解决方案.由于该算法的复杂度为O(N ^ 2),因此需要很长时间才能为大字符串生成正确的输出.

def longestSubstringFinder(string1, string2):
    answer = ""
    len1, len2 = len(string1), len(string2)
    for i in range(len1):
        match = ""
        for j in range(len2):
            if (i + j < len1 and string1[i + j] == string2[j]):
                match += string2[j]
            else:
                if (len(match) > len(answer)): answer = match
                match = ""
    return answer

print longestSubstringFinder("apple pie available", "apple pies")
print longestSubstringFinder("apples", "appleses")
print longestSubstringFinder("bapples", "cappleses")
Run Code Online (Sandbox Code Playgroud)

产量

apple pie
apples
apples
Run Code Online (Sandbox Code Playgroud)

  • 完全错了.try string1 ="2193588",string2 ="21943588" (5认同)
  • 这需要投票才能被删除......这是一个错误的答案...... (5认同)
  • 这不起作用,因为它没有考虑您需要对第二个字符串进行“重新匹配”的情况。例如,在“acdaf”与“acdacdaf”中,当从第一个字符串的“a”开始时,它将一直匹配到第二个字符串的“acda”部分,然后它将在c处中断。那么无论怎样你都不能再拿起acdaf了。 (3认同)
  • 在给定某些输入(例如“ apple pie ...”,“ apple pie”)的情况下,该算法是错误的,但是如果您切换参数位置,该算法将起作用。我认为在比较`i + j &lt;len1`时,if语句有问题。 (2认同)
  • 这是一个错误的答案,甚至不适用于相同的输入,它应该输出输入之一 (2认同)

jon*_*nas 12

人们也可以考虑os.path.commonprefix对字符起作用,因此可以用于任何字符串.

import os
common = os.path.commonprefix(['apple pie available', 'apple pies'])
assert common == 'apple pie'
Run Code Online (Sandbox Code Playgroud)

  • 当比较像 ['an apple pie available', 'apple pie'] 这样的字符串时,它不起作用。 (2认同)
  • 澄清答案,现在应该清楚这个解决方案是做什么的。在这方面,问题有点含糊。标题暗示“任何子字符串”,描述和示例表明“公共前缀”。 (2认同)

Ser*_*eyR 5

Evo相同,但要比较任意数量的字符串:

def common_start(*strings):
    """ Returns the longest common substring
        from the beginning of the `strings`
    """
    def _iter():
        for z in zip(*strings):
            if z.count(z[0]) == len(z):  # check all elements in `z` are the same
                yield z[0]
            else:
                return

    return ''.join(_iter())
Run Code Online (Sandbox Code Playgroud)


小智 5

使用第一个答案修复错误:

def longestSubstringFinder(string1, string2):
    answer = ""
    len1, len2 = len(string1), len(string2)
    for i in range(len1):
        for j in range(len2):
            lcs_temp=0
            match=''
            while ((i+lcs_temp < len1) and (j+lcs_temp<len2) and string1[i+lcs_temp] == string2[j+lcs_temp]):
                match += string2[j+lcs_temp]
                lcs_temp+=1
            if (len(match) > len(answer)):
                answer = match
    return answer

print longestSubstringFinder("dd apple pie available", "apple pies")
print longestSubstringFinder("cov_basic_as_cov_x_gt_y_rna_genes_w1000000", "cov_rna15pcs_as_cov_x_gt_y_rna_genes_w1000000")
print longestSubstringFinder("bapples", "cappleses")
print longestSubstringFinder("apples", "apples")
Run Code Online (Sandbox Code Playgroud)


And*_*rey 5

我发现最快的方法是使用suffix_trees包:

from suffix_trees import STree

a = ["xxxabcxxx", "adsaabc"]
st = STree.STree(a)
print(st.lcs()) # "abc"
Run Code Online (Sandbox Code Playgroud)