Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Algorithms & data structures

Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).

bubble_sort

Real sliding swaps; array + compare + swap.

// Bubble Sort — real sliding swaps. `array` gives fixed slot boxes and value
// cells; `compare(a, i, j)` flashes the values now in slots i and j, and
// `swap(a, i, j)` slides them past each other into the swapped slots. `swap`
// carries the array's occupancy forward, so a whole chain of swaps composes
// correctly (no `say`). We sort [3, 1, 2] -> [1, 2, 3].
//
//   manic examples/bubble_sort.manic

title("Bubble Sort");
canvas("16:9");

text(head, (cx, 130), "compare neighbours, swap if out of order");
display(head);  color(head, cyan);  size(head, 26);  hidden(head);
text(cap, (cx, 560), "");  color(cap, dim);  size(cap, 26);

array(a, "3 1 2", (cx, 360), 100, 100);

show(head, 0.5);
say(cap, "an unsorted array");
wait(0.6);

section("Pass 1");
say(cap, "compare slots 0 and 1:  3 > 1, swap");
compare(a, 0, 1);
swap(a, 0, 1);

say(cap, "compare slots 1 and 2:  3 > 2, swap");
compare(a, 1, 2);
swap(a, 1, 2);

section("Pass 2");
say(cap, "compare slots 0 and 1:  1 < 2, ok");
compare(a, 0, 1, lime);
say(cap, "compare slots 1 and 2:  2 < 3, ok");
compare(a, 1, 2, lime);

section("Sorted");
say(cap, "1  2  3 -- done");
recolor(a.cells, lime, 0.4);
par { pulse(a.c0);  pulse(a.c1);  pulse(a.c2); }
wait(1.4);

two_pointer

lo/hi index carets scanning inward on a sorted array.

// Two Pointers — the `pointer`/`pointat` primitive. `pointer(id, arr, slot, label)`
// drops a caret under a slot; `pointat(id, arr, slot)` slides it to another slot
// (its label follows). Pointers track slot *positions*, so they stay put as
// values move. Here `lo`/`hi` scan inward on a sorted array.
//
//   manic examples/two_pointer.manic

title("Two Pointers");
canvas("16:9");

text(head, (cx, 120), "two pointers scan toward the middle");
display(head);  color(head, cyan);  size(head, 26);  hidden(head);
text(cap, (cx, 560), "");  color(cap, dim);  size(cap, 26);

array(a, "1 3 5 7 9 11", (cx, 340), 92, 92);
pointer(lo, a, 0, "lo");
pointer(hi, a, 5, "hi");

show(head, 0.5);
say(cap, "start at both ends");
wait(0.5);
compare(a, 0, 5);

say(cap, "step both inward");
par { pointat(lo, a, 1);  pointat(hi, a, 4); }
compare(a, 1, 4);

par { pointat(lo, a, 2);  pointat(hi, a, 3); }
compare(a, 2, 3);

say(cap, "the pointers have met");
wait(1.2);

stack_queue

LIFO stack + FIFO queue, with action-point carets.

// Stack & Queue — dynamic structures, with carets marking WHERE each op acts.
// `push`/`pop` (stack, LIFO, grows up) and `enqueue`/`dequeue` (queue, FIFO,
// grows right) are mutating verbs: they add a cell and animate it in, tracking
// occupancy so a chain of ops composes. A `caret` marks the action point and is
// `move`d in step with each op so it rides the changing top / back.
//
//   manic examples/stack_queue.manic

title("Stack & Queue");
canvas("16:9");

let sx = 300;   let sy = 500;    // stack anchor (bottom cell centre)
let qx = 780;   let qy = 300;    // queue anchor (front cell centre)
let cw = 84;    let ch = 64;     // cell size
let stx = sx + 62;               // stack "top" caret sits right of the column

text(head, (cx, 80), "LIFO stack, FIFO queue");
display(head);  color(head, cyan);  size(head, 28);  hidden(head);
text(cap, (cx, 660), "");  color(cap, dim);  size(cap, 24);

text(sl, (sx, sy + 66), "stack: push / pop");  color(sl, lime);  size(sl, 22);
text(ql, (qx + cw, qy - 118), "queue: enqueue / dequeue");  color(ql, cyan);  size(ql, 22);

stack(st, (sx, sy), cw, ch);
queue(qu, (qx, qy), cw, ch);

