In [24]:
from functools import lru_cache
import operator
from itertools import count, zip_longest, chain
from collections import OrderedDict
from random import randint
In [2]:
def memo_holder(optimizer):
def f(*args, **kwds):
return_memo_table = kwds.pop('memo_table', False)
pair = optimized, memo_table = optimizer(*args, **kwds)
return pair if return_memo_table else optimized
return f
Have a look at https://en.wikipedia.org/wiki/Monge_array
In [3]:
def parity_numbered_rows(matrix, parity, include_index=False):
start = 0 if parity == 'even' else 1
return [(i, r) if include_index else r
for i in range(start, len(matrix), 2)
for r in [matrix[i]]]
def argmin(iterable, only_index=True):
index, minimum = index_min = min(enumerate(iterable), key=operator.itemgetter(1))
return index if only_index else index_min
def interleaving(one, another):
for o, a in zip_longest(one, another):
yield o
if a: yield a
def is_sorted(iterable, pred=lambda l, g: l <= g):
_, *rest = iterable
return all(pred(l, g) for l, g in zip(iterable, rest))
def minima_indexes(matrix):
if len(matrix) == 1: return [argmin(matrix.pop())]
recursion = minima_indexes(parity_numbered_rows(matrix, parity='even'))
even_minima = OrderedDict((i, m) for i, m in zip(count(start=0, step=2), recursion))
odd_minima = [argmin(odd_r[start:end]) + start
for o, odd_r in parity_numbered_rows(matrix, parity='odd', include_index=True)
for start in [even_minima[o-1]]
for end in [even_minima[o+1]+1 if o+1 in even_minima else None]]
return list(interleaving(even_minima.values(), odd_minima))
def minima(matrix):
return [matrix[i][m] for i, m in enumerate(minima_indexes(matrix))]
def is_not_monge(matrix):
return any(any(matrix[r][m] > matrix[r][i] for i in range(m))
for r, m in enumerate(minima_indexes(matrix)))
The following is a Monge array:
In [70]:
matrix = [
[10, 17, 13, 28, 23],
[17, 22, 16, 29, 23],
[24, 28, 22, 34, 24],
[11, 13, 6, 17, 7],
[45, 44, 32, 37, 23],
[36, 33, 19, 21, 6],
[75, 66, 51, 53, 34],
]
minima(matrix)
Out[70]:
In [71]:
minima_indexes(matrix)
Out[71]:
In [72]:
is_not_monge(matrix)
Out[72]:
The following is not a Monge array:
In [73]:
matrix = [
[37, 23, 22, 32],
[21, 6, 7, 10],
[53, 34, 30, 31],
[32, 13, 9, 6],
[43, 21, 15, 8],
]
minima(matrix) # produces a wrong answer!!!
Out[73]:
In [74]:
minima_indexes(matrix)
Out[74]:
In [75]:
is_not_monge(matrix)
Out[75]:
In [35]:
@memo_holder
def longest_increasing_subsequence(seq):
L = []
for i, current in enumerate(seq):
"""opt, arg = max([(l, j) for (l, j) in L[:i] if l[-1] < current],
key=lambda p: len(p[0]),
default=([], tuple()))
L.append(opt + [current], (arg, i))"""
L.append(max(filter(lambda prefix: prefix[-1] < current, L[:i]), key=len, default=[]) + [current])
return max(L, key=len), L
def lis_rec(seq):
@lru_cache(maxsize=None)
def rec(i):
current = seq[i]
return max([rec(j) for j in range(i) if seq[j] < current], key=len, default=[]) + [current]
return max([rec(i) for i, _ in enumerate(seq)], key=len)
a simple test case taken from page 157:
In [36]:
seq = [5, 2, 8, 6, 3, 6, 9, 7] # see page 157
In [37]:
subseq, memo_table = longest_increasing_subsequence(seq, memo_table=True)
In [38]:
subseq
Out[38]:
memoization table shows that [2,3,6,7]
is another solution:
In [39]:
memo_table
Out[39]:
In [40]:
lis_rec(seq)
Out[40]:
The following is an average case where the sequence is generated randomly:
In [41]:
length = int(5e3)
seq = [randint(0, length) for _ in range(length)]
In [42]:
%timeit longest_increasing_subsequence(seq)
In [43]:
%timeit lis_rec(seq)
worst scenario where the sequence is a sorted list, in increasing order:
In [44]:
seq = range(length)
In [45]:
%timeit longest_increasing_subsequence(seq)
In [46]:
%timeit lis_rec(seq)
In [26]:
@memo_holder
def edit_distance(xs, ys,
gap_in_xs=lambda y: 1, # cost of putting a gap in `xs` when reading `y`
gap_in_ys=lambda x: 1, # cost of putting a gap in `ys` when reading `x`
mismatch=lambda x, y: 1, # cost of mismatch (x, y) in the sense of `==`
gap = '▢',
mark=lambda s: s.swapcase(),
reduce=sum):
T = {}
T.update({ (i, 0):(xs[:i], gap * i, i) for i in range(len(xs)+1) })
T.update({ (0, j):( gap * j,ys[:j], j) for j in range(len(ys)+1) })
def combine(w, z):
a, b, c = zip(w, z)
return ''.join(a), ''.join(b), reduce(c)
for i, x in enumerate(xs, start=1):
for j, y in enumerate(ys, start=1):
T[i, j] = min(combine(T[i-1, j], (x, gap, gap_in_ys(x))),
combine(T[i, j-1], (gap, y, gap_in_xs(y))),
combine(T[i-1, j-1], (x, y, 0) if x == y else (mark(x), mark(y), mismatch(x, y))),
key=lambda t: t[2])
return T[len(xs), len(ys)], T
In [21]:
(xs, ys, cost), memo_table = edit_distance('exponential', 'polynomial', memo_table=True)
In [22]:
print('edit with cost {}:\n\n{}\n{}'.format(cost, xs, ys))
In [23]:
memo_table
Out[23]:
In [49]:
(xs, ys, cost), memo_table = edit_distance('exponential', 'polynomial', memo_table=True, mismatch=lambda x,y: 10)
In [50]:
print('edit with cost {}:\n\n{}\n{}'.format(cost, xs, ys))
In [51]:
memo_table
Out[51]:
In [49]:
@memo_holder
def matrix_product_ordering(o, w, op):
n = len(o)
T = {(i,i):(lambda: o[i], 0) for i in range(n)}
def combine(i,r,j):
t_ir, c_ir = T[i, r]
t_rj, c_rj = T[r+1, j]
return (lambda: op(t_ir(), t_rj()), c_ir + c_rj + w(i, r+1, j+1)) # w[i]*w[r+1]*w[j+1])
for d in range(1, n):
for i in range(n-d):
j = i + d
T[i, j] = min([combine(i, r, j) for r in range(i, j)], key=lambda t: t[-1])
opt, cost = T[0, n-1]
return (opt(), cost), T
def parens_str_proxy(w, **kwds):
return matrix_product_ordering(o=['▢']*(len(w)-1),
w=lambda l, c, r: w[l]*w[c]*w[r],
op=lambda a, b: '({} {})'.format(a, b),
**kwds)
In [50]:
(opt, cost), memo_table = parens_str_proxy(w={0:100, 1:20, 2:1000, 3:2, 4:50}, memo_table=True)
In [51]:
opt, cost
Out[51]:
In [52]:
{k:(thunk(), cost) for k,(thunk, cost) in memo_table.items()}
Out[52]:
In [53]:
(opt, cost), memo_table = parens_str_proxy(w={i:(i+1) for i in range(10)}, memo_table=True)
In [54]:
opt, cost
Out[54]:
In [55]:
{k:(thunk(), cost) for k,(thunk, cost) in memo_table.items()}
Out[55]:
In [56]:
from sympy import fibonacci, Matrix, init_printing
init_printing()
In [57]:
(opt, cost), memo_table = parens_str_proxy(w={i:fibonacci(i+1) for i in range(10)}, memo_table=True)
In [58]:
opt, cost
Out[58]:
In [59]:
{k:(thunk(), cost) for k,(thunk, cost) in memo_table.items()}
Out[59]:
In [60]:
def to_matrix_cost(dim, memo_table):
n, m = dim
return Matrix(n, m, lambda n,k: memo_table.get((n, k), (lambda: None, 0))[-1])
In [61]:
to_matrix_cost(dim=(9,9), memo_table=memo_table)
Out[61]:
In [211]:
@memo_holder
def longest_common_subsequence(A, B, gap_A, gap_B,
equal=lambda a, b: 1, shrink_A=lambda a: 0, shrink_B=lambda b: 0,
reduce=sum):
T = {}
T.update({(i, 0):([gap_A]*i, 0) for i in range(len(A)+1)})
T.update({(0, j):([gap_B]*j, 0) for j in range(len(B)+1)})
def combine(w, z):
alpha, beta = zip(w, z)
return list(chain.from_iterable(alpha)), reduce(beta)
for i, a in enumerate(A, start=1):
for j, b in enumerate(B, start=1):
T[i, j] = combine(T[i-1, j-1], ([a], equal(a, b))) if a == b else max(
combine(T[i, j-1], ([gap_B], -shrink_B(b))),
combine(T[i-1, j], ([gap_A], -shrink_A(a))),
key=lambda t: t[-1])
opt, cost = T[len(A), len(B)]
return (opt, cost), T
def pprint_memo_table(T, joiner, do=str):
return {k:(joiner.join(map(do, v[0])), v[1]) for k, v in T.items()}
In [224]:
(opt, cost), memo_table = longest_common_subsequence(A='ADCAAB',
B='BAABDCDCAACACBA', gap_A='▢', gap_B='○',
#shrink_B=lambda b:1,
memo_table=True)
In [225]:
print('BAABDCDCAACACBA')
print(''.join(opt))
In [226]:
pprint_memo_table(memo_table, joiner='')
Out[226]:
In [215]:
(opt, cost), memo_table = longest_common_subsequence(A=[0,1,1,2,3,5,8,13,21,34,55],
B=[1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440,],
gap_A='▢', gap_B='○',
#shrink_B=lambda b:1,
memo_table=True)
In [216]:
print(','.join(map(str, opt)))
In [217]:
pprint_memo_table(memo_table, joiner=',')
Out[217]:
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.