From 44f75732c4b2a2cabbb6dc1dec75f2b639aec7c4 Mon Sep 17 00:00:00 2001 From: Egomax4 Date: Sun, 16 Nov 2025 04:59:55 +0000 Subject: [PATCH] 2022114001 changes --- algorithms/Catalan.typ | 88 +++++++++++++++++++++++ algorithms/Matrix_Chain.typ | 139 ++++++++++++++++++++++++++++++++++++ main.typ | 4 ++ 3 files changed, 231 insertions(+) create mode 100644 algorithms/Catalan.typ create mode 100644 algorithms/Matrix_Chain.typ diff --git a/algorithms/Catalan.typ b/algorithms/Catalan.typ new file mode 100644 index 0000000..e9464c8 --- /dev/null +++ b/algorithms/Catalan.typ @@ -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)) +}$) \ No newline at end of file diff --git a/algorithms/Matrix_Chain.typ b/algorithms/Matrix_Chain.typ new file mode 100644 index 0000000..ce873f7 --- /dev/null +++ b/algorithms/Matrix_Chain.typ @@ -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 +}$) \ No newline at end of file diff --git a/main.typ b/main.typ index 9952d9b..e9d8dbd 100644 --- a/main.typ +++ b/main.typ @@ -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"