【问题标题】:Is it possible that the SequenceMatcher in Python's difflib could provide a more efficient way to calculate Levenshtein distance?Python difflib 中的 SequenceMatcher 是否有可能提供一种更有效的方法来计算 Levenshtein 距离?
【发布时间】:2012-09-30 06:30:16
【问题描述】:

这是计算 Levenshtein 距离的通用算法的教科书示例(我从 Magnus Hetland's webite 中提取):

def levenshtein(a,b):
    "Calculates the Levenshtein distance between a and b."
    n, m = len(a), len(b)
    if n > m:
        # Make sure n <= m, to use O(min(n,m)) space
        a,b = b,a
        n,m = m,n

    current = range(n+1)
    for i in range(1,m+1):
        previous, current = current, [i]+[0]*n
        for j in range(1,n+1):
            add, delete = previous[j]+1, current[j-1]+1
            change = previous[j-1]
            if a[j-1] != b[i-1]:
                change = change + 1
            current[j] = min(add, delete, change)

    return current[n]

不过,我想知道是否有更高效(并且可能更优雅)的纯 Python 实现,它使用 difflib 的 SequenceManager。在玩了它之后,这就是我想出的:

from difflib import SequenceMatcher as sm

def lev_using_difflib(s1, s2):
    a = b = size = distance = 0
    for m in sm(a=s1, b=s2).get_matching_blocks():
        distance += max(m.a-a, m.b-b) - size
        a, b, size = m
    return distance

我想不出一个失败的测试用例,而且性能似乎明显优于标准算法。

以下是依赖 difflib 的 levenshtein 算法的结果:

>>> from timeit import Timer
>>> setup = """
... from difflib import SequenceMatcher as sm
... 
... def lev_using_difflib(s1, s2):
...     a = b = size = distance = 0
...     for m in sm(a=s1, b=s2).get_matching_blocks():
...         distance += max(m.a-a, m.b-b) - size
...         a, b, size = m
...     return distance
... 
... strings = [('sunday','saturday'),
...            ('fitting','babysitting'),
...            ('rosettacode','raisethysword')]
... """
>>> stmt = """
... for s in strings:
...     lev_using_difflib(*s)
... """
>>> Timer(stmt, setup).timeit(100000)
36.989389181137085

这是标准的纯python实现:

>>> from timeit import Timer
>>> setup2 = """
... def levenshtein(a,b):
...     n, m = len(a), len(b)
...     if n > m:
...         a,b = b,a
...         n,m = m,n
... 
...     current = range(n+1)
...     for i in range(1,m+1):
...         previous, current = current, [i]+[0]*n
...         for j in range(1,n+1):
...             add, delete = previous[j]+1, current[j-1]+1
...             change = previous[j-1]
...             if a[j-1] != b[i-1]:
...                 change = change + 1
...             current[j] = min(add, delete, change)
... 
...     return current[n]
... 
... strings = [('sunday','saturday'),
...            ('fitting','babysitting'),
...            ('rosettacode','raisethysword')]
... """
>>> stmt2 = """
... for s in strings:
...     levenshtein(*s)
... """
>>> Timer(stmt2, setup2).timeit(100000)
55.594768047332764

使用 difflib 的 SequenceMatcher 算法的性能真的更好吗?或者它是否依赖于完全使比较无效的 C 库?如果它依赖于 C 扩展,我如何通过查看 difflib.py 实现来判断?

使用 Python 2.7.3 [GCC 4.2.1 (Apple Inc. build 5666)]

提前感谢您的帮助!

【问题讨论】:

  • SequenceMatcher 的来源不太长。略读即可。
  • @Blender 我做了......这些似乎在 C 中实现的唯一东西是集合模型中的双端队列和默认字典。但看起来其中任何一个都没有被用于序列匹配器。话虽如此,我在试图理解 C 扩展是如何使用的方面有点脱离我的元素。
  • 似乎(来自 SequenceMatcher 文档)SequenceMatcher 使用的算法不能保证生成最少数量的编辑,而是一组更“直观”的编辑。 Levenshtein 则相反。您是否尝试过生成多对长随机字符串并将它们作为输入提供给您的两个例程?这可能是一个更好的测试策略。
  • @SamMussmann 我的测试策略显然是不够的。有结果不正确的情况。

标签: python performance algorithm levenshtein-distance difflib


【解决方案1】:
>>> levenshtein('hello', 'world')
4
>>> lev_using_difflib('hello', 'world')
5

【讨论】:

  • 谢谢加雷斯。在发布和运行性能测试之前,我应该更彻底地测试正确性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-20
  • 2023-03-18
  • 1970-01-01
  • 2019-08-20
  • 2014-10-30
  • 1970-01-01
相关资源
最近更新 更多