Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions algorithms/Catalan.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#import "../lib/style.typ": *
#import "../lib/mapcode.typ": *

== Catalan Numbers

Compute the $n$-th Catalan number $C_n$. Catalan numbers appear in many counting problems.

Formal definition:
$
C_n = frac(1, n+1) binom(2n, n)
$
The recurrence relation used here is:
$
C_i = cases(
1 & "if " i = 0,
frac(2i(2i-1), i(i+1)) C_(i-1) & "if " i > 0
)
$

*As mapcode:*

_primitives_: `product`($*$), `division`($\/$)

$
I = i:[0..n] quad quad quad
X_i & = [0..n] -> NN quad quad quad
A = NN\
rho(n) & = { i -> bot | i in {0 dots n}} \
F(x_i) & = cases(
1 & "if " i = 0,
(x_(i-1) * (2i) * (2i-1)) / ((i+1) * i) & "otherwise"
)\
pi(x) & = x_n
$

#let inst_n = 5; // Compute C_5

#figure(
caption: [Computation of Catalan numbers using mapcode for $n = #inst_n$; 1D dynamic-programming table visualization.],
$#{
let rho = ((n)) => {
let x = ()
for i in range(0, n + 1) {
x.push(none)
}
x
}

let F_i = () => (x) => ((i,)) => {
if i == 0 {
1
} else if x.at(i - 1) != none {
// Use integer division where possible, but result might be float.
// The formula (2*i)*(2*i-1)*x[i-1]/((i+1)*i) simplifies to
// (2 * (2*i - 1) * x.at(i - 1)) / (i + 1)
// Let's use the user's provided formula directly
(2 * i) * (2 * i - 1) * x.at(i - 1) / ((i + 1) * i)
} else {
none
}
}
let F = () => map_tensor(F_i(), dim: 1)

let pi = ((n)) => (x) => x.at(n)

// draw DP table (1D array)
let x_h(x, diff_mask: none) = {
set text(weight: "bold")
let cells = ()
for i in range(0, x.len()) {
let val = if x.at(i) != none { [$C_#i = #x.at(i)$] } else { [$C_#i = bot$] }
if diff_mask != none and diff_mask.at(i) {
cells.push(rect(stroke: gray, fill: yellow.transparentize(70%), inset: 4pt)[#val])
} else {
cells.push(rect(stroke: gray, inset: 4pt)[#val])
}
}
grid(columns: x.len() * (auto,), rows: (20pt,), align: center, ..cells)
}

mapcode-viz(
rho, F(), pi((inst_n)),
X_h: x_h,
pi_name: [$pi_n$],
group-size: 2, // Show all steps horizontally
cell-size: 60mm, scale-fig: 50%
)((inst_n))
}$)
139 changes: 139 additions & 0 deletions algorithms/Matrix_Chain.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#import "../lib/style.typ": *
#import "../lib/mapcode.typ": *

== Matrix Chain Multiplication

Compute the minimum number of scalar multiplications needed to multiply a chain of matrices. The input $p$ is an array of dimensions, where matrix $M_i$ has dimensions $p_i times p_(i+1)$.

Formal definition (0-indexed):
$
m[i, j] = cases(
0 & "if " j = i+1,
min_(i < k < j) (m[i, k] + m[k, j] + p_i p_k p_j) & "if " j > i+1
)
$

*As mapcode:*

_primitives_: `sum`($+$), `min`(min)

$
I = (i,j):[0..n-1] times [0..n-1] quad "where" n = |p| \
// X is now *only* the matrix
X_(i,j) & = [0..n-1] times [0..n-1] -> NN_bot \
A = NN\
// rho is parameterized by (p, n) and returns *only* mat
rho_((p, n)) & = { (i,j) -> bot | i,j in {0 dots n-1}} \
// F is parameterized by (p) and receives *only* mat
F_((p))(x_(i,j)) & = cases(
0 & "if " j = i+1,
min_(i < k < j) (x_(i,k) + x_(k,j) + p_i p_k p_j) & "if " j > i+1,
bot & "otherwise"
)\
// pi is parameterized by (p, n) and receives *only* mat
pi_((p, n))(x) & = x_(0, n-1)
$

#let inst_p = (4,6,2,9,4,5,6); // 3 matrices: 10x30, 30x5, 5x60
#let inst_n = inst_p.len();

#figure(
caption: [Matrix Chain Multiplication (MCM) computation using mapcode for $p = #inst_p$.],
$#{
// rho is parameterized, returns *only* mat
let rho = ((p, n)) => {
let mat = ()
for i in range(0, n) {
let row = ()
for j in range(0, n) { row.push(none) }
mat.push(row)
}
mat // <-- CHANGED
}

// F_i is parameterized by (p).
// It receives x_full which is *just* the matrix.
let F_i = ((p)) => (x_full) => ((i, j)) => {
let mat = x_full // <-- CHANGED: x_full *is* the mat

if j <= i {
return none // Lower triangle and diagonal
} else if j == i + 1 {
return 0 // Cost of a single matrix
} else {
// j > i+1. Compute min cost
let costs = ()
for k in range(i + 1, j) {
let cost1 = mat.at(i).at(k)
let cost2 = mat.at(k).at(j)

// p comes from closure
if cost1 != none and cost2 != none {
let cost_split = cost1 + cost2 + p.at(i) * p.at(k) * p.at(j)
costs.push(cost_split)
}
}

if costs.len() > 0 {
return calc.min(..costs)
} else {
return none // Prerequisites not met
}
}
}

// F is parameterized by (p). It returns a function that
// map_tensor will apply to x_full (which is just mat).
let F = ((p)) => map_tensor(F_i((p)), dim: 2)

// No F_wrapped is needed anymore.

// pi is parameterized by (p, n).
// It receives x_full which is *just* the final matrix.
let pi = ((p, n)) => (x_full) => {
let mat = x_full // <-- CHANGED
mat.at(0).at(n - 1) // Final cost m[0, n-1]
}

// x_h receives x_full which is *just* the matrix
let x_h(x_full, diff_mask: none) = {
let mat = x_full // <-- CHANGED
let n = mat.len() // Get n from the matrix itself
set text(weight: "bold")
let rows = ()

// Header row
let header_cells = ()
header_cells.push(rect(stroke: none, inset: 4pt)[$i/j$])
for j in range(0, n) { header_cells.push(rect(fill: orange.transparentize(70%), inset: 4pt)[$#j$]) }
rows.push(grid(columns: (n + 1) * (auto,), ..header_cells))

for i in range(0, n) {
let row = ()
row.push(rect(fill: green.transparentize(70%), inset: 4pt)[$#i$]) // Row label

for j in range(0, n) {
let val = if mat.at(i).at(j) != none { [$#mat.at(i).at(j)$] } else { [$bot$] }
let cell = rect(stroke: gray, inset: 4pt)[#val]
if i >= j {
cell = rect(stroke: gray, fill: gray.transparentize(80%), inset: 4pt)[#val]
}

if diff_mask != none and diff_mask.at(i).at(j) {
cell = rect(stroke: gray, fill: yellow.transparentize(70%), inset: 4pt)[#val]
}
row.push(cell)
}
rows.push(grid(columns: (n + 1) * (auto,), ..row))
}
grid(rows: (n + 1), ..rows)
}

mapcode-viz(
rho, F((inst_p)), pi((inst_p, inst_n)), // <-- Call F, not F_wrapped
X_h: x_h,
pi_name: [$"pi_mcm"$],
group-size: 2, // DP computation order is diagonal
cell-size: 60mm, scale-fig: 75%
)((inst_p, inst_n)) // Pass (p, n) to parameterize rho, pi
}$)
4 changes: 4 additions & 0 deletions main.typ
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@ All primitives are _strict_ meaning they do not allow for undefined values (i.e.
#include "algorithms/LongestCommonSubsequence.typ"
#pagebreak()
#include "algorithms/leetcode/P2_add-two-numbers.typ"
#pagebreak()
#include "algorithms/Catalan.typ"
#pagebreak()
#include "algorithms/Matrix_Chain.typ"