Возможно ли, что SequenceMatcher в difflib Python может обеспечить более эффективный способ вычисления расстояния Левенштейна?

Вот хрестоматийный пример общего алгоритма расчета расстояния Левенштейна (я взял его с веб-сайта Магнуса Хетланда):

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, которая использует SequenceManager difflib. Поигравшись с ним, вот что у меня получилось:

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:

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

А вот стандартная реализация на чистом питоне:

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

Действительно ли производительность алгоритма с использованием SequenceMatcher difflib лучше? Или он полагается на библиотеку C, которая полностью делает сравнение недействительным? Если он полагается на расширения C, как я могу сказать, глядя на реализацию difflib.py?

Использование Python 2.7.3 [GCC 4.2.1 (Apple Inc., сборка 5666)]

Заранее спасибо за вашу помощь!


person damzam    schedule 30.09.2012    source источник
comment
Исходный код для SequenceMatcher не слишком длинный. Просто пролистай.   -  person Blender    schedule 30.09.2012
comment
@Blender Я сделал ... единственные вещи, которые, казалось, были реализованы в C, были deque и dict по умолчанию из модели коллекций. Но не похоже, чтобы кто-то из них использовался для Sequence Matcher. При этом я немного не в себе, пытаясь понять, как используются расширения C.   -  person damzam    schedule 30.09.2012
comment
Кажется (из документации SequenceMatcher), что алгоритм, который использует SequenceMatcher, не гарантирует создание минимального количества правок, а более интуитивно понятный набор правок. Левенштейн склоняется в противоположную сторону. Пробовали ли вы сгенерировать много пар длинных случайных строк и передать их в качестве входных данных для ваших двух подпрограмм? Это может быть лучшей стратегией тестирования.   -  person Sam Mussmann    schedule 01.10.2012
comment
@SamMussmann Моя стратегия тестирования была явно неадекватной. Бывают случаи, когда результаты неверны.   -  person damzam    schedule 03.10.2012


Ответы (1)


person    schedule
comment
Спасибо Гарет. Я должен был более тщательно проверить правильность, прежде чем публиковать и запускать тесты производительности. - person damzam; 03.10.2012