【问题标题】:Find common substring between two strings查找两个字符串之间的公共子字符串
【发布时间】:2013-09-13 23:20:17
【问题描述】:

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

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

string1 = apples
string2 = appleses

answer = apples

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

string1 = apple pie available
string2 = apple pies

answer = apple pie

我确信有一种简单的 Python 方法可以做到这一点,但我无法解决,感谢任何帮助和解释。

【问题讨论】:

标签: python string algorithm time-complexity dynamic-programming


【解决方案1】:

为了完整起见,标准库中的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

【讨论】:

  • 提醒那些在较长字符串上使用它的人,您可能希望在创建 SequenceMatcher 实例时将 kwarg "autojunk" 设置为 False。
  • 我会注意到 difflib 中有一些突出的错误应该会阻止它在实际场景中的使用。例如,众所周知的“启发式”似乎干扰了“get_matching_blocks”等方法的完整性。
  • 警告:这个答案没有找到最长的公共子字符串! 尽管有它的名字(和方法的文档),find_longest_match() 并没有做到它的名字所暗示的那样。 SequenceMatcher 的类文档确实暗示了这一点,但是说:This does not yield minimal edit sequences。例如,在某些情况下,find_longest_match() 会声称在两个长度为 1000 的字符串中存在 no 匹配项,即使存在长度大于 500 的匹配子字符串。
  • 伙计,土耳其编写了那个 API。强迫您每次都输入字符串的长度,而不是仅仅假设它是完整的字符串,并且 SequenceMatcher 的第一个参数几乎总是 None :@
【解决方案2】:
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())
>>> common_start("apple pie available", "apple pies")
'apple pie'

或者稍微奇怪的方式:

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))

这可能更具可读性

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))

【讨论】:

  • 这个解决方案目前还不完整。它只比较从第零位开始的两个字符串。例如: >>> common_start("XXXXXapple pie available", "apple pies") 返回一个空字符串。
  • @NitinNain:原始问题中从未澄清过。但是,是的,此解决方案仅找到字符串的常见 start
  • PEP479 生效后这项功能会起作用吗?
  • 否 - 来自that document“还有一些生成器表达式的例子,它们依赖于表达式、目标或谓词引发的 StopIteration (而不是在 for 循环中隐含的 __next__() 调用)。"
  • @Eric 仍然来自Python 3.6 release notesRaising the StopIteration exception inside a generator will now generate a DeprecationWarning。如果您使用Python3 -W default::DeprecationWarning 运行代码,最后两个示例都会引发DeprecationWarnings
【解决方案3】:

也可以考虑os.path.commonprefix,它适用于字符,因此可用于任何字符串。

import os
common = os.path.commonprefix(['apple pie available', 'apple pies'])
assert common == 'apple pie'

如函数名所示,这里只考虑两个字符串的公共前缀。

【讨论】:

  • 它不起作用,当比较像 ['an apple pie available', 'apple pies'] 这样的字符串时。
  • 澄清答案,现在应该清楚这个解决方案的作用。这个问题在这方面有点模糊。标题暗示“任何子串”,描述和示例表明“公共前缀”。
  • @famzah 您链接到os.commonpath 的文档,这与答案中使用的os.commonprefix 不同。但确实,可能存在一些限制,只是文档没有提及任何限制。
【解决方案4】:

它被称为最长公共子串问题。在这里,我提出一个简单易懂但效率低下的解决方案。为大字符串生成正确的输出需要很长时间,因为该算法的复杂度为 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")

输出

apple pie
apples
apples

【讨论】:

  • 这个算法在给定一些输入(例如“apple pie...”、“apple pie”)的情况下是不正确的,但是如果你切换参数位置就可以工作。我认为当您比较 i+j &lt; len1 时,if 语句有问题
  • 这适用于最长的前缀和后缀中断。例如。 x = "cov_basic_as_cov_x_gt_y_rna_genes_w1000000" y = "cov_rna15pcs_as_cov_x_gt_y_rna_genes_w1000000"
  • 完全错误。试试 string1="2193588" , string2="21943588"
  • 这需要投票才能被删除......这是一个错误的答案......
  • 这不起作用,因为它不考虑您需要对第二个字符串进行“重新匹配”的情况。例如,在 "acdaf" vs "acdacdaf" 中,当从第一个字符串的 "a" 开始时,它将一直匹配到第二个字符串的 "acda" 部分,然后它将在 c 处中断。那么无论如何你都不能再拿起acdaf了。
