Iterative Levenshtein DP with rolling 1D arrays for O(min(m,n))
space. Distances:
kitten -> sitting : 3
saturday -> sunday : 3
abc -> abc : 0
"" -> abcde : 5
intention -> execution : 5
----------------------------
total : 16
Complementary to the existing levenshtein.ml which uses the
exponential recursive form (only sums tiny strings); this one is
the practical iterative variant used for real ED.
Tests the recently-fixed <- with bare `if` rhs:
curr.(j) <- (if m1 < c then m1 else c) + 1
153 baseline programs total.
Array-backed binary min-heap with explicit size tracking via ref:
let push a size x =
a.(!size) <- x; size := !size + 1; sift_up a (!size - 1)
let pop a size =
let m = a.(0) in
size := !size - 1;
a.(0) <- a.(!size);
sift_down a !size 0;
m
Push [9;4;7;1;8;3;5;2;6], pop nine times -> 1,2,3,4,5,6,7,8,9.
Fold-as-decimal: ((((((((1*10+2)*10+3)*10+4)*10+5)*10+6)*10+7)*10+8)*10+9 = 123456789.
Tests recursive sift_up + sift_down, in-place array swap,
parent/lchild/rchild index arithmetic, combined push/pop session
with refs.
152 baseline programs total.
Polynomial rolling hash mod 1000003 with base 257:
- precompute base^(m-1)
- slide window updating hash in O(1) per step
- verify hash match with O(m) memcmp to skip false positives
rolling_match "abcabcabcabcabcabc" "abc" = 6
Six non-overlapping copies of "abc" at positions 0,3,6,9,12,15.
Tests `for _ = 0 to m - 2 do … done` unused loop variable
(uses underscore wildcard pattern), Char.code arithmetic, mod
arithmetic with intermediate negative subtractions, complex nested
if/begin branching with inner break-via-flag.
151 baseline programs total.
Classic CLRS Huffman code example. ADT:
type tree = Leaf of int * char | Node of int * tree * tree
Build by repeatedly merging two lightest trees (sorted-list pq):
let rec build_tree lst = match lst with
| [t] -> t
| a :: b :: rest ->
let merged = Node (weight a + weight b, a, b) in
build_tree (insert merged rest)
weighted path length (= total Huffman bits):
leaves {(5,a) (9,b) (12,c) (13,d) (16,e) (45,f)} -> 224
Tests sum-typed ADT with mixed arities, `function` keyword
pattern matching, recursive sorted insert, depth-counting recursion.
150 baseline programs total.
Previously `a.(i) <- if c then x else y` failed with
"unexpected token keyword if" because parse-binop-rhs called
parse-prefix for the rhs, which doesn't accept if/match/let/fun.
Real OCaml allows full expressions on the rhs of <-/:=. Fix:
special-case prec-1 ops in parse-binop-rhs to call parse-expr-no-seq
instead of parse-prefix. The recursive parse-binop-rhs with
min-prec restored after picks up any further chained <- (since both
ops are right-associative with no higher-prec binops above them).
Manacher baseline updated to use bare `if` on rhs of <-,
removing the parens workaround from iter 235. 607/607 regressions
remain clean.
Manacher's algorithm: insert # separators (length 2n+1) to unify
odd/even cases, then maintain palindrome radii p[] alongside a
running (center, right) pair to skip work via mirror reflection.
Linear time.
manacher "babadaba" = 7 (* witness: "abadaba", positions 1..7 *)
Note: requires parenthesizing the if-expression on the rhs of <-:
p.(i) <- (if pm < v then pm else v)
Real OCaml parses bare `if` at <-rhs since the rhs is at expr
level; our parser places <-rhs at binop level which doesn't include
`if` / `match` / `let`. Workaround until we relax the binop
RHS grammar.
149 baseline programs total.
Floyd-Warshall all-pairs shortest path with triple-nested for-loop:
for k = 0 to n - 1 do
for i = 0 to n - 1 do
for j = 0 to n - 1 do
if d.(i).(k) + d.(k).(j) < d.(i).(j) then
d.(i).(j) <- d.(i).(k) + d.(k).(j)
done
done
done
Graph (4 nodes, directed):
0->1 weight 5, 0->3 weight 10, 1->2 weight 3, 2->3 weight 1
Direct edge 0->3 = 10, but path 0->1->2->3 = 5+3+1 = 9.
Tests 2D array via Array.init with closure, nested .(i).(j) read
+ write, triple-nested for, in-place mutation under aliasing.
148 baseline programs total.
Modified merge sort that counts inversions during the merge step:
when an element from the right half is selected, the remaining
elements of the left half (mid - i + 1) all form inversions with
that right element.
count_inv [|8; 4; 2; 1; 3; 5; 7; 6|] = 12
Inversions of [8;4;2;1;3;5;7;6]:
with 8: (8,4)(8,2)(8,1)(8,3)(8,5)(8,7)(8,6) = 7
with 4: (4,2)(4,1)(4,3) = 3
with 2: (2,1) = 1
with 7: (7,6) = 1
total = 12
Tests: let rec ... and ... mutual recursion, while + ref + array
mutation, in-place sort with auxiliary scratch array.
145 baseline programs total.
Kahn's algorithm BFS topological sort:
let topo_sort n adj =
let in_deg = Array.make n 0 in
for i = 0 to n - 1 do
List.iter (fun j -> in_deg.(j) <- in_deg.(j) + 1) adj.(i)
done;
let q = Queue.create () in
for i = 0 to n - 1 do
if in_deg.(i) = 0 then Queue.push i q
done;
let count = ref 0 in
while not (Queue.is_empty q) do
let u = Queue.pop q in
count := !count + 1;
List.iter (fun v ->
in_deg.(v) <- in_deg.(v) - 1;
if in_deg.(v) = 0 then Queue.push v q
) adj.(u)
done;
!count
Graph: 0->{1,2}; 1->{3}; 2->{3,4}; 3->{5}; 4->{5}; 5.
Acyclic, so all 6 nodes can be ordered.
Tests Queue.{create,push,pop,is_empty}, mutable array via closure.
144 baseline programs total.
Standard 1D 0/1 knapsack DP with reverse inner loop:
let knapsack values weights cap =
let n = Array.length values in
let dp = Array.make (cap + 1) 0 in
for i = 0 to n - 1 do
let v = values.(i) and w = weights.(i) in
for c = cap downto w do
let take = dp.(c - w) + v in
if take > dp.(c) then dp.(c) <- take
done
done;
dp.(cap)
values: [|6; 10; 12; 15; 20|]
weights: [|1; 2; 3; 4; 5|]
knapsack v w 8 = 36 (* take items with weights 1, 2, 5 *)
Tests for-downto + array literal access in the same hot loop.
143 baseline programs total.
Classic 2D DP for longest common subsequence, optimized to use
two rolling 1D arrays (prev / curr) for O(min(m,n)) space:
for i = 1 to m do
for j = 1 to n do
if s1.[i-1] = s2.[j-1] then curr.(j) <- prev.(j-1) + 1
else if prev.(j) >= curr.(j-1) then curr.(j) <- prev.(j)
else curr.(j) <- curr.(j-1)
done;
for j = 0 to n do prev.(j) <- curr.(j) done
done;
prev.(n)
lcs "ABCBDAB" "BDCAB" = 4
Two valid LCS witnesses: BCAB and BDAB.
Avoids Array.make_matrix (not in our runtime) by manual rolling.
142 baseline programs total.
Disjoint-set union with path compression:
let make_uf n = Array.init n (fun i -> i)
let rec find p x =
if p.(x) = x then x
else begin let r = find p p.(x) in p.(x) <- r; r end
let union p x y =
let rx = find p x in let ry = find p y in
if rx <> ry then p.(rx) <- ry
After unioning (0,1), (2,3), (4,5), (6,7), (0,2), (4,6):
{0,1,2,3} {4,5,6,7} {8} {9} --> 4 components.
Tests Array.init with closure, recursive find, in-place .(i)<-r.
139 baseline programs total.
Hoare quickselect with Lomuto partition: recursively narrows the
range to whichever side contains the kth index. Mutates the array
in place via .(i)<-v. The median (k=4) of [7;2;9;1;5;6;3;8;4] is 5.
let rec quickselect arr lo hi k =
if lo = hi then arr.(lo)
else begin
let pivot = arr.(hi) in
let i = ref lo in
for j = lo to hi - 1 do
if arr.(j) < pivot then begin
let t = arr.(!i) in
arr.(!i) <- arr.(j); arr.(j) <- t;
i := !i + 1
end
done;
...
end
Exercises array literal syntax + in-place mutation in the same
program, ensuring [|...|] yields a mutable backing.
138 baseline programs total.
Added parser support for OCaml array literal syntax:
[| e1; e2; ...; en |] --> Array.of_list [e1; e2; ...; en]
[||] --> Array.of_list []
Desugaring keeps the array representation unchanged (ref-of-list)
since Array.of_list is a no-op constructor for that backing.
Tokenizer emits [, |, |, ] as separate ops; parser detects [ followed
by | and enters array-literal mode, terminating on |].
Baseline lis.ml exercises the syntax:
let lis arr =
let n = Array.length arr in
let dp = Array.make n 1 in
for i = 1 to n - 1 do
for j = 0 to i - 1 do
if arr.(j) < arr.(i) && dp.(j) + 1 > dp.(i) then
dp.(i) <- dp.(j) + 1
done
done;
let best = ref 0 in
for i = 0 to n - 1 do
if dp.(i) > !best then best := dp.(i)
done;
!best
lis [|10; 22; 9; 33; 21; 50; 41; 60; 80|] = 6
137 baseline programs total.
Classic Josephus problem solved with the standard recurrence:
let rec josephus n k =
if n = 1 then 0
else (josephus (n - 1) k + k) mod n
josephus 50 3 + 1 = 11
50 people stand in a circle, every 3rd is eliminated; the last
survivor is at position 11 (1-indexed). Tests recursion + mod.
136 baseline programs total.
Counts integer partitions via classic DP:
let partition_count n =
let dp = Array.make (n + 1) 0 in
dp.(0) <- 1;
for k = 1 to n do
for i = k to n do
dp.(i) <- dp.(i) + dp.(i - k)
done
done;
dp.(n)
partition_count 15 = 176
Tests Array.make, .(i)<-/.(i) array access, nested for-loops, refs.
135 baseline programs total.
Uses Euclid's formula: for coprime m > k of opposite parity, the
triple (m^2 - k^2, 2mk, m^2 + k^2) is a primitive Pythagorean.
let count_primitive_triples n =
let c = ref 0 in
for m = 2 to 50 do
let kk = ref 1 in
while !kk < m do
if (m - !kk) mod 2 = 1 && gcd m !kk = 1 then begin
let h = m * m + !kk * !kk in
if h <= n then c := !c + 1
end;
kk := !kk + 1
done
done;
!c
count_primitive_triples 100 = 16
The 16 triples include the classics (3,4,5), (5,12,13), (8,15,17),
(7,24,25), and end with (65,72,97).
134 baseline programs total.
A Harshad (or Niven) number is divisible by its digit sum:
let count_harshad limit =
let c = ref 0 in
for n = 1 to limit do
if n mod (digit_sum n) = 0 then c := !c + 1
done;
!c
count_harshad 100 = 33
All single-digit numbers (1..9) qualify trivially. Plus 10, 12, 18,
20, 21, 24, 27, 30, 36, 40, 42, 45, 48, 50, 54, 60, 63, 70, 72, 80,
81, 84, 90, 100 (24 more) = 33 total under 100.
133 baseline programs total.
Walks digits via mod 10 / div 10, accumulating the reversed value:
let reverse_int n =
let m = ref n in
let r = ref 0 in
while !m > 0 do
r := !r * 10 + !m mod 10;
m := !m / 10
done;
!r
reverse 12345 + reverse 100 + reverse 7
= 54321 + 1 + 7
= 54329
Trailing zeros collapse (reverse 100 = 1, not 001).
132 baseline programs total.
Walks the pin-knockdown list applying strike/spare bonuses through
a 10-frame counter:
strike (10): score 10 + next 2 throws, advance i+1
spare (a + b = 10): score 10 + next 1 throw, advance i+2
open (a + b < 10): score a + b, advance i+2
Frame ten special-cases are handled implicitly: the input includes
bonus throws naturally and the while-loop terminates after frame 10.
bowling_score [10; 7; 3; 9; 0; 10; 0; 8; 8; 2; 0; 6;
10; 10; 10; 8; 1]
= 20+19+9+18+8+10+6+30+28+19
= 167
131 baseline programs total.
Single-helper tail-recursive loop threading an accumulator:
let factorial n =
let rec go n acc =
if n <= 1 then acc
else go (n - 1) (n * acc)
in
go n 1
factorial 12 = 479_001_600
Companion to factorial.ml (10! = 3628800 via doubly-recursive
style); same answer-shape, different evaluator stress: this version
has constant stack depth.
130 baseline programs total — milestone.