Example code for recursion, dynamic programming (using memoization), min-cost path problem, and minimal voice leading. (Original code here)
import numpy as npdef fib(n):base cases
if n < 1:
return 0
if n in [1, 2]:
return 1iteration
a = 1
b = 1
for i in range(3, n + 1):
result = a + b
a = b
b = result
return resultdef fib_recurse(n):base cases
if n < 1:
return 0
if n in [1, 2]:
return 1recursive case
return fib_recurse(n-2) + fib_recurse(n-1)Notice that for n > 30 fib_recurse(n) is progressively and significantly
slower than fib(n).
memo = {}def fib_memo(n):base cases
if n < 1:
return 0
if n in [1, 2]:
return 1if problem already solved, find solution in the memo
try:
return memo[n]if problem is not in the memo, solve and memoize
except KeyError:
memo[n] = fib_memo(n-2) + fib_memo(n-1)
return memo[n]Test the timing of the three Fibonacci functions
import time
n = 35
start_time = time.time()
answer = fib(n)
end_time = time.time()
print('fib(%s) = %s' % (n, answer))
print('Answer found in', end_time - start_time, 'ms')
print()
start_time = time.time()
answer = fib_recurse(n)
end_time = time.time()
print('fib_recurse(%s) = %s' % (n, answer))
print('Answer found in', end_time - start_time, 'ms')
print()
start_time = time.time()
answer = fib_memo(n)
end_time = time.time()
print('fib_memo(%s) = %s' % (n, answer))
print('Answer found in', end_time - start_time, 'ms')
print()Given a cost matrix of shape (size) row by col, return the value of
the minimum cost path through the matrix.
def min_cost(cost, row, col):base cases
if row == 0 and col == 0:
return cost[row, col]
if row < 0 or col < 0:
return float('Inf')recursive case
result = cost[row][col] + min(min_cost(cost, row-1, col),
min_cost(cost, row, col-1),
min_cost(cost, row-1, col-1))
return resultmemo = {}Memoized version of min_cost function
def min_cost_memo(cost, row, col):Base cases
if row == 0 and col == 0:
return cost[0][0]
if row < 0 or col < 0:
return float('Inf')check memo
try:
return memo[(row, col)]recursive case
except KeyError:
result = cost[row][col] + min(min_cost_memo(cost, row, col-1),
min_cost_memo(cost, row-1, col-1),
min_cost_memo(cost, row-1, col))write result to memo
memo[(row, col)] = result
return result
M = np.array([[1, 2, 3],
[4, 8, 2],
[1, 5, 3]])the minimum cost is 8
print('For matrix `M` the minimum cost is', min_cost(M, 2, 2))
print()
N = 10"A random seed (or seed state, or just seed) is a number (or vector) used to initialize a pseudorandom number generator." – Wikipedia
np.random.seed(42)
random_matrix = np.random.random([N, N])
print('random_matrix is')
print(random_matrix)random_matrix is
[[ 0.37454012 0.95071431 0.73199394 0.59865848 0.15601864 0.15599452
0.05808361 0.86617615 0.60111501 0.70807258]
[ 0.02058449 0.96990985 0.83244264 0.21233911 0.18182497 0.18340451
0.30424224 0.52475643 0.43194502 0.29122914]
[ 0.61185289 0.13949386 0.29214465 0.36636184 0.45606998 0.78517596
0.19967378 0.51423444 0.59241457 0.04645041]
[ 0.60754485 0.17052412 0.06505159 0.94888554 0.96563203 0.80839735
0.30461377 0.09767211 0.68423303 0.44015249]
[ 0.12203823 0.49517691 0.03438852 0.9093204 0.25877998 0.66252228
0.31171108 0.52006802 0.54671028 0.18485446]
[ 0.96958463 0.77513282 0.93949894 0.89482735 0.59789998 0.92187424
0.0884925 0.19598286 0.04522729 0.32533033]
[ 0.38867729 0.27134903 0.82873751 0.35675333 0.28093451 0.54269608
0.14092422 0.80219698 0.07455064 0.98688694]
[ 0.77224477 0.19871568 0.00552212 0.81546143 0.70685734 0.72900717
0.77127035 0.07404465 0.35846573 0.11586906]
[ 0.86310343 0.62329813 0.33089802 0.06355835 0.31098232 0.32518332
0.72960618 0.63755747 0.88721274 0.47221493]
[ 0.11959425 0.71324479 0.76078505 0.5612772 0.77096718 0.4937956
0.52273283 0.42754102 0.02541913 0.10789143]]
Minimum cost for the random_matrix is 3.33835343297
print('Minimum cost for the `random_matrix` is:',
min_cost_memo(random_matrix, N-1, N-1))
print()def min_cost_path(cost, row, col):
memo = {(0, 0): cost[0][0]}helper function contained with the larger function
def min_cost_memo(cost, row, col):base cases
if row == 0 and col == 0:
return cost[0][0]
if row < 0 or col < 0:
return float('Inf')recursive case
try:
return memo[(row, col)]
except KeyError:
result = cost[row][col] + min(min_cost_memo(cost, row, col-1),
min_cost_memo(cost, row-1, col-1),
min_cost_memo(cost, row-1, col))
memo[(row, col)] = result
return resultcall helper function
min_cost_memo(cost, row, col)return memo (rather than minimum cost)
return memomemo is a dictionary created in min_cost_path based on a matrix
of shape row by col.
def trackback(memo, row, col):create a path with the final cell as the initial item
path = [(row, col)]continue loop until the last item in the path is the cell (0, 0)
while path[-1] != (0, 0):
row, col = path[-1]consider the neighbors to the left, above, and diagonal
neighbors = [(row-1, col), (row, col-1), (row-1, col-1)]
neighbor_costs = []
for neighbor in neighbors:
try:
cost = memo[neighbor]if neighbor is not in memo, the cell is outside of the matrix
except KeyError:
cost = float('Inf')
neighbor_costs.append(cost)find the neighbor with the minimum cost
index = np.argmin(neighbor_costs)append this neighbor to the path
path.append(neighbors[index])
return path[::-1]path_costs = min_cost_path(M, 2, 2)Dictionary or memo of minimum path costs for matrix M is
{(0, 0): 1, (1, 0): 5, (2, 0): 6, (0, 1): 3, (1, 1): 9, (2, 1): 10,
(0, 2): 6, (1, 2): 5, (2, 2): 8}
print('Dictionary or memo of minimum path costs for matrix `M` is\n',
path_costs)
path = trackback(path_costs, 2, 2)Minimum cost path for M is
[(0, 0), (0, 1), (1, 2), (2, 2)]
print('Minimum cost path for `M` is\n', path)
print()A and B or lists or numpy arrays representing pitch-class sets.
Returns the minimum distance, and tuples for A and B ordered by
voices and including any doubled pcs.
For example,
>>> A = [9, 11, 4]
>>> B = [10, 3, 5]
>>> minimal_vl(A, B)
(4.0, (4, 4, 9, 11), (3, 5, 10, 10))
indicating that the total voice-leading distance is 4, and the voice leading contains the following four voices: 4 to 3, 4 to 5, 9 to 10, and 11 to 10.
def minimal_vl(A, B):Create sorted numpy arrays for each pcset
A = np.sort(A)
B = np.sort(B)Generate a voice-leading matrix of motions from A to all rotations of B
rows = len(A)
cols = len(B)
B_rotations = [np.roll(B, i) for i in range(cols)]
matrices = [generate_vl_matrix(A, B_rot) for B_rot in B_rotations]Memo for each voice-leading matrix
memos = []
for matrix in matrices:
memos.append(min_cost_path(matrix, rows, cols))Find minimal voice-leading distance and path
min_distances = []
for i, memo in enumerate(memos):
min_distances.append(memo[(rows, cols)] - matrices[i][0][0])
min_index = np.argmin(min_distances)
path = trackback(memos[min_index], rows, cols)
B_rot = B_rotations[min_index]
chord1, chord2 = zip(*[(A[x%rows], B_rot[y%cols]) for x, y in path[:-1]])
return min_distances[min_index], chord1, chord2Helper function for minimal_vl. X and Y are lists or numpy arrays
representing pc-sets.
Returns a 2D matrix. Rows correspond to X[0], X[1], ...
X[-1], X[0]. Columns correspond to Y[0], Y[1], ..., Y[-1],
Y[0].
def generate_vl_matrix(X, Y):Add copy of first pc to the end of the list (circular space)
X = np.append(X, X[0])
Y = np.append(Y, Y[0])Create empty matrix
vl_M = np.empty([len(X), len(Y)])Fill matrix with all pairwise interval classes
for i, x in enumerate(X):
for j, y in enumerate(Y):
vl_M[i][j] = vl_dist(x, y)
return vl_Mdef vl_dist(x, y): return min((x - y) % 12, (y - x) % 12)
A = [0, 2, 4, 6, 7, 9, 10]
B = [6, 8, 10, 0, 1, 3, 4]vld, chord1, chord2 = minimal_vl(A, B)Minimal voice-leading distance: 4.0
print('Minimal voice-leading distance:', vld)Minimal voice-leading path: (0, 2, 4, 4, 6, 7, 9, 10) ->
(0, 1, 3, 4, 6, 8, 10, 10)
print('Minimal voice-leading path:', chord1, '->', chord2)