【解决方案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")

【讨论】:

    【解决方案6】:

    Evo's 相同,但可以比较任意数量的字符串:

    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())
    

    【讨论】:

      【解决方案7】:

      试试:

      import itertools as it
      ''.join(el[0] for el in it.takewhile(lambda t: t[0] == t[1], zip(string1, string2)))
      

      它从两个字符串的开头进行比较。

      【讨论】:

      • 我现在希望 python 使 it.takewhile 成为一种语言功能:a for a, b in zip(string1, string2) while a == b
      • ''.join(el[0] for el in itertools.takewhile(lambda t: t[0] == t[1], zip("ahello", "hello"))) 返回"",这似乎是不正确的。正确的结果是"hello"
      • @AndersonGreen:你说得对,虽然他的例子只考虑了第一个字符的起点,但我在回答中也指出了这一点。
      【解决方案8】:
      def matchingString(x,y):
          match=''
          for i in range(0,len(x)):
              for j in range(0,len(y)):
                  k=1
                  # now applying while condition untill we find a substring match and length of substring is less than length of x and y
                  while (i+k <= len(x) and j+k <= len(y) and x[i:i+k]==y[j:j+k]):
                      if len(match) <= len(x[i:i+k]):
                         match = x[i:i+k]
                      k=k+1
          return match  
      
      print matchingString('apple','ale') #le
      print matchingString('apple pie available','apple pies') #apple pie     
      

      【讨论】:

        【解决方案9】:

        此脚本要求您提供最小公共子字符串长度,并在两个字符串中提供所有公共子字符串。此外,它还消除了较长子字符串已经包含的较短子字符串。

        def common_substrings(str1,str2):
            len1,len2=len(str1),len(str2)
        
            if len1 > len2:
                str1,str2=str2,str1
                len1,len2=len2,len1
        
            min_com = int(input('Please enter the minumum common substring length:'))
            
            cs_array=[]
            for i in range(len1,min_com-1,-1):
                for k in range(len1-i+1):
                    if (str1[k:i+k] in str2):
                        flag=1
                        for m in range(len(cs_array)):
                            if str1[k:i+k] in cs_array[m]:
                            #print(str1[k:i+k])
                                flag=0
                                break
                        if flag==1:
                            cs_array.append(str1[k:i+k])
            if len(cs_array):
                print(cs_array)
            else:
                print('There is no any common substring according to the parametres given')
        
        common_substrings('ciguliuana','ciguana')
        common_substrings('apples','appleses')
        common_substrings('apple pie available','apple pies')
        

        【讨论】:

          【解决方案10】:

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

          from suffix_trees import STree
          
          a = ["xxxabcxxx", "adsaabc"]
          st = STree.STree(a)
          print(st.lcs()) # "abc"
          

          【讨论】:

            【解决方案11】:

            Trie 数据结构效果最好,比 DP 更好。 这是代码。

            class TrieNode:
                def __init__(self):
                    self.child = [None]*26
                    self.endWord = False
            
            class Trie:
            
                def __init__(self):
                    self.root = self.getNewNode()
            
                def getNewNode(self):
                    return TrieNode()
            
                def insert(self,value):
                    root = self.root
            
            
                    for i,character in enumerate(value):
                        index = ord(character) - ord('a')
                        if not root.child[index]:
                            root.child[index] = self.getNewNode()
                        root = root.child[index]
            
                    root.endWord = True
            
            
                def search(self,value):
                    root = self.root
            
                    for i,character in enumerate(value):
                        index = ord(character) - ord('a')
                        if not root.child[index]:
                            return False
                        root = root.child[index]
                    return root.endWord
            
            def main(): 
            
                # Input keys (use only 'a' through 'z' and lower case) 
                keys = ["the","anaswe"] 
                output = ["Not present in trie", 
                        "Present in trie"] 
            
                # Trie object 
                t = Trie() 
            
                # Construct trie 
                for key in keys: 
                    t.insert(key) 
            
                # Search for different keys 
                print("{} ---- {}".format("the",output[t.search("the")])) 
                print("{} ---- {}".format("these",output[t.search("these")])) 
                print("{} ---- {}".format("their",output[t.search("their")])) 
                print("{} ---- {}".format("thaw",output[t.search("thaw")])) 
            
            if __name__ == '__main__': 
                main() 
            

            如有疑问,请告诉我。

            【讨论】:

              【解决方案12】:

              如果我们有一个单词列表,我们需要找到所有常见的子字符串,我会检查上面的一些代码,最好的是https://stackoverflow.com/a/42882629/8520109,但它有一些错误,例如 'histhome''homehist'。在这种情况下,我们应该有 'hist''home' 作为结果。此外,如果参数的顺序改变,它会有所不同。所以我更改了代码以查找每个子字符串块,它会产生一组常见的子字符串:

              main = input().split(" ")    #a string of words separated by space
              def longestSubstringFinder(string1, string2):
                  '''Find the longest matching word'''
                  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
              
              def listCheck(main):
                  '''control the input for finding substring in a list of words'''
                  string1 = main[0]
                  result = []
                  for i in range(1, len(main)):
                      string2 = main[i]
                      res1 = longestSubstringFinder(string1, string2)
                      res2 = longestSubstringFinder(string2, string1)
                      result.append(res1)
                      result.append(res2)
                  result.sort()
                  return result
              
              first_answer = listCheck(main)
              
              final_answer  = []
              
              
              for item1 in first_answer:    #to remove some incorrect match
                  string1 = item1
                  double_check = True
                  for item2 in main:
                      string2 = item2
                      if longestSubstringFinder(string1, string2) != string1:
                          double_check = False
                  if double_check:
                      final_answer.append(string1)
              
              print(set(final_answer))
              

              main = 'ABACDAQ BACDAQA ACDAQAW XYZCDAQ' #>>> {'CDAQ'}
              main = 'homehist histhome' #>>> {'hist', 'home'}
              
              

              【讨论】:

                【解决方案13】:
                def LongestSubString(s1,s2):
                    if len(s1)<len(s2) :
                        s1,s2 = s2,s1  
                    
                    maxsub =''
                    for i in range(len(s2)):
                        for j in range(len(s2),i,-1):
                            if s2[i:j] in s1 and j-i>len(maxsub):                
                                return  s2[i:j]
                

                【讨论】:

                • 我建议在最后添加一个return '',因为在退化的情况下,你不想返回None(默认情况下是python);你想返回空字符串。
                【解决方案14】:

                返回第一个最长的公共子串:

                def compareTwoStrings(string1, string2):
                    list1 = list(string1)
                    list2 = list(string2)
                
                    match = []
                    output = ""
                    length = 0
                
                    for i in range(0, len(list1)):
                
                        if list1[i] in list2:
                            match.append(list1[i])
                
                            for j in range(i + 1, len(list1)):
                
                                if ''.join(list1[i:j]) in string2:
                                    match.append(''.join(list1[i:j]))
                
                                else:
                                    continue
                        else:
                            continue
                
                    for string in match:
                
                        if length < len(list(string)):
                            length = len(list(string))
                            output = string
                
                        else:
                            continue
                
                    return output
                

                【讨论】:

                  【解决方案15】:
                  **Return the comman longest substring** 
                  def longestSubString(str1, str2):
                      longestString = ""
                      maxLength = 0
                      for i in range(0, len(str1)):
                          if str1[i] in str2:
                              for j in range(i + 1, len(str1)):
                                  if str1[i:j] in str2:
                                      if(len(str1[i:j]) > maxLength):
                                          maxLength = len(str1[i:j])
                                          longestString =  str1[i:j]
                  return longestString
                  

                  【讨论】:

                    【解决方案16】:

                    这是名为“最长序列查找器”的课堂问题。我给出了一些对我有用的简单代码,我的输入也是一个序列列表,也可以是一个字符串:

                    def longest_substring(list1,list2):
                        both=[]
                        if len(list1)>len(list2):
                            small=list2
                            big=list1
                        else:
                            small=list1
                            big=list2
                        removes=0
                        stop=0
                        for i in small:
                            for j in big:
                                if i!=j:
                                    removes+=1
                                    if stop==1:
                                        break
                                elif i==j:
                                    both.append(i)
                                    for q in range(removes+1):
                                        big.pop(0)
                                    stop=1
                                    break
                            removes=0
                        return both
                    

                    【讨论】:

                      【解决方案17】:

                      好像这个问题没有足够的答案,这里有另一种选择:

                      from collections import defaultdict
                      def LongestCommonSubstring(string1, string2):
                          match = ""
                          matches = defaultdict(list)
                          str1, str2 = sorted([string1, string2], key=lambda x: len(x))
                      
                          for i in range(len(str1)):
                              for k in range(i, len(str1)):
                                  cur = match + str1[k]
                                  if cur in str2:
                                      match = cur
                                  else:
                                      match = ""
                                  
                                  if match:
                                      matches[len(match)].append(match)
                              
                          if not matches:
                              return ""
                      
                          longest_match = max(matches.keys())
                              
                          return matches[longest_match][0]
                      
                      

                      一些例子:

                      LongestCommonSubstring("whose car?", "this is my car")
                      > ' car'
                      LongestCommonSubstring("apple pies", "apple? forget apple pie!")
                      > 'apple pie'
                      
                      

                      【讨论】:

                        【解决方案18】:

                        这不是最有效的方法,但它是我能想到的并且有效。如果有人可以改进它,请做。它的作用是创建一个矩阵并将 1 放在字符匹配的位置。然后它扫描矩阵以找到最长的 1 对角线,并跟踪它的开始和结束位置。然后它以开始和结束位置作为参数返回输入字符串的子字符串。

                        注意:这只会找到一个最长的公共子字符串。如果有多个,您可以创建一个数组来存储结果并返回它此外,它区分大小写,因此 (Apple pie, apple pie) 将返回 pple pie。

                        def longestSubstringFinder(str1, str2):
                        answer = ""
                        
                        if len(str1) == len(str2):
                            if str1==str2:
                                return str1
                            else:
                                longer=str1
                                shorter=str2
                        elif (len(str1) == 0 or len(str2) == 0):
                            return ""
                        elif len(str1)>len(str2):
                            longer=str1
                            shorter=str2
                        else:
                            longer=str2
                            shorter=str1
                        
                        matrix = numpy.zeros((len(shorter), len(longer)))
                        
                        for i in range(len(shorter)):
                            for j in range(len(longer)):               
                                if shorter[i]== longer[j]:
                                    matrix[i][j]=1
                        
                        longest=0
                        
                        start=[-1,-1]
                        end=[-1,-1]    
                        for i in range(len(shorter)-1, -1, -1):
                            for j in range(len(longer)):
                                count=0
                                begin = [i,j]
                                while matrix[i][j]==1:
                        
                                    finish=[i,j]
                                    count=count+1 
                                    if j==len(longer)-1 or i==len(shorter)-1:
                                        break
                                    else:
                                        j=j+1
                                        i=i+1
                        
                                i = i-count
                                if count>longest:
                                    longest=count
                                    start=begin
                                    end=finish
                                    break
                        
                        answer=shorter[int(start[0]): int(end[0])+1]
                        return answer
                        

                        【讨论】:

                          【解决方案19】:

                          首先一个 helper 函数改编自 itertools pairwise recipe 以生成子字符串。

                          import itertools
                          def n_wise(iterable, n = 2):
                              '''n = 2 -> (s0,s1), (s1,s2), (s2, s3), ...
                          
                              n = 3 -> (s0,s1, s2), (s1,s2, s3), (s2, s3, s4), ...'''
                              a = itertools.tee(iterable, n)
                              for x, thing in enumerate(a[1:]):
                                  for _ in range(x+1):
                                      next(thing, None)
                              return zip(*a)
                          

                          然后是一个函数,它迭代子字符串,最长的优先,并测试成员资格。 (不考虑效率)

                          def foo(s1, s2):
                              '''Finds the longest matching substring
                              '''
                              # the longest matching substring can only be as long as the shortest string
                              #which string is shortest?
                              shortest, longest = sorted([s1, s2], key = len)
                              #iterate over substrings, longest substrings first
                              for n in range(len(shortest)+1, 2, -1):
                                  for sub in n_wise(shortest, n):
                                      sub = ''.join(sub)
                                      if sub in longest:
                                          #return the first one found, it should be the longest
                                          return sub
                          
                          s = "fdomainster"
                          t = "exdomainid"
                          print(foo(s,t))
                          

                          >>> 
                          domain
                          >>> 
                          

                          【讨论】:

                            【解决方案20】:
                            def LongestSubString(s1,s2):
                                left = 0
                                right =len(s2)
                                while(left<right):
                                    if(s2[left] not in s1):
                                        left = left+1
                                    else:
                                        if(s2[left:right] not in s1):
                                            right = right - 1
                                        else:
                                            return(s2[left:right])
                            
                            s1 = "pineapple"
                            s2 = "applc"
                            print(LongestSubString(s1,s2))
                            

                            【讨论】:

                              猜你喜欢
                              • 2013-04-18
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 2014-12-07
                              • 2016-04-20
                              相关资源
                              最近更新 更多