// action-point markers
caret(top, (stx, sy), "top", left);        // rides the top of the stack
caret(front, (qx, qy - 52), "front", down); // fixed: dequeue leaves here
caret(back, (qx, qy + 52), "back", up);     // rides the back of the queue

show(head, 0.5);

section("Stack");
say(cap, "push 5, 3, 8 — each lands on top, the top caret rises");
push(st, "5");
par { push(st, "3");  move(top, (stx, sy - ch)); }
par { push(st, "8");  move(top, (stx, sy - 2*ch)); }
say(cap, "pop — the top value (8) leaves, the caret drops back");
par { pop(st);  move(top, (stx, sy - ch)); }
wait(0.5);

section("Queue");
say(cap, "enqueue A, B, C — they join at the back caret");
enqueue(qu, "A");
par { enqueue(qu, "B");  move(back, (qx + cw, qy + 52)); }
par { enqueue(qu, "C");  move(back, (qx + 2*cw, qy + 52)); }
say(cap, "dequeue — the front leaves, the rest advance, back shifts in");
par { dequeue(qu);  move(back, (qx + cw, qy + 52)); }
par { dequeue(qu);  move(back, (qx, qy + 52)); }

say(cap, "stack: in and out at the top.  queue: in at back, out at front.");
wait(1.2);

linked_list

Singly / doubly / circular — classic node anatomy + pointer re-threading.

// Linked List — classic anatomy, three variations. A node is a framed box split
// into compartments: singly `[ data | .next ]`, doubly `[ .prev | data | next. ]`,
// where a pointer field carries a dot its arrow starts from. `head` marks the
// entry node; the tail's `next` ends at `NULL` (singly/doubly) or curves back to
// the head (circular). `insert`/`remove` re-thread the pointers — no shifting.
//
//   manic examples/linked_list.manic

title("Linked List");
canvas("16:9");

text(head, (cx, 56), "node = data + pointer field(s); head in, NULL or loop at the tail");
display(head);  color(head, cyan);  size(head, 24);  hidden(head);
text(cap, (cx, 700), "");  color(cap, dim);  size(cap, 24);

text(t1, (150, 160), "singly");    color(t1, lime);   size(t1, 22);
text(t2, (150, 350), "doubly");    color(t2, lime);   size(t2, 22);
text(t3, (150, 545), "circular");  color(t3, lime);   size(t3, 22);

list(sa, "3 8 5", (cx, 160), singly, 64, 50);
list(da, "3 8 5", (cx, 350), doubly, 64, 50);
list(ca, "3 8 5", (cx, 545), circular, 64, 50);

show(head, 0.5);
say(cap, "three classic variations of the same idea");
wait(0.9);

section("Insert");
say(cap, "insert 7 after node 1 in the doubly list — pointers re-thread, no shift");
insert(da, 1, "7");
wait(0.6);

section("Remove");
say(cap, "remove the head of the singly list — the list re-points past it");
remove(sa, 0);

say(cap, "data + pointers: the whole family from one primitive");
wait(1.4);

hashmap

Hash a key to a bucket; collisions chain on (separate chaining).

// Hash Map — separate chaining. `hashmap(id, n, (cx,cy))` draws n numbered
// buckets in a column; `put(id, key, val)` hashes the key to a bucket and chains
// the `key:val` entry on (collisions extend the chain); `get(id, key)` hashes,
// then scans that bucket's chain — each entry flashes until the key matches
// (lime) or the chain ends (bucket flashes magenta = miss).
//
// (Hash = sum of the key's bytes mod n. "cat" and "act" are anagrams, so they
//  collide — same bucket, chained.)
//
//   manic examples/hashmap.manic

title("Hash Map");
canvas("16:9");

text(head, (cx, 60), "separate chaining: hash the key, chain on collision");
display(head);  color(head, cyan);  size(head, 26);  hidden(head);
text(cap, (cx, 690), "");  color(cap, dim);  size(cap, 24);

hashmap(ht, 5, (360, 360), 128, 46);

show(head, 0.5);
say(cap, "put cat, dog, ok — each hashes to its bucket");
put(ht, "cat", "7");
put(ht, "dog", "3");
put(ht, "ok", "1");
wait(0.5);

section("Collision");
say(cap, "put act — same bytes as cat, so it collides and chains on");
put(ht, "act", "9");
wait(0.6);

section("Lookup");
say(cap, "get act — scan the chain in bucket 2 until the key matches");
get(ht, "act");
say(cap, "get xyz — hashes to a bucket, scans, falls off the end: miss");
get(ht, "xyz");
wait(1.2);