diff --git a/algorithms/BinarySearch.typ b/algorithms/BinarySearch.typ new file mode 100644 index 0000000..bbeb1b2 --- /dev/null +++ b/algorithms/BinarySearch.typ @@ -0,0 +1,147 @@ +#import "../lib/style.typ": * +#import "../lib/mapcode.typ": * + +== Binary Search +#set math.equation(numbering: none) + +Search for a target value in a sorted array and return its index. If the target is not found, return -1. + +Formal definition: +$ +"bs"("low", "high") = cases( + -1 & "if " "low" > "high", + "mid" & "if " A["mid"] = "target", + "bs"("low", "mid"-1) & "if " A["mid"] > "target", + "bs"("mid"+1, "high") & "if " A["mid"] < "target" +) +$ + +where $"mid" = floor(("low" + "high")/2)$ + +Examples: +- $A = [1, 3, 5, 7, 9]$, target $= 5 -> 2$ +- $A = [1, 3, 5, 7, 9]$, target $= 6 -> -1$ + +*As mapcode:* + +_primitives_: `comparison` ($<$, $=$), `arithmetic` ($+$, $-$, $floor(dot / 2)$) are strict. i.e., operations on $bot$ are undefined. + +$ +I = (A: ZZ^*, "target": ZZ) quad quad quad +X &= (NN times NN) -> (ZZ union {-1} union {bot}) quad quad quad +A = ZZ union {-1}\ +rho(A, "target") &= {("low", "high") -> bot | "low" in {0 dots n}, "high" in {-1 dots n-1}}\ +F(x_(l,h)) &= cases( + -1 & "if " l > h, + m & "if " A[m] = "target", + x_(l, m-1) & "if " A[m] > "target", + x_(m+1, h) & "if " A[m] < "target" +) quad "where " m = floor((l + h)/2)\ +pi(x) &= x_(0, n-1) quad "where " n = |A| +$ + +#let inst_arr = (1, 3, 5, 7, 9, 11, 13, 15, 17, 19); +#let inst_target = 13; +#let inst_n = inst_arr.len(); + +#figure( + caption: [Binary Search computation using mapcode for $A = #inst_arr$ and target $= #inst_target$], +$ +#{ + let rho = ((arr, target)) => { + let n = arr.len() + let x = (:) + for low in range(0, n + 1) { + for high in range(low - 1, n) { + let key = "(" + str(low) + "," + str(high) + ")" + x.insert(key, none) + } + } + x + } + + let F_key = ((arr, target)) => (x) => (key) => { + // Parse key string like "(0, 9)" back to (low, high) + let parts = key.trim("(").trim(")").split(",") + let low = int(parts.at(0).trim()) + let high = int(parts.at(1).trim()) + + if low > high { + -1 + } else { + let mid = calc.floor((low + high) / 2) + if arr.at(mid) == target { + mid + } else if arr.at(mid) > target { + let dep_key = "(" + str(low) + "," + str(mid - 1) + ")" + if dep_key in x and x.at(dep_key) != none { + x.at(dep_key) + } else { + none + } + } else { + let dep_key = "(" + str(mid + 1) + "," + str(high) + ")" + if dep_key in x and x.at(dep_key) != none { + x.at(dep_key) + } else { + none + } + } + } + } + + let F = ((arr, target)) => (x) => { + let x_new = (:) + for (key, val) in x { + if val != none { + x_new.insert(key, val) + } else { + x_new.insert(key, F_key((arr, target))(x)(key)) + } + } + x_new + } + + let pi = ((arr, target)) => (x) => { + let n = arr.len() + if n == 0 { return -1 } + let key = "(" + str(0) + "," + str(n - 1) + ")" + let result = x.at(key) + if result == none { -1 } else { result } + } + + let X_h = (x, diff_mask: none) => { + // Show only key ranges for visualization + let entries = x.pairs().sorted(key: ((k, v)) => k) + let cells = () + + // Show important ranges + let important_keys = ("(0," + str(inst_n - 1) + ")", "(0,4)", "(5,9)", "(6,9)", "(6,7)", "(6,6)") + for key in important_keys { + if key in x { + let val = if x.at(key) != none {[$#x.at(key)$]} else {[$bot$]} + cells.push(table.cell([$#key$])) + cells.push(table.cell([$-> #val$])) + } + } + + table( + columns: 2, + align: (right, left), + stroke: none, + inset: 3pt, + ..cells + ) + } + + mapcode-viz( + rho, F((inst_arr, inst_target)), pi((inst_arr, inst_target)), + X_h: X_h, + pi_name: [$mpi$], + group-size: 3, + cell-size: 50mm, scale-fig: 80% + )((inst_arr, inst_target)) +} +$ +) + diff --git a/algorithms/InorderTraversal.typ b/algorithms/InorderTraversal.typ new file mode 100644 index 0000000..3d1b13b --- /dev/null +++ b/algorithms/InorderTraversal.typ @@ -0,0 +1,144 @@ +#import "../lib/style.typ": * +#import "../lib/mapcode.typ": * + +== Inorder Traversal of Binary Tree +#set math.equation(numbering: none) + +Perform an inorder traversal of a binary tree, returning the sequence of node values visited in the order: left subtree, root, right subtree. + +Formal definition: +$ +"inorder"(i) = cases( + [v_i] & "if " i "is leaf", + "inorder"("left"(i)) + [v_i] + "inorder"("right"(i)) & "otherwise" +) +$ + +where $v_i$ is the value at node $i$, and $+$ denotes list concatenation. + +Example: + +``` + 4 + / \ + 2 6 + / \ / \ + 1 3 5 7 +``` + +Result: $[1, 2, 3, 4, 5, 6, 7]$ + +*As mapcode:* + +_primitives_: `list concatenation` ($+$) is strict. i.e., concatenation with $bot$ is undefined. + +$ +I = "Tree represented as array" [(v_i, l_i, r_i)]_i quad quad quad +X &= NN -> (ZZ^* union {bot}) quad quad quad +A = ZZ^*\ +rho("Tree") &= {i -> bot | i in {0 dots |"Tree"|-1}}\ +F(x_i) &= cases( + [v_i] & "if " l_i = "None" and r_i = "None", + x_(l_i) + [v_i] + x_(r_i) & "otherwise" +)\ +pi(x) &= x_0 quad "(" "root's inorder sequence" ")" +$ + +where each tree node $i$ has value $v_i$, left child index $l_i$, and right child index $r_i$ (None if no child). + +#let inst_tree = ( + (4, 1, 2), // Node 0: value=4, left=1, right=2 + (2, 3, 4), // Node 1: value=2, left=3, right=4 + (6, 5, 6), // Node 2: value=6, left=5, right=6 + (1, none, none), // Node 3: value=1, leaf + (3, none, none), // Node 4: value=3, leaf + (5, none, none), // Node 5: value=5, leaf + (7, none, none) // Node 6: value=7, leaf +); + +#figure( + caption: [Inorder Traversal computation using mapcode for the binary tree shown above], +$ +#{ + let rho = (tree) => { + let x = () + for i in range(0, tree.len()) { + x.push(none) + } + x + } + + let F_i = (tree) => (x) => ((i,)) => { + let node = tree.at(i) + let val = node.at(0) + let left_idx = node.at(1) + let right_idx = node.at(2) + + // Leaf node + if left_idx == none and right_idx == none { + (val,) + } else { + let left_list = if left_idx != none { x.at(left_idx) } else { () } + let right_list = if right_idx != none { x.at(right_idx) } else { () } + + // Check if dependencies are resolved + if left_idx != none and left_list == none { + return none + } + if right_idx != none and right_list == none { + return none + } + + // Concatenate: left + [val] + right + let result = () + if left_list != none { + result = result + left_list + } + result = result + (val,) + if right_list != none { + result = result + right_list + } + result + } + } + + let F = (tree) => map_tensor(F_i(tree), dim: 1) + + let pi = (tree) => (x) => x.at(0) + + let X_h = (x, diff_mask: none) => { + let cells = () + for i in range(0, x.len()) { + let val = x.at(i) + let node_val = inst_tree.at(i).at(0) + + let content = if val != none { + $[#val.map(v => str(v)).join(", ")]$ + } else { + $bot$ + } + + if diff_mask != none and diff_mask.at(i) { + cells.push(rect(fill: yellow.transparentize(70%), inset: 3pt)[ + #text(size: 8pt)[$"node"_#node_val$]: #content + ]) + } else { + cells.push(rect(stroke: none, inset: 3pt)[ + #text(size: 8pt)[$"node"_#node_val$]: #content + ]) + } + } + stack(dir: ttb, spacing: 3pt, ..cells) + } + + mapcode-viz( + rho, F(inst_tree), pi(inst_tree), + X_h: X_h, + pi_name: [$mpi$], + group-size: 4, + cell-size: 35mm, scale-fig: 70% + )(inst_tree) +} +$ +) + diff --git a/main.typ b/main.typ index 9952d9b..b473dd4 100644 --- a/main.typ +++ b/main.typ @@ -51,4 +51,8 @@ All primitives are _strict_ meaning they do not allow for undefined values (i.e. #pagebreak() #include "algorithms/LongestCommonSubsequence.typ" #pagebreak() +#include "algorithms/BinarySearch.typ" +#pagebreak() +#include "algorithms/InorderTraversal.typ" +#pagebreak() #include "algorithms/leetcode/P2_add-two-numbers.typ"