Как можно применить memoization к этому алгоритму?
После того, как класс difflib.SequenceMatcher
в стандартной библиотеке Python был непригоден для моих нужд, для решения проблемного пространства был написан общий "diff" -инг-модуль. После нескольких месяцев, чтобы больше думать о том, что он делает, рекурсивный алгоритм, похоже, ищет больше, чем нужно, путем повторного поиска тех же областей в последовательности, что и отдельный "поток поиска" также может быть рассмотрен.
Цель модуля diff
- вычислить разницу и сходство между парой последовательностей (list, tuple, string, bytes, bytearray и т.д.). Первоначальная версия была намного медленнее, чем текущая форма кода, увидев увеличение скорости в десять раз. Как можно применить memoization к следующему коду? Каков наилучший способ переписать алгоритм для дальнейшего увеличения любой возможной скорости?
class Slice:
__slots__ = 'prefix', 'root', 'suffix'
def __init__(self, prefix, root, suffix):
self.prefix = prefix
self.root = root
self.suffix = suffix
################################################################################
class Match:
__slots__ = 'a', 'b', 'prefix', 'suffix', 'value'
def __init__(self, a, b, prefix, suffix, value):
self.a = a
self.b = b
self.prefix = prefix
self.suffix = suffix
self.value = value
################################################################################
class Tree:
__slots__ = 'nodes', 'index', 'value'
def __init__(self, nodes, index, value):
self.nodes = nodes
self.index = index
self.value = value
################################################################################
def search(a, b):
# Initialize startup variables.
nodes, index = [], []
a_size, b_size = len(a), len(b)
# Begin to slice the sequences.
for size in range(min(a_size, b_size), 0, -1):
for a_addr in range(a_size - size + 1):
# Slice "a" at address and end.
a_term = a_addr + size
a_root = a[a_addr:a_term]
for b_addr in range(b_size - size + 1):
# Slice "b" at address and end.
b_term = b_addr + size
b_root = b[b_addr:b_term]
# Find out if slices are equal.
if a_root == b_root:
# Create prefix tree to search.
a_pref, b_pref = a[:a_addr], b[:b_addr]
p_tree = search(a_pref, b_pref)
# Create suffix tree to search.
a_suff, b_suff = a[a_term:], b[b_term:]
s_tree = search(a_suff, b_suff)
# Make completed slice objects.
a_slic = Slice(a_pref, a_root, a_suff)
b_slic = Slice(b_pref, b_root, b_suff)
# Finish the match calculation.
value = size + p_tree.value + s_tree.value
match = Match(a_slic, b_slic, p_tree, s_tree, value)
# Append results to tree lists.
nodes.append(match)
index.append(value)
# Return largest matches found.
if nodes:
return Tree(nodes, index, max(index))
# Give caller null tree object.
return Tree(nodes, index, 0)
Ссылка: Как оптимизировать рекурсивный алгоритм, чтобы не повторять себя?
Ответы
Ответ 1
Как сказал ~ unutbu, попробуйте memoized decorator и следующие изменения:
@memoized
def search(a, b):
# Initialize startup variables.
nodes, index = [], []
a_size, b_size = len(a), len(b)
# Begin to slice the sequences.
for size in range(min(a_size, b_size), 0, -1):
for a_addr in range(a_size - size + 1):
# Slice "a" at address and end.
a_term = a_addr + size
a_root = list(a)[a_addr:a_term] #change to list
for b_addr in range(b_size - size + 1):
# Slice "b" at address and end.
b_term = b_addr + size
b_root = list(b)[b_addr:b_term] #change to list
# Find out if slices are equal.
if a_root == b_root:
# Create prefix tree to search.
a_pref, b_pref = list(a)[:a_addr], list(b)[:b_addr]
p_tree = search(a_pref, b_pref)
# Create suffix tree to search.
a_suff, b_suff = list(a)[a_term:], list(b)[b_term:]
s_tree = search(a_suff, b_suff)
# Make completed slice objects.
a_slic = Slice(a_pref, a_root, a_suff)
b_slic = Slice(b_pref, b_root, b_suff)
# Finish the match calculation.
value = size + p_tree.value + s_tree.value
match = Match(a_slic, b_slic, p_tree, s_tree, value)
# Append results to tree lists.
nodes.append(match)
index.append(value)
# Return largest matches found.
if nodes:
return Tree(nodes, index, max(index))
# Give caller null tree object.
return Tree(nodes, index, 0)
Для memoization словари являются лучшими, но их нельзя нарезать, поэтому их нужно изменить на списки, как указано в комментариях выше.
Ответ 2
Вы можете использовать декоратор memoize из Python Decorator Library
и используйте его следующим образом:
@memoized
def search(a, b):
При первом вызове search
с аргументами a,b
результат вычисляется и запоминается (сохраняется в кеше). Второй раз search
вызывается с теми же аргументами, результат возвращается из кеша.
Обратите внимание, что для работы декоратора memoized
аргументы должны быть хешируемыми. Если a
и b
являются кортежами чисел, то они хешируются. Если они являются списками, вы можете преобразовать их в кортежи, прежде чем передавать их на search
.
Это не похоже на то, что search
принимает dicts
в качестве аргументов, но если бы они были, то они не были бы хешируемыми, а декодер memoization не смог бы сохранить результат в кеше.
Ответ 3
Прошло более 9 лет с тех пор, как был задан вопрос, но концепция внутреннего кэширования результатов для ускорения алгоритма была, наконец, применена к коду сегодня. Результаты этого приложения можно увидеть ниже:
#! /usr/bin/env python3
"""Compute differences and similarities between a pair of sequences.
After finding the "difflib.SequenceMatcher" class unsuitable, this module
was written and re-written several times into the polished version below."""
__author__ = 'Stephen "Zero" Chappell <[email protected]>'
__date__ = '3 September 2019'
__version__ = '$Revision: 4 $'
class Slice:
__slots__ = 'prefix', 'root', 'suffix'
def __init__(self, prefix, root, suffix):
self.prefix = prefix
self.root = root
self.suffix = suffix
class Match:
__slots__ = 'a', 'b', 'prefix', 'suffix', 'value'
def __init__(self, a, b, prefix, suffix, value):
self.a = a
self.b = b
self.prefix = prefix
self.suffix = suffix
self.value = value
class Tree:
__slots__ = 'nodes', 'index', 'value'
def __init__(self, nodes, index, value):
self.nodes = nodes
self.index = index
self.value = value
def search(a, b):
return _search(a, b, {})
def _search(a, b, memo):
# Initialize startup variables.
nodes, index = [], []
a_size, b_size = len(a), len(b)
# Begin to slice the sequences.
for size in range(min(a_size, b_size), 0, -1):
for a_addr in range(a_size - size + 1):
# Slice "a" at address and end.
a_term = a_addr + size
a_root = a[a_addr:a_term]
for b_addr in range(b_size - size + 1):
# Slice "b" at address and end.
b_term = b_addr + size
b_root = b[b_addr:b_term]
# Find out if slices are equal.
if a_root == b_root:
# Create prefix tree to search.
key = a_prefix, b_prefix = a[:a_addr], b[:b_addr]
if key not in memo:
memo[key] = _search(a_prefix, b_prefix, memo)
p_tree = memo[key]
# Create suffix tree to search.
key = a_suffix, b_suffix = a[a_term:], b[b_term:]
if key not in memo:
memo[key] = _search(a_suffix, b_suffix, memo)
s_tree = memo[key]
# Make completed slice objects.
a_slice = Slice(a_prefix, a_root, a_suffix)
b_slice = Slice(b_prefix, b_root, b_suffix)
# Finish the match calculation.
value = size + p_tree.value + s_tree.value
match = Match(a_slice, b_slice, p_tree, s_tree, value)
# Append results to tree lists.
nodes.append(match)
index.append(value)
# Return largest matches found.
if nodes:
return Tree(nodes, index, max(index))
# Give caller null tree object.
return Tree(nodes, index, 0)