difflib не может правильно найти коды операций

1

Я столкнулся с очень странной проблемой в библиотеке difflib в python. У меня две строки следующим образом, и я запускаю get_opcodes на них следующим образом:

import difflib

str1 = "MatrixElement(MatrixSymbol('Btd', Integer(11), Integer(11)), Integer(0), Integer(9))), Mul(Float('1.0', precision=24), MatrixElement(MatrixSymbol('Btd', Integer(11), Integer(11)), Integer(0), Integer(10))))"
str2 = "MatrixElement(MatrixSymbol('Btd', Integer(11), Integer(11)), Integer(1), Integer(9))), Mul(Float('1.0', precision=24), MatrixElement(MatrixSymbol('Btd', Integer(11), Integer(11)), Integer(1), Integer(10))))"
difflib.SequenceMatcher(None, str1,str2).get_opcodes()

Только в этом конкретном примере вывод diff выглядит следующим образом, что, очевидно, неверно.

[('equal', 0, 69, 0, 69),
 ('replace', 69, 70, 69, 70),
 ('equal', 70, 188, 70, 188),
 ('insert', 188, 188, 188, 201),
 ('equal', 188, 190, 201, 203),
 ('replace', 190, 206, 203, 206)]

Правильный вывод не должен содержать код операции insert, поскольку ничего нового не добавлено.

Является ли это потенциально ошибкой в difflib?

Теги:
difflib

1 ответ

2
Лучший ответ

Это не ошибка. Существует несколько способов преобразования последовательности в другую, то один вывод difflib здесь правильный.

Хотя, вы правы, чтобы задаться вопросом, почему difflib выбрал это нечетное преобразование вместо этого:

[('equal', 0, 69, 0, 69),
 ('replace', 69, 70, 69, 70),
 ('equal', 70, 188, 70, 188),
 ('replace', 188, 189, 188, 189),
 ('equal', 189, 206, 189, 206)]

Это сводится к одному: autojunk=True

Приготовьтесь узнать о junk !

Основной алгоритм построения кодов операций происходит из SequenceMatcher.get_matching_blocks, этот метод разбивает предоставленные последовательности на соответствующие подпоследовательности.

Чтобы сделать это эффективно, он сначала разбирает str2 и строит dict где ключи являются символами последовательности, а значения - это списки индексов соответствующего символа.

Хотя это может быть очень насыщенным памятью, и, таким образом, по умолчанию difflib.SequenceMatcher будет рассматривать некоторые повторяющиеся символы как нежелательные и не хранить свои индексы.

Из документа difflib:

Автоматическая эвристика нежелательной почты: [...] Если дубликаты элементов (после первого) составляют более 1% последовательности, а последовательность составляет не менее 200 наименований, этот элемент обозначается как "популярный" и обрабатывается как мусор с целью согласования последовательности. [...]

В вашем конкретном случае виновником является символ ( который рассматривается как "мусор". Объект SequenceMatcher не может видеть совпадающую последовательность, начинающуюся с индекса 189, потому что это (.

Обработка мусора

Самый простой способ получить ожидаемый результат - установить autojunk=False.

difflib.SequenceMatcher(None, str1, str2, autojunk=False).get_opcodes()

Это выводит то, что вы ожидали:

[('equal', 0, 69, 0, 69),
 ('replace', 69, 70, 69, 70),
 ('equal', 70, 188, 70, 188),
 ('replace', 188, 189, 188, 189),
 ('equal', 189, 206, 189, 206)]

Хотя, обратите внимание, что иногда отключение autojunk полностью не может быть лучшим вариантом, поскольку он, скорее всего, будет потреблять больше памяти и времени. Лучшим подходом было бы указать, что считается нежелательным.

[...] эти "мусорные" элементы - это те, которые в некотором смысле неинтересны, например пустые строки или пробелы [...]

Это особенно актуально, когда вы используете difflib.ratio чтобы получить меру сходства между последовательностями. В этом случае вы можете игнорировать пробелы, поскольку они, как правило, неинтересны в отношении текстового сопоставления.

Таким образом, если вы отключите autojunk, вы все равно можете предоставить функцию isjunk которая будет указывать на игнорирование, скажем, пробелов. Этот аргумент - это тот, который вы задали как None в вашем примере.

import difflib
from string import whitespace

...

difflib.SequenceMatcher(lambda x: x in whitespace, str1, str2, autojunk=False)
  • 0
    Хорошее объяснение! Большое спасибо.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню