计算两个字符串的字母差异

22

这是我想要的行为:

a: IGADKYFHARGNYDAA
c: KGADKYFHARGNYEAA
2 difference(s).

不,我是Python的新手,所以我正在尝试解决一些逻辑问题来澄清我的思路! - rocker789
你在尝试之前(在这里提问)取得了多少进展?我建议一些教程,我最喜欢的是优达学城的CS101 - Andy Hayden
实际上,我从早上开始一直在处理它,但已经对它感到厌烦了,所以现在才问你! - rocker789
@hayden:实际上,你发布的不是一个URL :) - Niklas B.
@rocker:已经有一个一行代码的答案可以得到你想要的结果,你只需要使用它即可。 - Niklas B.
显示剩余2条评论
12个回答

24
def diff_letters(a,b):
    return sum ( a[i] != b[i] for i in range(len(a)) )

Hayden,我按照你的方法尝试了,但它没有计算出有多少个不同之处。 - rocker789
4
你还可以使用 zipsum(1 for x,y in zip(a, b) if x != y)。在我看来,对布尔值求和有点不太直观 :P - Niklas B.
2
@rocker:我觉得你在这里缺乏一些基本的理解。 - Niklas B.
2
我认为sum(x!=y for x,y in zip(a,b))是一种更稳定的方法,因为给定的示例将在ab长度不同时出错。 - Jack Aidley
list.count(True)sum() 更快。 - young_souvlaki
显示剩余4条评论

13

我认为这个例子将适用于您的具体情况,而且不会给您的Python软件版本带来太多麻烦,也不会出现互操作性问题(请升级到2.7):

a='IGADKYFHARGNYDAA'
b='KGADKYFHARGNYEAA'

u=zip(a,b)
d=dict(u)

x=[]
for i,j in d.items(): 
    if i==j:
        x.append('*') 
    else: 
        x.append(j)
        
print x

输出:['*', 'E', '*', '*', 'K', '*', '*', '*', '*', '*']


通过一些调整,你可以得到你想要的内容... 如果有帮助,请告诉我 :-)


更新

你也可以使用这个:

a='IGADKYFHARGNYDAA'
b='KGADKYFHARGNYEAA'

u=zip(a,b)
for i,j in u:
    if i==j:
        print i,'--',j
    else: 
        print i,'  ',j

输出:

I    K
G -- G
A -- A
D -- D
K -- K
Y -- Y
F -- F
H -- H
A -- A
R -- R
G -- G
N -- N
Y -- Y
D    E
A -- A
A -- A

更新2

你可以修改代码如下:

y=[]
counter=0
for i,j in u:
    if i==j:
        print i,'--',j
    else: 
        y.append(j)
        print i,'  ',j
        
print '\n', y

print '\n Length = ',len(y)

输出:

I    K
G -- G
A -- A
D -- D
K -- K
Y -- Y
F -- F
H -- H
A -- A
R -- R
G -- G
N -- N
Y -- Y
D    E
A -- A
A    X

['K', 'E', 'X']

 Length =  3

但是我想计算并打印出输出中有多少个不同的元素,那么在你的上述代码中该如何实现呢? - rocker789
看看我的回答更新2..如果有帮助到你就让我知道。请记住,相同答案有很多变化,你可以根据需要进行调整..玩得开心 :-) - securecurve
兄弟,如果我只想计算不显示字母的不同之处,我该怎么做? - rocker789
我的意思是我想打印出有多少不同之处,应该打印成这样:2个差异。 - rocker789
@securecurve:在第二次更新中,您添加了一个计数器,但您没有对它进行增量/使用。 - JS.

12

原理

  1. 同时迭代两个字符串并比较字符。
  2. 通过添加空格或|字符将结果存储在一个新字符串中,对于每个不同的字符,增加从零开始的整数值。
  3. 输出结果。

实现

您可以使用内置的zip函数或itertools.izip同时迭代两个字符串,后者在处理大量输入时性能更好。如果字符串大小不同,则仅针对较短部分进行迭代。如果是这种情况,则可以用表示不匹配的字符填充剩余部分。

import itertools

