From ec1ff0f540c8750dbdea046e0f7128e04827ea17 Mon Sep 17 00:00:00 2001 From: fine-man Date: Sun, 16 Nov 2025 21:43:17 +0530 Subject: [PATCH] feat: add implementations for Rod Cutting and Subset Sum problems with visualizations --- algorithms/RodCutting.typ | 117 ++++++++++++++++++++++++++++++++ algorithms/SubsetSum.typ | 139 ++++++++++++++++++++++++++++++++++++++ main.typ | 4 ++ 3 files changed, 260 insertions(+) create mode 100644 algorithms/RodCutting.typ create mode 100644 algorithms/SubsetSum.typ diff --git a/algorithms/RodCutting.typ b/algorithms/RodCutting.typ new file mode 100644 index 0000000..38151e6 --- /dev/null +++ b/algorithms/RodCutting.typ @@ -0,0 +1,117 @@ +#import "../lib/style.typ": * +#import "../lib/mapcode.typ": * + +== Rod Cutting Problem + +Given a rod of length $n$ and a table of prices $P$ for different lengths, +determine the maximum revenue $r_n$ obtainable by cutting up the rod and +selling the pieces. + +Formal definition: +Let $r_i$ be the maximum revenue for a rod of length $i$. +$ +r_n = cases( + 0 & "if " n = 0, + max_(1 <= i <= n) (P_i + r_(n-i)) & "if " n > 0 +) +$ + +*As mapcode:* + +Here, $NN$ is the set of non-negative integers $\{0, 1, ...\}$, +and $NN_bot = NN union {bot}$. + +_primitives_: `max`, `sum`($+$) + +$ +I &= P:vec(NN) times n:NN quad quad quad "where" m = |P| \ +X_n &= [0..n] -> NN_bot quad quad quad \ +A &= NN \ +rho(n) &= { i -> bot | i in {0 dots n}} \ +F_P(x_i) &= cases( + 0 & "if " i = 0, + max_(1 <= j <= i) (P_(j-1) + x_(i-j)) & "if " i > 0 " and " j-1 < m +)\ +pi_n(x) &= x_n +$ + +// Price table: P[0] = price for length 1, P[1] = price for length 2, etc. +// So for this example: length 1 costs 1, length 2 costs 5, length 3 costs 8, length 4 costs 9 +#let inst_P = (1, 5, 8, 9); +// Target length +#let inst_n = 4; + +#figure( + caption: [Rod Cutting DP table for $n = #inst_n$ and prices $P = #inst_P$.], +$ +#{ + let rho = (inst) => { + let x = () + for i in range(0, inst + 1) { + x.push(none) + } + x + } + + let F_i = (P) => (x) => ((i,)) => { + if i == 0 { + 0 + } else if x.at(i) != none { + // Already computed, keep the value + x.at(i) + } else { + // Check if all dependencies are computed + let all_deps_ready = true + for j in range(1, i + 1) { + if j - 1 < P.len() and x.at(i - j) == none { + all_deps_ready = false + break + } + } + + if not all_deps_ready { + none + } else { + let max_rev = 0 + // j is the length of the first cut + for j in range(1, i + 1) { + // Check if we have a price for this cut + if j - 1 < P.len() { + let p_j = P.at(j - 1) + let r_remaining = x.at(i - j) + max_rev = calc.max(max_rev, p_j + r_remaining) + } + } + max_rev + } + } + } + let F = map_tensor(F_i(inst_P), dim: 1) + + let pi = (i) => (x) => x.at(i) + + // X_h visualizer from fibonacci.typ + let X_h = (x, diff_mask: none) => { + let cells = x.enumerate().map(((i, x_i)) => { + let val = if x_i != none {[$#x_i$]} else {[$bot$]} + if diff_mask != none and diff_mask.at(i) { + // changed element: highlight + rect(fill: yellow.transparentize(70%), inset: 2pt)[$#val$] + } else { + rect(stroke: none, inset: 2pt)[$#val$] + } + }) + $vec(delim: "[", ..cells)$ + } + + mapcode-viz( + rho, F, pi(inst_n), + X_h: X_h, + pi_name: [$mpi (#inst_n)$], + group-size: inst_n + 1, // Show all steps in one row + cell-size: 10mm, + scale-fig: 95% + )(inst_n) +} +$ +) \ No newline at end of file diff --git a/algorithms/SubsetSum.typ b/algorithms/SubsetSum.typ new file mode 100644 index 0000000..2d53a5f --- /dev/null +++ b/algorithms/SubsetSum.typ @@ -0,0 +1,139 @@ +#import "../lib/style.typ": * +#import "../lib/mapcode.typ": * + +== Subset Sum Problem + +Given a set of non-negative integers $S$ and a target sum $T$, determine +if there is a subset of $S$ whose elements sum to $T$. + +Formal definition: +Let $D(i, j)$ be true if a sum of $j$ can be made using the first $i$ items. +$ +D(i, j) = cases( + "true" & "if " j = 0, + "false" & "if " i = 0 " and " j > 0, + D(i-1, j) & "if " S_i > j, + D(i-1, j) or D(i-1, j - S_i) & "otherwise" +) +$ + +*As mapcode:* + +_primitives_: `or` + +$ +I &= S:bb(N)^* times T:bb(N) quad quad quad "where" m = |S| \ +X_(m,T) &= [0..m] times [0..T] -> {bot, "true", "false"} \ +A &= {"true", "false"} \ +rho(m,T) &= { (i,j) |-> bot | i in {0 dots m}, j in {0 dots T}} \ +F_S(x_(i,j)) &= cases( + "true" & "if " j = 0, + "false" & "if " i = 0 " and " j > 0, + x_(i-1, j) & "if " S_(i-1) > j, + x_(i-1, j) or x_(i-1, j - S_(i-1)) & "otherwise" +) \ +pi_(S,T) (x) &= x_(m,T) +$ + +#let inst_S = (3, 5, 8); +#let inst_T = 11; +#let inst_m = inst_S.len(); + +#figure( + caption: [Subset Sum DP table for $S = #inst_S$ and $T = #inst_T$.], +$#{ + let rho = ((inst_m, inst_T)) => { + let x = () + for i in range(0, inst_m + 1) { + let row = () + for j in range(0, inst_T + 1) { + row.push(none) + } + x.push(row) + } + x + } + let F_i = (S) => (x) => ((i,j)) => { + if j == 0 { + true + } else if i == 0 and j > 0 { + false + } else { + let item = S.at(i - 1) + if item > j { + // Item is too large, 'without' case + let without = x.at(i - 1).at(j) + if without != none { + without + } else { + none + } + } else { + // 'with' or 'without' + let without = x.at(i - 1).at(j) + let with = x.at(i - 1).at(j - item) + if without != none and with != none { + without or with + } else { + none + } + } + } + } + let F = (S) => map_tensor(F_i(S), dim: 2) + + let pi = ((S, T)) => (x) => { + let m = S.len() + x.at(m).at(T) + } + + // draw DP table + let x_h(x, diff_mask:none) = { + set text(weight: "bold") + let rows = () + + // header row: Target Sum + let header_cells = () + header_cells.push(rect(fill: green.transparentize(70%), inset: 4pt)[$i slash j$]) + for j in range(0, inst_T + 1) { + header_cells.push(rect(fill: orange.transparentize(70%), inset: 4pt)[$#j$]) + } + rows.push(grid(columns: header_cells.len() * (14pt,), rows: 14pt, align: center + horizon, ..header_cells)) + + for i in range(0, x.len()) { + let row = () + // left label: Item from S + if i == 0 { + row.push(rect(fill: green.transparentize(70%), inset: 4pt)[$0$]) + } else { + row.push(rect(fill: green.transparentize(70%), inset: 4pt)[$#inst_S.at(i - 1)$]) + } + + for j in range(0, x.at(i).len()) { + let val = if x.at(i).at(j) == true { + $checkmark$ + } else if x.at(i).at(j) == false { + $times$ + } else { + $bot$ + } + + if diff_mask != none and diff_mask.at(i).at(j) { + row.push(rect(stroke: gray, fill: yellow.transparentize(70%), inset: 4pt)[#val]) + } else { + row.push(rect(stroke: gray, inset: 4pt)[#val]) + } + } + rows.push(grid(columns: row.len() * (14pt,), rows: 14pt, align: center + horizon, ..row)) + } + grid(align: center, ..rows) + } + + mapcode-viz( + rho, F(inst_S), pi((inst_S, inst_T)), + X_h: x_h, + pi_name: [$pi ((#inst_m, #inst_T))$], + group-size: calc.min(3, inst_m), + cell-size: 55mm, scale-fig: 65% + )((inst_m, inst_T)) +}$) \ No newline at end of file diff --git a/main.typ b/main.typ index 9952d9b..cdbf9da 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/SubsetSum.typ" +#pagebreak() +#include "algorithms/RodCutting.typ" \ No newline at end of file