Я столкнулся с очень странной проблемой в библиотеке 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
здесь правильный.
Хотя, вы правы, чтобы задаться вопросом, почему 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
будет рассматривать некоторые повторяющиеся символы как нежелательные и не хранить свои индексы.
Автоматическая эвристика нежелательной почты: [...] Если дубликаты элементов (после первого) составляют более 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)