Сообщить об ошибке.

Класс SequenceMatcher() модуля difflib, сходство строк в Python

Вычисление сходства двух последовательностей

Синтаксис:

import difflib

difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True)

Параметры:

  • isjunk=None - функция, которая фильтрует мусорные элементы,
  • a, b - сравниваемые последовательности,
  • autojunk=True - отключение автоматической эвристики мусора.

Описание:

Конструктор класса SequenceMatcher() модуля difflib имеет необязательный аргумент isjunk должен быть None или функцией с одним аргументом, которая принимает элемент последовательности и возвращает True тогда и только тогда, когда элемент является “мусорным” и должен игнорироваться. Передача None для isjunk эквивалентна передаче lambda x: False, другими словами, никакие элементы не игнорируются.

Например если сравнивать строки как последовательности символов и при этом не надо учитывать пробелы или табуляции, то можно передать следующую лямбда-функцию:

lambda x: x in " \t"

Необязательные аргументы a и b - это сравниваемые последовательности. Обе по умолчанию пустые строки. Элементы обеих последовательностей должны быть хешируемыми.

Необязательный аргумент autojunk можно использовать для отключения автоматической эвристики мусора.

Объекты SequenceMatcher получают три атрибута данных:

  • bjunk - набор элементов b, для которых Isjunk=True
  • bpopular - набор не-мусорных элементов, считающихся популярными эвристикой, если она не отключена
  • b2j - словарь, отображающий остальные элементы b в список позиций, где они встречаются.

Все три атрибута данных сбрасываются всякий раз, когда b сбрасывается с помощью методов set_seqs() или set_seq2(). Описание этих методов смотрите ниже.

Объекты класса SequenceMatcher() имеют следующие методы:

set_seqs(a, b):

Метод set_seqs(a, b) устанавливает две последовательности a, b для сравнения.

Класс SequenceMatcher() вычисляет и кэширует подробную информацию о второй b последовательности, поэтому, если нужно сравнить одну последовательность с разными последовательностями, используйте метод set_seq2(), чтобы задать последовательность один раз и вызвать метод set_seq1() повторно для сравнения с каждой новой последовательностью.

set_seq1(a):

Метод set_seq1(a) устанавливает первую последовательность a для сравнения. Вторая сравниваемая последовательность b не изменяется.

set_seq2(b):

Метод set_seq2(a) устанавливает вторую последовательность b для сравнения. Первая сравниваемая последовательность a не изменяется.

find_longest_match(alo, ahi, blo, bhi):

Метод find_longest_match(alo, ahi, blo, bhi) найдет самый длинный соответствующий блок в a[alo:ahi] и b[blo:bhi].

