diff --git a/algorithms/TowerofHanoi.typ b/algorithms/TowerofHanoi.typ new file mode 100644 index 0000000..2c38e6d --- /dev/null +++ b/algorithms/TowerofHanoi.typ @@ -0,0 +1,188 @@ +#import "../lib/style.typ": * +#import "../lib/mapcode.typ": * + +== Tower of Hanoi +#set math.equation(numbering: none) + +Solve the Tower of Hanoi puzzle with $n$ disks. +Algorithm: +1. Move $n-1$ disks from source to auxiliary. +2. Move the largest disk from source to destination. +3. Move $n-1$ disks from auxiliary to destination. + +*As mapcode (Dynamic Programming Model):* +We model the state `X` as a 2D table `X[k][p]`, where $k$ is the number of disks and $p$ is an index for one of the 6 rod permutations. +$ X[k, p] = "solution for hanoi(k, ...)" $ + +$ + I = n:NN \ + X = [0..n] times [0..5] -> [text("Move")]_bot \ + A = [text("Move")] \ + rho(n) & = { (k,p) -> bot | k in {0..n}, p in {0..5}} \ + F(x)_(k,p) & = cases( + [] & "if " k = 0, + x[k-1, p_1] + [(s,d)] + x[k-1, p_2] & "if deps " x[k-1, ..] != bot, + bot & "otherwise" + ) \ + pi(n)(x) & = x[n, 0] // p=0 is the main problem "A->C (B)" +$ + +// --- 1. Global Definitions (Helper Data) --- + +// Define all 6 permutations (src, dst, aux) +#let perms = ( + (src: "A", dst: "C", aux: "B"), // p=0 (Main Problem) + (src: "A", dst: "B", aux: "C"), // p=1 + (src: "B", dst: "C", aux: "A"), // p=2 + (src: "B", dst: "A", aux: "C"), // p=3 + (src: "C", dst: "B", aux: "A"), // p=4 + (src: "C", dst: "A", aux: "B") // p=5 +) + +// Pre-calculate the dependency indices for each permutation `p` +// dep_logic[p] = (p_dep1, p_dep2) +// Example: p=0 (A->C, B) depends on: +// 1. h(k-1, A, B, C) -> p=1 +// 2. h(k-1, B, C, A) -> p=2 +#let dep_logic = ( + (1, 2), // p=0 + (0, 5), // p=1 + (3, 0), // p=2 + (2, 4), // p=3 + (5, 2), // p=4 + (4, 3) // p=5 +) + +#let inst_n = 2; // Example for 2 disks + +// --- 2. Mapcode Functions (rho, F, pi) --- + +// rho: Initialize (n+1) x 6 table with `none` +#let rho = n => { + let x = () + for k in range(0, n + 1) { + let row = () + for p in range(6) { + row.push(none) // bot + } + x.push(row) + } + x +} + +// F_i: Logic for a single cell X[k][p] +#let F_i = x => ((k_idx, p_idx)) => { + // Base case: 0 disks requires no moves + if k_idx == 0 { + () + } else { + // 1. Get info for this permutation + let p_info = perms.at(p_idx) + let move_mid = ( (p_info.src, p_info.dst), ) + + // 2. Find the dependency indices from our pre-calculated map + let (p1_idx, p2_idx) = dep_logic.at(p_idx) + + // 3. Look up solutions for sub-problems in the *previous* state `x` + let moves1 = x.at(k_idx - 1).at(p1_idx) + let moves2 = x.at(k_idx - 1).at(p2_idx) + + // 4. Check if dependencies are met (i.e., not none) + if moves1 != none and moves2 != none { + // 5. Dependencies met. Compute the result using array concatenation. + moves1 + move_mid + moves2 + } else { + // Dependencies not met. Stay ⊥ (none). + none + } + } +} + +// F: Applies F_i to the entire 2D state array +#let F = map_tensor(F_i, dim: 2) + +// pi: Extract final result (solution for n disks, main permutation p=0) +#let pi = n => x => x.at(n).at(0) + +// --- 3. Visualization Helpers (X_h, A_h) --- + +// X_h: Renders the state X +#let X_h = (x, diff_mask: none) => { + // Show the status of the main problem (p=0) for each k + let cells = () + for k in range(0, x.len()) { + let row = x.at(k) + let val = row.at(0) // Get the p=0 (main) permutation + + let cell_val_str = if val != none and type(val) == array { + let moves = val + if moves.len() > 0 { + let move_str = moves.slice(0, calc.min(moves.len(), 3)).map(m => { + if type(m) == array and m.len() >= 2 { + str(m.at(0)) + "->" + str(m.at(1)) + } else { "?" } + }).join(", ") + if moves.len() > 3 { move_str + "..." } else { move_str } + } else { "()" } // Empty moves + } else { + [$bot$] // This can be math, it's just content + } + + let cell_changed = false + if diff_mask != none and k < diff_mask.len() { + let row_mask = diff_mask.at(k) + if row_mask != none and type(row_mask) == array and 0 < row_mask.len() { + cell_changed = row_mask.at(0) + } + } + + // This creates content, which can include math + let cell_content = [$k=#k: #cell_val_str$] + + if cell_changed { + // CORRECTED: Added % + cells.push(rect(fill: yellow.transparentize(70%), inset: 2pt)[#cell_content]) + } else { + cells.push(cell_content) + } + } + + // Return a `table` (a content-mode function), not `$mat` + table( + columns: 1, + align: center, + ..cells + ) +} + + +// A_h: Renders the final answer +#let A_h = a => { + if a != none and type(a) == array { + let moves_items = a.map(m => "(" + str(m.at(0)) + "," + str(m.at(1)) + ")") + let moves_str = moves_items.join(", ") + [$(#moves_str)$] + } else { + [$bot$] + } +} + +// --- 4. Visualization Execution --- +#figure( + caption: [Tower of Hanoi computation using mapcode for $#inst_n$ disks], + $ + #{ + mapcode-viz( + rho, + F, + pi(inst_n), + X_h: X_h, + A_h: A_h, + pi_name: [$pi$], + group-size: inst_n + 1, // Show all k steps + cell-size: 40mm, + scale-fig: 90% + )(inst_n) + } + $, +) \ No newline at end of file diff --git a/algorithms/TreeHeight.typ b/algorithms/TreeHeight.typ new file mode 100644 index 0000000..0527b33 --- /dev/null +++ b/algorithms/TreeHeight.typ @@ -0,0 +1,168 @@ +#import "../lib/style.typ": * +#import "../lib/mapcode.typ": * + +== Recursive Height of a Binary Tree +#set math.equation(numbering: none) + +Compute the recursive height of a binary tree. +Formal definition: +$ + "Height"(n) = cases( + -1 & "if " n = "None", + 1 + max("Height"("n.left"), "Height"("n.right")) & "otherwise" + ) +$ + +*As mapcode:* + +_primitives_: `max` function, `addition`($+$). + +In the mapcode framework, we model tree height computation as a DP table where each node's height depends on its children's heights: + +$ + I = "TreeDef" \ + X = [0..N-1] -> NN_bot \ + A = NN \ + rho("tree") & = {i -> bot | i in [0..N-1]} \ + F(x)_i & = cases( + -1 & "if node" i "is terminal"\\ + 1 + max(x_{l(i)}, x_{r(i)}) & "if both children solved"\\ + bot & "otherwise" + ) \ + pi(x) & = x_{root} +$ + +// --- Input Instance --- +// We define the tree structure with node indices instead of string IDs +#let inst_tree = ( + nodes: ( + (id: 0, val: 10, left: 1, right: 2), // node 0: root with value 10, left child 1, right child 2 + (id: 1, val: 5, left: 3, right: 4), // node 1: value 5, left child 3, right child 4 + (id: 2, val: 15, left: -1, right: -1), // node 2: value 15, left None (-1), right None (-1) + (id: 3, val: 2, left: -1, right: -1), // node 3: value 2, left None, right None + (id: 4, val: 7, left: -1, right: -1), // node 4: value 7, left None, right None + ), + root: 0, + size: 5 +) + +// --- Mapcode Functions (rho, F, pi) --- + +// rho: Initialize with all values as undefined (bot) +#let rho = tree_data => { + let x = () + for i in range(0, tree_data.size) { + x.push(none) // Represents bot + } + x +} + +// F_i: Compute height for a specific node based on current state +// Returns the new height for node at index `node_idx` given the previous state +#let F_i = (tree_data) => (prev_state) => ((node_idx,)) => { + let node = tree_data.nodes.at(node_idx) + + // Base case: if node is None (-1) we return -1, but since we index nodes differently, + // we handle terminal nodes specially + if node_idx >= tree_data.nodes.len() or node_idx < 0 { + return -1 // Not a valid node + } + + // Check if this is a leaf node (no valid children) + let left_idx = node.left + let right_idx = node.right + + // Terminal case: both children are None (-1) + if left_idx == -1 and right_idx == -1 { + return 0 // Height of leaf node is 0 + } + + // Get children heights from previous state + let left_height = if left_idx >= 0 and left_idx < prev_state.len() { + prev_state.at(left_idx) + } else { + if left_idx == -1 { -1 } else { none } // -1 represents None + } + + let right_height = if right_idx >= 0 and right_idx < prev_state.len() { + prev_state.at(right_idx) + } else { + if right_idx == -1 { -1 } else { none } // -1 represents None + } + + // Check if dependencies are satisfied + let left_ok = (left_idx == -1 or (left_idx >= 0 and left_height != none)) + let right_ok = (right_idx == -1 or (right_idx >= 0 and right_height != none)) + + if left_ok and right_ok { + // Calculate height based on children + let actual_left_height = if left_idx == -1 { -1 } else { left_height } + let actual_right_height = if right_idx == -1 { -1 } else { right_height } + + 1 + calc.max(actual_left_height, actual_right_height) + } else { + none // Dependencies not met yet + } +} + +// F: Apply F_i to all nodes using map_tensor +#let F = tree_data => map_tensor(F_i(tree_data), dim: 1) + +// pi: Extract the height of the root node +#let pi = tree_data => x => x.at(tree_data.root) + +// --- Visualization Helpers --- +// X_h: Render the state as a vector of heights +#let X_h = (x, diff_mask: none) => { + let cells = () + for i in range(0, calc.min(x.len(), 10)) { // Limit display + let height = x.at(i) + let val = if height != none { + $h_#i = #height$ + } else { + $h_#i = bot$ + } + + let is_changed = if diff_mask != none and i < diff_mask.len() { + if type(diff_mask.at(i)) == bool { diff_mask.at(i) } else { false } + } else { + false + } + + if is_changed { + cells.push(rect(fill: yellow.transparentize(70%), inset: 2pt)[$#val$]) + } else { + cells.push(val) + } + } + $vec(delim: "[", ..cells)$ +} + +// A_h: Render the final answer +#let A_h = a => { + if a != none { + [$"height = " #a$] + } else { + [$bot$] + } +} + +// --- Visualization Execution --- +#figure( + caption: [Recursive Tree Height computation using mapcode.], + $ + #{ + mapcode-viz( + rho, + F(inst_tree), + pi(inst_tree), + X_h: X_h, + A_h: A_h, + pi_name: [$pi$], + group-size: 3, + cell-size: 10mm, + scale-fig: 85% + )(inst_tree) + } + $, +) \ No newline at end of file diff --git a/main.typ b/main.typ index 9952d9b..cf99df5 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" +#include "algorithms/TowerOfHanoi.typ" +#pagebreak() +#include "/algorithms/TreeHeight.typ" +#pagebreak() \ No newline at end of file