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


75

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

因此,如果我有2个字符串-

string1 = apples
string2 = appleses

answer = apples

另一个示例,因为字符串可能包含多个单词。

string1 = apple pie available
string2 = apple pies

answer = apple pie

我敢肯定有一种简单的Python方式可以做到这一点,但是我无法解决,感谢您的帮助和解释。


8
如果string1 = bapplesstring2 = cappleses呢?
Ashwini Chaudhary 2013年


11
如果您需要通用前缀os.path.commonprefix(['apples', 'appleses']) -> 'apples'`
jfs


问题的内容与标题中的内容不符。所描述的问题是最长的公共前缀
heorhi

Answers:


24

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

4
在给定某些输入(例如“苹果派...”,“苹果派”)的情况下,该算法是不正确的,但是如果您切换参数位置,该算法将起作用。我认为在比较时,if语句出了问题i+j < len1
REALFREE 2014年

这适用于最长的前缀,并在后缀处中断。例如x = "cov_basic_as_cov_x_gt_y_rna_genes_w1000000" y = "cov_rna15pcs_as_cov_x_gt_y_rna_genes_w1000000"
Dima Lituiev

8
这完全是错误的。试试string1 =“ 2193588”,string2 =“ 21943588”
Nozar Safari

1
这需要下票才能被删除...这是一个错误的答案...
grepit

1
这是行不通的,因为它没有考虑您需要对第二个字符串进行“重新匹配”的情况。例如,在“ acdaf”和“ acdacdaf”中,当从第一个字符串的“ a”开始时,它将一直匹配到第二个字符串的“ acda”部分,然后它将在c处中断。然后,无论您如何都无法拿起acdaf。
玉木樱

158

为了完整起见,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

3
对于那些在较长的字符串上使用此参数的用户,在创建SequenceMatcher实例时,可能需要将kwarg“ autojunk”设置为False。
MLP '18

1
我将注意到difflib中存在一些突出的错误,这些错误应阻止在实际场景中使用它。例如,似乎众所周知的“启发式”会干扰诸如“ get_matching_blocks”之类的方法的完整性。
W4t3randWind

2
警告:此答案找不到最长的公共子字符串!尽管有其名称(以及该方法的文档),find_longest_match()但并未执行其名称所隐含的含义。的类文档SequenceMatcher确实暗示了这一点,说:This does not yield minimal edit sequences。举例来说,在一些情况下,find_longest_match()将权利要求有没有在长度为1000的两个字符串匹配,即使有长度的匹配子> 500
阿列克西Torhamo

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

7
到目前为止,该解决方案尚未完成。它仅从零位置比较两个字符串。例如:>>> common_start(“ XXXXXapple pie available”,“ apple pies”)返回一个空字符串。
Nitin Nain 2014年

2
@NitinNain:在原始问题中从未对此进行过澄清。但是,是的,此解决方案只能找到字符串的共同起点
Eric

1
PEP479生效后,这项功能会起作用吗?
Janus Troelsen

1
否-该文档中的内容“还有一些生成器表达式的例子,这些表达式依赖于表达式,目标或谓词(而不是for循环固有的__next __()调用)引发的StopIteration 。
Eric

1
@Eric还是从Python的3.6版本说明Raising the StopIteration exception inside a generator will now generate a DeprecationWarning。如果使用运行代码Python3 -W default::DeprecationWarning,则后两个示例都将引发DeprecationWarnings
jpyams

26

人们可能还会认为os.path.commonprefix它适用于字符,因此可以用于任何字符串。

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

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


1
比较['可用的苹果派','苹果派']之类的字符串时,它不起作用。
GoTop

1
明确的答案,现在应该清楚此解决方案的作用。在这方面,这个问题有点含糊。标题表示“任何子串”,描述和示例表示“公共前缀”。
jonas

@famzah您链接os.commonpath到此文档的文档os.commonprefix与答案中使用的文档不同。但是,确实存在一些限制,只是文档中没有提及任何限制。
jonas

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

4

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

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

2

尝试:

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

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


我现在想让pythonit.takewhile具有语言功能:a for a, b in zip(string1, string2) while a == b
Eric

''.join(el[0] for el in itertools.takewhile(lambda t: t[0] == t[1], zip("ahello", "hello")))返回"",这似乎是不正确的。正确的结果是"hello"
安德森·格林

@AndersonGreen:没错,虽然他的示例仅考虑了第一个字符的起点,但它并不能完全回答问题,我也在回答中指出了这一点。
Birei 2015年

1
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     

1

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

如有疑问,请通知我。


1

如果我们有一个单词列表,我们需要找到所有常见的子字符串,我检查上面的一些代码,最好的是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'}


0

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

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

0

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

注意:这只会找到一个最长的公共子字符串。如果不止一个,则可以创建一个数组将结果存储在其中并返回该值。此外,它区分大小写,因此(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

0
**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

0

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

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

0

该脚本要求您最小公共子串长度,并在两个字符串中给出所有公共子串。而且,它消除了较长的子字符串已经包含的较短的子字符串。

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

0

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

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'


0
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]

-1

首先,根据itertools的成对配方改编一个辅助函数以生成子字符串。

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

-1
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))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.