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
188 changes: 188 additions & 0 deletions algorithms/TowerofHanoi.typ
Original file line number Diff line number Diff line change
@@ -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)
}
$,
)
168 changes: 168 additions & 0 deletions algorithms/TreeHeight.typ
Original file line number Diff line number Diff line change
@@ -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)
}
$,
)
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"
#include "algorithms/TowerOfHanoi.typ"
#pagebreak()
#include "/algorithms/TreeHeight.typ"
#pagebreak()