У вас есть три основных решения: 1) написать собственную реализацию diff
; 2) взломать модуль difflib
; 3) найти обходной путь.
Ваша собственная реализация
В случае 1) вы можете посмотреть на этот вопрос
и прочитайте несколько книг, таких как CLRS или книги Роберта Седжвика.
Взломать модуль difflib
В случае 2) посмотрите исходный код : get_matching_blocks
, звонки find_longest_match
на , линия 479, В ядре find_longest_match
имеется словарь b2j
, который отображает элементы списка a
на их индексы в списке b
. Если вы перезапишите этот словарь, вы можете достичь того, что вы хотите. Вот стандартная версия:
>>> import difflib
>>> from difflib import SequenceMatcher
>>> list3 = ["orange","apple","lemons","grape"]
>>> list4 = ["pears", "oranges","apple", "lemon", "cherry", "grapes"]
>>> s = SequenceMatcher(None, list3, list4)
>>> s.get_matching_blocks()
[Match(a=1, b=2, size=1), Match(a=4, b=6, size=0)]
>>> [(b.a+i, b.b+i, list3[b.a+i], list4[b.b+i]) for b in s.get_matching_blocks() for i in range(b.size)]
[(1, 2, 'apple', 'apple')]
Вот взломанная версия:
>>> s = SequenceMatcher(None, list3, list4)
>>> s.b2j
{'pears': [0], 'oranges': [1], 'apple': [2], 'lemon': [3], 'cherry': [4], 'grapes': [5]}
>>> s.b2j = {**s.b2j, 'orange':s.b2j['oranges'], 'lemons':s.b2j['lemon'], 'grape':s.b2j['grapes']}
>>> s.b2j
{'pears': [0], 'oranges': [1], 'apple': [2], 'lemon': [3], 'cherry': [4], 'grapes': [5], 'orange': [1], 'lemons': [3], 'grape': [5]}
>>> s.get_matching_blocks()
[Match(a=0, b=1, size=3), Match(a=3, b=5, size=1), Match(a=4, b=6, size=0)]
>>> [(b.a+i, b.b+i, list3[b.a+i], list4[b.b+i]) for b in s.get_matching_blocks() for i in range(b.size)]
[(0, 1, 'orange', 'oranges'), (1, 2, 'apple', 'apple'), (2, 3, 'lemons', 'lemon'), (3, 5, 'grape', 'grapes')]
Это не сложно автоматизировать, но я бы не советовал вам это решение, поскольку существует очень простой обходной путь.
Обходной путь
Идея состоит в том, чтобы группировать слова по семьям:
families = [{"pears", "peras"}, {"orange", "oranges", "naranjas"}, {"apple", "manzana"}, {"lemons", "lemon", "limón"}, {"cherry", "cereza"}, {"grape", "grapes"}]
Теперь легко создать словарь, который переводит каждое слово в семействе в одно из этих слов (назовем его главным словом):
>>> d = {w:main for main, *alternatives in map(list, families) for w in alternatives}
>>> d
{'pears': 'peras', 'orange': 'naranjas', 'oranges': 'naranjas', 'manzana': 'apple', 'lemon': 'lemons', 'limón': 'lemons', 'cherry': 'cereza', 'grape': 'grapes'}
Обратите внимание, что main, *alternatives in map(list, families)
распаковывает семейство в основное слово (первое в списке) и список альтернатив, используя оператор звездочки:
>>> head, *tail = [1,2,3,4,5]
>>> head
1
>>> tail
[2, 3, 4, 5]
Затем вы можете преобразовать списки, чтобы использовать только основные слова:
>>> list3=["orange","apple","lemons","grape"]
>>> list4=["pears", "oranges","apple", "lemon", "cherry", "grapes"]
>>> list5=["peras", "naranjas", "manzana", "limón", "cereza", "uvas"]
>>> [d.get(w, w) for w in list3]
['naranjas', 'apple', 'limón', 'grapes']
>>> [d.get(w, w) for w in list4]
['peras', 'naranjas', 'apple', 'limón', 'cereza', 'grapes']
>>> [d.get(w, w) for w in list5]
['peras', 'naranjas', 'apple', 'limón', 'cereza', 'uvas']
Выражение d.get(w, w)
вернет d[w]
, если w
является ключом, иначе w
само по себе. Следовательно, слова, принадлежащие семье, преобразуются в основное слово этой семьи, а остальные слова остаются нетронутыми.
Эти списки легко сравнить с difflib
.
Важно: временная сложность преобразования списков незначительна по сравнению с алгоритмом сравнения, поэтому вы не должны видеть разницу.
Полный код
В качестве бонуса полный код:
def match_seq(list1, list2):
"""A generator that yields matches of list1 vs list2"""
s = SequenceMatcher(None, list1, list2)
for block in s.get_matching_blocks():
for i in range(block.size):
yield block.a + i, block.b + i # you don't need to store the matches, just yields them
def create_convert(*families):
"""Return a converter function that converts a list
to the same list with only main words"""
d = {w:main for main, *alternatives in map(list, families) for w in alternatives}
return lambda L: [d.get(w, w) for w in L]
families = [{"pears", "peras"}, {"orange", "oranges", "naranjas"}, {"apple", "manzana"}, {"lemons", "lemon", "limón"}, {"cherry", "cereza"}, {"grape", "grapes", "uvas"}]
convert = create_convert(*families)
list3=["orange","apple","lemons","grape"]
list4=["pears", "oranges","apple", "lemon", "cherry", "grapes"]
list5=["peras", "naranjas", "manzana", "limón", "cereza", "uvas"]
print ("list3 vs list4")
for a,b in match_seq(convert(list3), convert(list4)):
print(a,b, list3[a],list4[b])
# list3 vs list4
# 0 1 orange oranges
# 1 2 apple apple
# 2 3 lemons lemon
# 3 5 grape grapes
print ("list3 vs list5")
for a,b in match_seq(convert(list3), convert(list5)):
print(a,b, list3[a],list5[b])
# list3 vs list5
# 0 1 orange naranjas
# 1 2 apple manzana
# 2 3 lemons limón
# 3 5 grape uvas