def compare(string1, string2, no_match_c=' ', match_c='|'):
    if len(string2) < len(string1):
        string1, string2 = string2, string1
    result = ''
    n_diff = 0
    for c1, c2 in itertools.izip(string1, string2):
        if c1 == c2:
            result += match_c
        else:
            result += no_match_c
            n_diff += 1
    delta = len(string2) - len(string1)
    result += delta * no_match_c
    n_diff += delta
    return (result, n_diff)

示例

以下是一个简单的测试,和您之前提供的示例有些不同。请注意,我使用了下划线表示不匹配的字符,以更好地展示结果字符串如何扩展到较长字符串的大小。

def main():
    string1 = 'IGADKYFHARGNYDAA AWOOH'
    string2 = 'KGADKYFHARGNYEAA  W'
    result, n_diff = compare(string1, string2, no_match_c='_')

    print "%d difference(s)." % n_diff  
    print string1
    print result
    print string2

main()

输出:

niklas@saphire:~/Desktop$ python foo.py 
6 difference(s).
IGADKYFHARGNYDAA AWOOH
_||||||||||||_|||_|___
KGADKYFHARGNYEAA  W

这是最详细的答案,也考虑了缺失的字母。应该选择它。 - Jorrick Sleijster

6

Python拥有优秀的difflib库,该库提供了所需的功能。

以下是文档中的示例用法:

import difflib  # Works for python >= 2.1

>>> s = difflib.SequenceMatcher(lambda x: x == " ",
...                     "private Thread currentThread;",
...                     "private volatile Thread currentThread;")
>>> for block in s.get_matching_blocks():
...     print "a[%d] and b[%d] match for %d elements" % block
a[0] and b[0] match for 8 elements
a[8] and b[17] match for 21 elements
a[29] and b[38] match for 0 elements    

但是,Thomas,我应该如何在Python 2.6.5中使用它? - rocker789
不确定是否回答了问题。OP想要一种朴素的逐字匹配,而不是LCS。 - Niklas B.
4
是的,但我想也许原帖作者会对更高级的字符串比较感兴趣,考虑到逐字逐句的比较已经在其他答案中被强调了。 - Thomas Orozco

2
a = "IGADKYFHARGNYDAA" 
b = "KGADKYFHARGNYEAAXXX"
match_pattern = zip(a, b)                                 #give list of tuples (of letters at each index)
difference = sum (1 for e in zipped if e[0] != e[1])     #count tuples with non matching elements
difference = difference + abs(len(a) - len(b))            #in case the two string are of different lenght, we add the lenght difference

1

我还没有看到有人使用reduce函数,所以我会包含一段我一直在使用的代码:

reduce(lambda x, y: x + 1 if y[0] != y[1] else x, zip(source, target), 0)

这将为您提供sourcetarget中不同字符的数量。


1

使用difflib.ndiff,你可以用一行代码实现这个功能,而且还比较容易理解:

>>> import difflib
>>> a = 'IGADKYFHARGNYDAA'
>>> c = 'KGADKYFHARGNYEAA'
>>> sum([i[0] != ' '  for i in difflib.ndiff(a, c)]) / 2
2

(sum之所以有效,是因为 True == 1False == 0)

下面的内容可以清楚地说明正在发生什么以及为什么需要 / 2:

>>> [i for i in difflib.ndiff(a,c)]
['- I',
 '+ K',
 '  G',
 '  A',
 '  D',
 '  K',
 '  Y',
 '  F',
 '  H',
 '  A',
 '  R',
 '  G',
 '  N',
 '  Y',
 '- D',
 '+ E',
 '  A',
 '  A']

这也适用于字符串长度不同的情况。

0

我喜欢 Niklas R 的 答案,但它有一个问题(取决于你的期望)。使用以下两个测试用例来测试答案:

print compare('berry','peach')
print compare('berry','cherry')

我们可以合理地期望樱桃浆果桃子更相似。然而,我们得到的浆果桃子之间的差异较小,比浆果樱桃之间的差异还要小:
(' |   ', 4)  # berry, peach
('   |  ', 5) # berry, cherry