Если isjunk был опущен или None, то метод find_longest_match() возвращает (i, j, k) такой, что a[i:i+k] равна b[j:j+k], где alo <= i <= i+k <= ahi и blo <= j <= j+k <= bhi. Для всех (i', j', k'), удовлетворяющих этим условиям, также выполняются дополнительные условия k >= k', i <= i', и если i == i', j <= j'. Другими словами, из всех совпадающих блоков вернется тот, который встретится раньше всего в последовательности a, а из всех совпадающих блоков, которые встречаются раньше всего в а, вернется тот, который встретится раньше всего в b.

>>> import difflib as df
>>> s = df.SequenceMatcher(None, " abcd", "abcd abcd")
>>> s.find_longest_match(0, 5, 0, 9)
# Match(a=0, b=4, size=5)

Если был задан isjunk, то сначала определяется самый длинный совпадающий блок, как указано выше, но с ограничением, что в блоке не появляется "мусорный" элемент. Затем этот блок расширяется настолько, насколько это возможно, путем сопоставления только нежелательных элементов с обеих сторон. Таким образом, результирующий блок никогда не совпадает с мусором, за исключением того, что идентичный мусор оказывается рядом с совпадением.

Вот тот же пример, что и раньше, но с учетом того, что пробелы являются ненужными. Это препятствует непосредственному сопоставлению "abcd" с "abcd" в хвостовой части второй последовательности. Вместо этого только "abcd" может соответствовать, и соответствует самому левому "abcd" во второй последовательности:

>>> import difflib as df
>>> s = df.SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")
>>> s.find_longest_match(0, 5, 0, 9)
# Match(a=1, b=0, size=4)

Если блоки не совпадают, это возвращает (alo, blo, 0).

get_matching_blocks():

Метод get_matching_blocks() возвращает список тройных кортежей, которые описывают непересекающиеся совпадающие подпоследовательности. Каждая тройка имеет вид (i, j, n) и означает, что a[i:i+n] == b[j:j+n]. Тройки монотонно увеличиваются в i и j.

Последний тройной кортеж является фиктивным и имеет значение (len(a), len(b), 0). Это единственный кортеж с n == 0. Если (i, j, n) и (i', j', n') являются смежными кортежами в списке, а второй не является последним кортежем в списке, то i+n < i' или j+n < j'. Другими словами, смежные тройные кортежи всегда описывают несмежные равные блоки.

>>> import difflib as df
>>> s = df.SequenceMatcher(None, "abxcd", "abcd")
>>> s.get_matching_blocks()
# [
# Match(a=0, b=0, size=2), 
# Match(a=3, b=2, size=2), 
# Match(a=5, b=4, size=0)
# ]
get_opcodes():

Метод get_opcodes() возвращает список из кортежей с 5-ю элементами, которые описывают как получить последовательность a из последовательности b. Каждый кортеж имеет форму (tag, i1, i2, j1, j2). Первый кортеж имеет i1 == j1 == 0, а остальные кортежи имеют i1, равное i2 из предыдущего кортежа, а также j1, равное предыдущему j2.

Значения тегов tag - это строки, имеющие следующие значения:

  • 'replace' - a[i1:i2] следует заменить на b[j1:j2].
  • 'delete' - a[i1:i2] должны быть удалены. Обратите внимание, что в этом случае j1 == j2.
  • 'insert' - b[j1:j2] должен быть вставлен в a[i1:i1]. Обратите внимание, что в этом случае i1 == i2.
  • 'equal' - a[i1:i2] == b[j1:j2] - подпоследовательности равны.
>>> import difflib as df
>>> a = "qabxcd"
>>> b = "abycdf"
>>> s = df.SequenceMatcher(None, a, b)
>>> for tag, i1, i2, j1, j2 in s.get_opcodes():
...     print('{:7}   a[{}:{}] --> b[{}:{}] {!r:>8} --> {!r}'.format(
...         tag, i1, i2, j1, j2, a[i1:i2], b[j1:j2]))
delete    a[0:1] --> b[0:0]      'q' --> ''
equal     a[1:3] --> b[0:2]     'ab' --> 'ab'
replace   a[3:4] --> b[2:3]      'x' --> 'y'
equal     a[4:6] --> b[3:5]     'cd' --> 'cd'
insert    a[6:6] --> b[5:6]       '' --> 'f'
get_grouped_opcodes(n=3):

Метод get_grouped_opcodes() возвращает генератор групп с числом строк контекста до n.

Начиная с групп, возвращаемых get_opcodes(), этот метод разбивает меньшие кластеры изменений и устраняет промежуточные диапазоны, которые не имеют изменений.

Группы возвращаются в том же формате, что и get_opcodes().

ratio():

Метод ratio() возвращает меру подобия/схожести последовательностей в виде числа с плавающей точкой в диапазоне [0, 1].

Где T - общее число элементов в обеих последовательностях, а M - число совпадений. Мера подобия последовательностей это 2.0*M/T. Обратите внимание, что мера подобия равная 1.0 будет, если последовательности идентичны, и 0.0, если они не имеют ничего общего.

Меру подобия/схожести вычислять дорого, если методы get_matching_blocks() или get_opcodes() еще не были вызваны. В этом случае нужно сначала вызвать методы quick_ratio() или real_quick_ratio(), чтобы получить верхнюю границу.

Важно: результат вызова метода ratio() может зависеть от порядка аргументов. Например:

>>> import difflib as df
>>> df.SequenceMatcher(None, 'tide', 'diet').ratio()
# 0.25
>>> df.SequenceMatcher(None, 'diet', 'tide').ratio()
# 0.5
quick_ratio():

Метод quick_ratio() относительно быстро возвращает верхнюю границу соотношения ratio().

real_quick_ratio():

Метод real_quick_ratio() очень быстро возвращает верхнюю границу соотношения ratio(), но с большей погрешностью.

Три метода, которые возвращают отношение соответствия к общему количеству символов, могут давать разные результаты из-за разных уровней аппроксимации, хотя методы quick_ratio() и real_quick_ratio() так же хороши, как и ratio():

>>> import difflib as df
>>> s = df.SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
# 0.75
>>> s.quick_ratio()
# 0.75
>>> s.real_quick_ratio()
# 1.0

Примеры использования SequenceMatcher():

В этом примере сравниваются две строки, считая пробелы ненужными:

import difflib as df

s = df.SequenceMatcher(lambda x: x == " ",
                    "private Thread currentThread;",
                    "private volatile Thread currentThread;")

Метод ratio() возвращает число с плавающей точкой в ​​[0, 1], измеряя сходство последовательностей. Как правило, значение ratio() выше 0,6 означает, что последовательности близки:

print(round(s.ratio(), 3))
# 0.866

Если нужно только совпадение последовательностей, удобно использовать метод get_matching_blocks():

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

Обратите внимание, что последний кортеж, возвращаемый get_matching_blocks(), всегда является фиктивным, (len(a), len(b), 0) и это единственный случай, когда в последнем кортеже число совпадающих элементов равно 0.

Если надо узнать, как изменить первую последовательность, что бы получить вторую, используйте метод get_opcodes():

for opcode in s.get_opcodes():
    print("%6s a[%d:%d] b[%d:%d]" % opcode)

# equal a[0:8] b[0:8]
# insert a[8:8] b[8:17]
# equal a[8:29] b[17:38]

Интересные заметки о SequenceMatcher.

  • Алгоритм SequenceMatcher:

    Базовый алгоритм SequenceMatcher() появился раньше и немного сложнее, чем алгоритм, опубликованный в конце 1980-х годов Ратклиффом и Обершелпом под гиперболическим названием "сопоставление с образцом гештальта". Идея состоит в том, чтобы найти самую длинную непрерывную совпадающую подпоследовательность, которая не содержит "мусорных" элементов. "Ненужные" элементы - это те элементы, которые в некотором смысле неинтересны, такие как пустые строки или пробелы. Обработка мусора является расширением алгоритма Ratcliff и Obershelp. Затем эта же идея рекурсивно применяется к фрагментам последовательностей слева и справа от совпадающей подпоследовательности.

  • Время:

    Основной алгоритм Ratcliff-Obershelp - это кубическое время в худшем случае и квадратичное время в ожидаемом случае. SequenceMatcher() является квадратичным временем для наихудшего случая и имеет поведение ожидаемого случая, сложным образом зависящее от того, сколько элементов имеют общие последовательности, лучшее время линейно.

  • Автоматическая эвристика мусора:

    SequenceMatcher поддерживает эвристику, которая автоматически обрабатывает определенные элементы последовательности как нежелательные. Эвристика подсчитывает, сколько раз каждый отдельный элемент появляется в последовательности. Если дубликаты элемента составляют более 1% последовательности, а длина последовательности составляет не менее 200 элементов, этот элемент помечается как "популярный" и рассматривается как нежелательный с целью сопоставления последовательности. Эту эвристику можно отключить, задав аргумент autojunk=False.