From f453d441345304a99f351943e052626f0ea83ae3 Mon Sep 17 00:00:00 2001 From: chasmiccoder Date: Mon, 8 Dec 2025 02:40:18 +0530 Subject: [PATCH] Added: IntegerPartitions and Mergesort --- algorithms/IntegerPartitions.typ | 242 +++++++++++++++++++++++++++++++ algorithms/Mergesort.typ | 208 ++++++++++++++++++++++++++ main.typ | 4 + 3 files changed, 454 insertions(+) create mode 100644 algorithms/IntegerPartitions.typ create mode 100644 algorithms/Mergesort.typ diff --git a/algorithms/IntegerPartitions.typ b/algorithms/IntegerPartitions.typ new file mode 100644 index 0000000..2277ce5 --- /dev/null +++ b/algorithms/IntegerPartitions.typ @@ -0,0 +1,242 @@ +#import "../lib/style.typ": * +#import "../lib/mapcode.typ": * + +== Integer Partitions +#set math.equation(numbering: none) + +Generate all integer partitions of a positive integer $n$. A partition of $n$ is a way of writing $n$ as a sum of positive integers, where order doesn't matter. + +Formal definition: + +For integer $n in NN$, find all sequences $[p_1, p_2, ..., p_k]$ where: +$ +p_1 + p_2 + ... + p_k = n quad "and" quad p_1 >= p_2 >= ... >= p_k >= 1 +$ + +Examples of partitions: +- $n = 4$: $[4], [3,1], [2,2], [2,1,1], [1,1,1,1]$ (5 partitions) +- $n = 5$: $[5], [4,1], [3,2], [3,1,1], [2,2,1], [2,1,1,1], [1,1,1,1,1]$ (7 partitions) + +Recursive structure: +$ +P(n, m) = "partitions of" n "using parts" <= m +$ +$ +P(0, m) &= {[]} quad "for all" m\ +P(n, m) &= union.big_(k=1)^(min(n,m)) {[k] + p | p in P(n-k, k)} +$ + +*As mapcode:* + +_primitives_: List concatenation and set union. Both strict on $bot$. + +State: map $(s, m) -> "List"["Partition"]$ where $s$ is sum and $m$ is max part allowed. + +$ I = n: NN quad quad X &= (NN times NN) -> "Set"["Partition"]_bot quad quad A = "Set"["Partition"]\ +rho(n) &= {(0, m) -> {[]} | m in [0..n]} union {(s,m) -> bot | s > 0}\ +F(x) &= cases( + x((s,m)) & "if" (s,m) in "dom"(x), + union.big_(k=1)^(min(s,m)) {[k] + p | p in x((s-k, k))} & "if all children defined", + bot & "otherwise" +)\ +pi(x) &= x((n, n)) +$ + +Convergence: Fixed point reached when all reachable $(s,m)$ states are computed, typically in $O(n)$ iterations. + +#let inst_n = 5; +#figure( + caption: [Integer partitions computation using mapcode for $n = #inst_n$], +$ +#{ + // Check if partition already exists in list + let contains_partition = (partitions, target) => { + for p in partitions { + if p.len() != target.len() { continue } + let match = true + for i in range(p.len()) { + if p.at(i) != target.at(i) { + match = false + break + } + } + if match { return true } + } + false + } + + // ρ: Initialize base cases (0, m) = [[]] + let rho = n => { + let state = () + for s in range(0, n + 1) { + let row = () + for m in range(0, n + 1) { + if s == 0 { + // Empty partition represented as single element array with marker + row.push(((-1,),)) + } else { + row.push(none) + } + } + state.push(row) + } + state + } + + // F: Build partitions by prepending parts + let F_i = x => ((s, m)) => { + // Base case: sum is 0, return empty partition marker + if s == 0 { + return ((-1,),) + } + + // Already computed - return as is + let current = x.at(s).at(m) + if current != none { + return current + } + + // Try to compute from children + let result = () + let max_k = calc.min(s, m) + + for k in range(1, max_k + 1) { + let child_s = s - k + let child_m = k + let child_parts = x.at(child_s).at(child_m) + + if child_parts == none { + // Can't compute yet - child not ready + return none + } + + // For each partition in child, prepend k + for child_part in child_parts { + let new_part = if child_part.at(0) == -1 { + // Child is empty partition, just return [k] + (k,) + } else { + // Prepend k to child partition + (k,) + child_part + } + + if not contains_partition(result, new_part) { + result.push(new_part) + } + } + } + + if result.len() > 0 { + result + } else { + none + } + } + let F = map_tensor(F_i, dim: 2) + + // π: Extract partitions of (n, n) + let pi = n => x => { + let result = x.at(n).at(n) + if result != none and result.len() > 0 and result.at(0).at(0) == -1 { + // Return empty array if marked as empty + return ((),) + } + result + } + + // Display 2D state grid showing defined cells + let X_h = (x, diff_mask: none) => { + let n = inst_n + let rows = () + + for s in range(0, calc.min(n + 1, 6)) { + let cells = () + for m in range(0, calc.min(n + 1, 6)) { + let cell_val = x.at(s).at(m) + + let content = if cell_val != none { + let count = cell_val.len() + if s == 0 { + text(size: 7pt, $emptyset$) + } else if count <= 2 { + let parts_str = cell_val.map(p => { + "[" + p.map(str).join(",") + "]" + }).join(",") + text(size: 6pt, $#{parts_str}$) + } else { + text(size: 8pt, $#count$) + } + } else { + $dot$ + } + + // Check if changed + let is_new = if diff_mask != none and s < diff_mask.len() { + let mask_row = diff_mask.at(s) + if mask_row != none and m < mask_row.len() { + mask_row.at(m) + } else { false } + } else { false } + + if is_new { + cells.push(rect(fill: yellow.transparentize(70%), inset: 1.5pt, width: 11mm, height: 8mm)[#content]) + } else { + cells.push(rect(stroke: 0.5pt, inset: 1.5pt, width: 11mm, height: 8mm)[#content]) + } + } + rows.push(cells) + } + + // Build table manually + table( + columns: calc.min(n + 1, 6), + rows: calc.min(n + 1, 6), + stroke: none, + ..rows.flatten() + ) + } + + let A_h = partitions => { + if partitions != none { + let count = partitions.len() + if count == 1 and partitions.at(0).len() == 0 { + text(size: 9pt, $"1 partition: []"$) + } else { + let display = partitions.slice(0, calc.min(7, count)).map(p => { + "[" + p.map(str).join(",") + "]" + }).join(", ") + if count > 7 { + display = display + ", ..." + } + text(size: 8pt, [$#count "partitions:" #display$]) + } + } else { + $bot$ + } + } + + mapcode-viz( + rho, + F, + pi(inst_n), + X_h: X_h, + A_h: A_h, + pi_name: [$pi$], + dim: 2, + group-size: 3, + cell-size: 70mm, + scale-fig: 65%, + )(inst_n) +} +$ +) + +*Partition count (sequence A000041):* +$ +p(0)=1, p(1)=1, p(2)=2, p(3)=3, p(4)=5, p(5)=7, p(6)=11, p(7)=15, ... +$ + +*Complexity:* +- States computed: $O(n^2)$ - all pairs $(s, m)$ where $0 <= s, m <= n$ +- Per state: $O(p(s))$ - number of partitions to store +- Total: exponential in worst case, but tractable for moderate $n$ \ No newline at end of file diff --git a/algorithms/Mergesort.typ b/algorithms/Mergesort.typ new file mode 100644 index 0000000..b48bbe3 --- /dev/null +++ b/algorithms/Mergesort.typ @@ -0,0 +1,208 @@ +#import "../lib/style.typ": * +#import "../lib/mapcode.typ": * + +== Mergesort +#set math.equation(numbering: none) + +Sort an array of integers using the bottom-up mergesort algorithm. Elements are progressively merged from smaller sorted segments into larger ones. + +Formal definition: + +Given array $a = [a_0, a_1, ..., a_(n-1)]$, produce sorted array $a' = [a'_0, a'_1, ..., a'_(n-1)]$ where: +$ +forall i < j: a'_i <= a'_j quad "and" quad {a'_i} = {a_i} +$ + +Bottom-up approach: +- Start: treat each element as a sorted segment of length 1 +- Iterate: merge pairs of adjacent sorted segments +- Double segment length each iteration +- Terminate: when segment length exceeds array size + +Examples: +- $"mergesort"([5,2,8,1]) -> [1,2,5,8]$ +- $"mergesort"([9,3,7,5,6]) -> [3,5,6,7,9]$ + +*As mapcode:* + +_primitives_: Binary `merge` operator combines sorted sequences. Strict: returns $bot$ if either input is $bot$. + +State: each iteration $i$ stores tuple $(w_i, a_i)$ where $w_i$ is merge width, $a_i$ is partially sorted array. + +$ I = a: "Seq"[ZZ] quad quad X &= NN -> (NN times "Seq"[ZZ])_bot quad quad A = "Seq"[ZZ]\ +rho(a) &= {0 -> (1, a), k -> bot | k > 0}\ +F(x_k) &= cases( + x_0 & "if " k = 0, + (w', a') & "if " x_(k-1) != bot "and" w < n, + x_(k-1) & "if " x_(k-1) != bot "and" w >= n +) \ +& "where " (w, a) = x_(k-1) ", " w' = 2w ", " a' = "merge_pairs"(a, w) \ +pi(x) &= a "where" (w, a) = "last"({x_k | x_k != bot "and" w >= n}) +$ + +Iteration count: $ceil(log_2 n)$ steps until $w >= n$. + +#let inst_arr = (5, 2, 8, 1, 6, 3); +#figure( + caption: [Mergesort using mapcode for input $a = #inst_arr$], +$ +#{ + // Combine two sorted sequences + let combine_sorted = (left, right) => { + let out = () + let (p, q) = (0, 0) + + while p < left.len() and q < right.len() { + if left.at(p) <= right.at(q) { + out.push(left.at(p)) + p += 1 + } else { + out.push(right.at(q)) + q += 1 + } + } + + // Append remaining + while p < left.len() { out.push(left.at(p)); p += 1 } + while q < right.len() { out.push(right.at(q)); q += 1 } + + out + } + + // Merge adjacent pairs with given width + let merge_pairs = (sequence, width) => { + let merged = () + let pos = 0 + let total = sequence.len() + + while pos < total { + let end_left = calc.min(pos + width, total) + let end_right = calc.min(pos + 2 * width, total) + + let left_part = sequence.slice(pos, end_left) + let right_part = if end_left < total { + sequence.slice(end_left, end_right) + } else { () } + + if right_part.len() > 0 { + merged += combine_sorted(left_part, right_part) + } else { + merged += left_part + } + + pos += 2 * width + } + + merged + } + + // ρ: Initial state with width=1 + let rho = sequence => { + let capacity = calc.ceil(calc.log(sequence.len(), base: 2)) + 3 + let states = () + for _ in range(capacity) { states.push(none) } + states.at(0) = (1, sequence) + states + } + + // F: Apply one merge iteration + let F_i = states => ((index,)) => { + if index == 0 { + return states.at(0) + } + + let prev = states.at(index - 1) + if prev == none { return none } + + let (width, arr) = prev + let n = arr.len() + + // Already complete + if width >= n { + return (width, arr) + } + + // Perform merge with doubled width + let next_arr = merge_pairs(arr, width) + (width * 2, next_arr) + } + let F = map_tensor(F_i, dim: 1) + + // π: Extract final sorted array + let pi = original => states => { + let n = original.len() + for idx in range(states.len() - 1, -1, step: -1) { + let state = states.at(idx) + if state != none { + let (width, arr) = state + if width >= n { return arr } + } + } + original + } + + // Display state as (width: [elements]) + let X_h = (states, diff_mask: none) => { + let rendered = () + + for idx in range(states.len()) { + let state = states.at(idx) + + let content = if state != none { + let (w, arr) = state + let elements = arr.map(x => str(x)).join(",") + $(w=#w: [#elements])$ + } else { + $bot$ + } + + // Check if this state changed + let changed = if diff_mask != none and idx < diff_mask.len() { + let mask_val = diff_mask.at(idx) + if type(mask_val) == array { + mask_val.any(b => b) + } else { + mask_val + } + } else { false } + + if changed { + rendered.push(rect(fill: yellow.transparentize(70%), inset: 3pt)[#content]) + } else { + rendered.push(rect(stroke: none, inset: 3pt)[#content]) + } + } + + $vec(delim: "[", ..rendered)$ + } + + let A_h = result => { + if result != none { + let vals = result.map(x => str(x)).join(",") + $[#vals]$ + } else { + $bot$ + } + } + + mapcode-viz( + rho, + F, + pi(inst_arr), + X_h: X_h, + A_h: A_h, + pi_name: [$pi$], + dim: 1, + group-size: 4, + cell-size: 22mm, + scale-fig: 80%, + )(inst_arr) +} +$ +) + +*Analysis:* +- Iterations: $O(log n)$ - width doubles each step +- Per iteration: $O(n)$ - scan entire array +- Total time: $O(n log n)$ +- Space: $O(log n)$ - one state per iteration \ No newline at end of file diff --git a/main.typ b/main.typ index 9952d9b..c6f63de 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/Mergesort.typ" +#pagebreak() +#include "algorithms/IntegerPartitions.typ" \ No newline at end of file