这种情况发生在字符串反向比正向更相似的时候。为了扩展 Niklas R 的答案,我们可以添加一个辅助函数,该函数返回正常(正向)差异和反转字符串的差异之间的最小差异:

def fuzzy_compare(string1, string2):
    (fwd_result, fwd_diff) = compare(string1, string2)
    (rev_result, rev_diff) = compare(string1[::-1], string2[::-1])
    diff = min(fwd_diff, rev_diff)
    return diff

请再次使用以下测试用例:
print fuzzy_compare('berry','peach')
print fuzzy_compare('berry','cherry')

...然后我们得到

4 # berry, peach
2 # berry, cherry

就像我之前所说的那样,这只是在 Niklas R. 的答案基础上进行了扩展,而不是修改

如果你只是想要一个简单的差异函数(考虑到上述的陷阱),可以使用以下代码:

def diff(a, b):
    delta = do_diff(a, b)
    delta_rev = do_diff(a[::-1], b[::-1])
    return min(delta, delta_rev)

def do_diff(a,b):
    delta = 0
    i = 0
    while i < len(a) and i < len(b):
        delta += a[i] != b[i]
        i += 1
    delta += len(a[i:]) + len(b[i:])
    return delta

测试用例:

print diff('berry','peach')
print diff('berry','cherry')

最后一个考虑因素是diff函数在处理长度不同的单词时的本身。有两个选择:

  1. 将长度差异视为不同字符之间的差异。
  2. 忽略长度差异,仅比较最短的单词。

例如:

  • appleapples在考虑所有字符时相差1。
  • appleapples在只考虑最短单词时相差0。

当仅考虑最短单词时,我们可以使用:

def do_diff_shortest(a,b):
    delta, i = 0, 0
    if len(a) > len(b):
        a, b = b, a
    for i in range(len(a)):
        delta += a[i] != b[i]
    return delta

...迭代的次数由最短单词决定,其余的被忽略。或者我们可以考虑不同的长度:

def do_diff_both(a, b):
    delta, i = 0, 0
    while i < len(a) and i < len(b):
        delta += a[i] != b[i]
        i += 1
    delta += len(a[i:]) + len(b[i:])
    return delta

在这个例子中,任何剩余的字符都会被计算并添加到diff值中。为了测试这两个函数。
print do_diff_shortest('apple','apples')
print do_diff_both('apple','apples')

将输出:

0 # Ignore extra characters belonging to longest word.
1 # Consider extra characters.

0
当循环遍历一个字符串时,创建一个计数器对象,用于在每次迭代中标识当前所在的字母。然后使用该计数器作为索引来引用另一个序列。
a = 'IGADKYFHARGNYDAA'
b = 'KGADKYFHARGNYEAA'

counter = 0
differences = 0
for i in a:
    if i != b[counter]:
        differences += 1
    counter += 1

在这里,每当我们遇到序列a中与序列b相同位置的字母不同时,我们将“差异”加1。然后,在移动到下一个字母之前,我们将计数器加1。

0
这里是我对比较两个字符串的类似问题所提出解决方案的答案,基于此处展示的解决方案: https://dev59.com/PGct5IYBdhLWcg3wQ7Qy#12226960
因为 itertools.izip 在 Python3 中不能使用,我找到的解决方案是简单地使用 zip 函数:https://dev59.com/oVwY5IYBdhLWcg3wwp84#32303142
用于比较两个字符串的函数:
def compare(string1, string2, no_match_c=' ', match_c='|'):
    if len(string2) < len(string1):
        string1, string2 = string2, string1
    result = ''
    n_diff = 0
    for c1, c2 in zip(string1, string2):
        if c1 == c2:
            result += match_c
        else:
            result += no_match_c
            n_diff += 1
    delta = len(string2) - len(string1)
    result += delta * no_match_c
    n_diff += delta
    return (result, n_diff)

设置两个字符串进行比较并调用函数:

def main():
    string1 = 'AAUAAA'
    string2 = 'AAUCAA'
    result, n_diff = compare(string1, string2, no_match_c='_')
    print("%d difference(s)." % n_diff)
    print(string1)
    print(result)
    print(string2)

main()

这将返回:

1 difference(s).
AAUAAA
|||_||
AAUCAA

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接