commit 2f620a2fe7714fe71ae7846d90c781bd9ce3a2fb Author: zekroTJA Date: Mon Oct 19 21:51:23 2026 +0200 init diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..31b0d20 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,98 @@ +# Context + +I'm learning Haskell. I'm very proficient in Go, Rust and Python, and have used TypeScript, Java and C#. +I have practical functional programming experience with OCaml and Elixir: immutability, recursion, +ADTs, pattern matching and higher-order functions are familiar. Don't explain those from scratch. + +When explaining a concept, relate it to Rust or OCaml where there's a close match +(e.g. `Maybe` ≈ `Option`, typeclasses ≈ traits). Keep explanations concise. + +# Goals + +I want to use Haskell for Advent of Code 2026. The core skills I need: + +- **Simple IO:** reading and parsing string data from files +- **String parsing:** parsing and managing strings +- **Data transformation:** mapping, filtering, folding, grouping +- **Terminal IO:** printing and formatting data for the terminal + +# Project + +- Build tool: cabal. Commands: `cabal build`, `cabal test`, `cabal repl`, `cabal run`. +- Exercises: `src/Exercises/LessonNN.hs`, tests in `test/Exercises/LessonNNSpec.hs` (hspec, hspec-discover). +- AoC solutions: `src/AoC/YYYY/DayNN.hs`; inputs in `inputs/YYYY/DayNN.txt` (gitignored, never commit them). +- Course files: `syllabus.md` (plan), `progress.md` (session log), `review.md` (spaced repetition). +- Lint and format: `hlint src/`, `ormolu --mode inplace $(git ls-files '*.hs')`. + +# Rules for you + +## Exercises and solutions + +- Never write solutions to exercises unless I explicitly say "show solution". +- You may write everything around the exercises: cabal config, module stubs with `undefined`, + type signatures, hspec tests, test data, and helper code that isn't part of the exercise. +- Never edit my solution code. Point to the line and explain; I make the change. +- Give hints in escalating steps, one step per message: concept → relevant function/type → partial code. +- After I've solved an exercise and tests pass, show one idiomatic reference solution to compare with mine, + and briefly point out the differences. + +## Errors and reviews + +- When I hit a compiler error, first ask what I think it means, or point me to the key part of the message. + Explain fully only if I'm still stuck. +- Review my code for idiomatic Haskell: pattern matching over `if`, folds over manual recursion, + no partial functions (`head`, `tail`, `!!`, `read`, `fromJust`), `Text` over `String` for real input, + strict folds and `Data.Map.Strict`. Order findings by importance; skip nitpicks hlint would catch. +- Verify claims about types, functions or behavior in `cabal repl` instead of relying on memory. + Check that functions exist in the libraries this project depends on. + +# Course workflow + +## Starting a session + +1. Read `syllabus.md`, `progress.md` and `review.md`. +2. Briefly state where we are: last session's date and topic, and today's lesson. +3. Warm-up (5–10 min): quiz me on items in `review.md` that are due today or earlier. + Short questions: predict a type, an output, or which function to use. One question at a time. +4. Continue with the first lesson in `syllabus.md` that isn't ticked, even if its date has passed. + Dates in the syllabus are guidance, not deadlines. + +## During a lesson + +- Teach the lesson's goals in small chunks, each followed by a quick GHCi experiment for me to run. + Use the lesson's **Bridge** notes; skip parts I clearly already know. +- Before the exercises, create the stubs and failing tests for that lesson's exercise list. +- If I'm moving fast, offer one harder bonus exercise. If I'm struggling, slow down and add a simpler + exercise before continuing. + +## Ending a session + +When I say "wrap up" (or the lesson's exercises are done): + +1. Check the lesson's **Check** criterion with 2–3 short questions or a mini-task. +2. Tick the lesson's box in `syllabus.md` only if I passed the check. Tick exercise boxes for completed exercises. + Don't change anything else in `syllabus.md` without asking me. +3. Append an entry to `progress.md` using the template below. +4. Update `review.md`: add new weak spots, and reschedule reviewed items. +5. Suggest a commit message; commit only if I agree. + +## progress.md entry template + +```markdown +## YYYY-MM-DD — Lesson N: + +- Covered: +- Exercises: / +- Struggled with: +- Next: +``` + +## review.md format + +A table with columns `Concept | Example question | Last reviewed | Interval (days) | Next review`. +Intervals: start at 1. Answered well → next interval (1 → 3 → 7 → 14 → 30). Answered poorly → back to 1. + +## Pace + +If I fall behind by 2 or more lessons, propose how to adjust the syllabus (merge or trim lessons) +instead of silently skipping content. diff --git a/docs/syllabus.md b/docs/syllabus.md new file mode 100644 index 0000000..f0c88d2 --- /dev/null +++ b/docs/syllabus.md @@ -0,0 +1,647 @@ +# Haskell Syllabus: Advent of Code 2026 Edition + +## Overview + +**Goal:** Solve Advent of Code 2026 in Haskell. Focus on four core skills: + +1. **Simple IO:** reading and parsing string data from files +2. **String parsing:** parsing and managing strings +3. **Data transformation:** mapping, filtering, folding, grouping +4. **Terminal IO:** printing and formatting data for the terminal + +Plus the techniques AoC puzzles keep asking for: grids, search, simulation, memoization and performance. + +**Background:** Proficient in Go, Rust and Python. Experienced with TypeScript, Java and C#. Prior functional programming in OCaml and Elixir. + +**Structure:** 24 lessons in 6 units, about 60–90 minutes each, worked through at my own pace. +Every 4th lesson is a practice session with real puzzles from past years. The next lesson is always the first unticked one. + +**Approach:** Skip what I already know (immutability, recursion, ADTs, pattern matching, closures). Spend the time on what is different in Haskell: purity and `IO`, laziness, typeclasses, `Text` vs `String`, monadic parsing, and writing efficient code without mutation. + +| Unit | Lessons | Theme | +| ---- | ------- | ----------------------------------------- | +| 1 | 1–4 | Setup and foundations | +| 2 | 5–8 | Data transformation | +| 3 | 9–12 | IO and basic parsing | +| 4 | 13–16 | Parser combinators and terminal output | +| 5 | 17–20 | AoC techniques: grids, search, simulation | +| 6 | 21–24 | Performance, toolkit and mock puzzle days | + +### How each session runs + +1. **Warm-up (5–10 min):** review items due in `review.md`. +2. **Concepts (20–30 min):** the lesson's topics, explained against Rust/OCaml where it helps. +3. **Exercises (30–45 min):** stubs with failing tests in `src/Exercises/LessonNN.hs`. I write the code; Claude gives hints, not solutions. +4. **Wrap-up (5 min):** check criterion, update `progress.md` and `review.md`, tick the lesson, commit. + +**Practice sessions** use past AoC puzzles. Solutions go in `src/AoC/YYYY/DayNN.hs`, puzzle inputs in `inputs/YYYY/DayNN.txt`. +AoC asks people not to share their puzzle inputs, so `inputs/` is in `.gitignore`. + +### Legend + +- `[ ]` not started · `[x]` done +- **Bridge:** how a concept maps to a language I already know +- **Check:** what I should be able to do without help at the end of the lesson + +--- + +## Unit 1: Setup and foundations + +### Lesson 1: Toolchain and AoC project setup + +- [ ] Lesson complete + +**Goals** + +- Install GHC, cabal and HLS via GHCup; editor integration working (type-on-hover, errors inline). +- Create the cabal project with a library, an executable and an hspec test suite (hspec-discover). +- Learn the GHCi loop: `:l`, `:r`, `:t`, `:i`, `:k`, `:doc`, `:set +s`. +- Install `hlint` and `ormolu`; know how to run them. +- Set up the repo layout: `src/Exercises/`, `src/AoC/`, `inputs/` (gitignored), `test/`. + +**Bridge:** cabal ≈ cargo (`cabal build`, `cabal run`, `cabal test`, `cabal repl`). Hackage ≈ crates.io. Hoogle ≈ docs.rs, but you can search by type signature. + +**Exercises** + +- [ ] Get `cabal test` green with one trivial test. +- [ ] Add `text`, `containers` and `megaparsec` as dependencies and import each once to confirm they build. +- [ ] Look up `[a] -> Int`, `(a -> b) -> [a] -> [b]` and `String -> [String]` on Hoogle. Note what you find. + +**Check:** I can create, build, test and REPL into the project from scratch. + +### Lesson 2: Syntax, types and functions + +- [ ] Lesson complete + +**Goals** + +- Function definitions, type signatures, currying, partial application, operator sections `(+ 1)`, `(/ 2)`. +- `where` vs `let`, guards, `case`, pattern matching on tuples and lists. +- Composition `(.)` and application `($)`; reading point-free code (and knowing when not to write it). +- `data`, `newtype`, `type`; records and record update syntax; deriving `Show`, `Eq`, `Ord`. +- `Maybe` and `Either` as the everyday error types. + +**Bridge** + +- `Maybe a` ≈ Rust `Option` / OCaml `'a option`. `Either e a` ≈ `Result` (note: error on the **left**). +- `data Dir = N | E | S | W` ≈ OCaml variant / Rust enum. +- `newtype` ≈ Rust tuple-struct newtype, zero runtime cost. +- `x & f` (from `Data.Function`) ≈ Elixir/OCaml `|>`. + +**Exercises** + +- [ ] Model a `Dir` type with `turnLeft`, `turnRight`, `opposite`, and `move :: Dir -> (Int, Int) -> (Int, Int)`. +- [ ] Write `safeDiv :: Int -> Int -> Maybe Int` and `parseDir :: Char -> Either String Dir`. +- [ ] Rewrite three small functions in point-free style, then decide which version reads better. + +**Check:** I can read a type signature and predict roughly what a function does before reading its body. + +### Lesson 3: Typeclasses, purity and laziness + +- [ ] Lesson complete + +**Goals** + +- Typeclasses as interfaces: `Show`, `Eq`, `Ord`, `Enum`, `Bounded`, `Num`, `Semigroup`, `Monoid`; writing an instance by hand. +- Constraints in signatures: `(Ord a, Show a) => [a] -> String`. +- Purity: the "pure core, thin IO shell" design that every AoC solution will follow. +- Laziness: infinite lists, `take`/`iterate`/`cycle`, and when laziness bites (space leaks from lazy folds). +- Partial functions to avoid: `head`, `tail`, `!!`, `read`, `fromJust`. Safe alternatives. + +**Bridge** + +- Typeclasses ≈ Rust traits (closest match), more than OCaml modules or Java interfaces. +- `Semigroup`/`Monoid` ≈ `impl Add` plus an identity; `<>` combines, `mempty` is the empty value. +- Laziness ≈ Rust iterators or Python generators, except it's the default everywhere. + +**Exercises** + +- [ ] Derive `Enum` and `Bounded` for `Dir` and use them to rewrite `turnRight` without listing every case. +- [ ] Write a `Monoid` instance for a `Stats` type (count, sum, min, max) and combine a list with `mconcat`. +- [ ] Predict, then verify in GHCi: `take 5 (filter even [1..])`, `length [undefined, undefined]`, `foldl (+) 0 [1..10^7]` vs `foldl' (+) 0 [1..10^7]` with `:set +s`. + +**Check:** I can explain why `foldl'` is preferred over `foldl`, and design a program as pure functions plus a small `main`. + +### Lesson 4: Practice session 1 + +- [ ] Lesson complete + +**Goals** + +- Solve first real puzzles with what I know, using `String`, `lines`, `words` and `read` for now. +- Establish the solution template: `parse`, `part1`, `part2`, `main` reading `inputs/YYYY/DayNN.txt`. +- Test against the example input from the puzzle text before running on the real input. + +**Exercises** + +- [ ] 2022 Day 1 (Calorie Counting), both parts +- [ ] 2024 Day 1 (Historian Hysteria), both parts +- [ ] Compare with the reference solutions afterwards; note idioms to adopt in `review.md`. + +**Check:** I can go from puzzle text to a correct answer for an easy puzzle, with example-based tests. + +--- + +## Unit 2: Data transformation + +### Lesson 5: Lists and the core toolkit + +- [ ] Lesson complete + +**Goals** + +- `map`, `filter`, `foldr`, `foldl'`, `zip`, `zipWith`, `zip3`, `concatMap`, `takeWhile`, `dropWhile`, `span`, `break`, `splitAt`. +- `sum`, `product`, `maximum`, `minimum`, `and`, `or`, `any`, `all`, `elem`, `lookup`. +- List comprehensions vs `map`/`filter` chains; comprehensions for generating combinations and pairs. +- `Data.List`: `sort`, `sortOn`, `sortBy`, `group`, `groupBy`, `nub`, `partition`, `transpose`, `tails`, `inits`, `isPrefixOf`, `isInfixOf`. +- `Data.Ord`: `comparing`, `Down` for descending sorts. + +**Bridge** + +- `foldr`/`foldl'` ≈ OCaml `List.fold_right`/`List.fold_left`, Rust `.fold()`. +- `sortOn (Down . snd)` ≈ Rust `sort_by_key(|x| Reverse(x.1))`. +- `[(x, y) | (x:ys) <- tails xs, y <- ys]` ≈ all unique pairs, like Python `itertools.combinations(xs, 2)`. + +**Exercises** + +- [ ] Top-N: given `[(String, Int)]`, return the 3 highest scores, ties broken by name. +- [ ] Run-length encoding: `"aaabcc"` → `[('a',3),('b',1),('c',2)]` using `group`. +- [ ] Sliding windows: `windows 3 [1..6]` → `[[1,2,3],[2,3,4],[3,4,5],[4,5,6]]`. +- [ ] All pairs of a list whose sum equals a target. +- [ ] Transpose a grid of characters and find columns where all characters are the same. + +**Check:** I can solve a typical "transform this list" task with library functions instead of hand-written recursion. + +### Lesson 6: Maps, sets and pipelines that can fail + +- [ ] Lesson complete + +**Goals** + +- `Data.Map.Strict` (qualified as `M`): `fromList`, `fromListWith`, `insertWith`, `adjust`, `alter`, `lookup`, `findWithDefault`, `toList`, `foldrWithKey`, `unionWith`. +- `Data.Set`: membership, union, intersection, difference; `Data.IntMap` for `Int` keys. +- Counting and grouping idioms: frequency tables, group-by-key. +- Functor and `fmap`/`<$>` on `Maybe`, `Either`, lists. +- `traverse`/`mapM` to turn `[Maybe a]` into `Maybe [a]` (fail fast); `mapMaybe` and `catMaybes` to drop failures. + +**Bridge** + +- `M.fromListWith (+)` ≈ Rust `entry().or_insert(0) += 1` or Python `Counter`. +- `traverse parse xs` ≈ Rust `xs.iter().map(parse).collect::, _>>()`. +- `>>=` on `Maybe` ≈ Rust `.and_then()`; `<$>` ≈ `.map()`. + +**Exercises** + +- [ ] Word frequency: count words in a string, case-insensitive, sorted by count desc. +- [ ] Group `(department, employee)` pairs into `Map String [String]`. +- [ ] Validate a list of raw strings into `Either String [Int]`, reporting the first bad value. +- [ ] Find items present in list A but not B, and in both, using `Set`. + +**Check:** I reach for `Map`/`Set` naturally, and I can chain `Maybe`/`Either` functions without nested `case` pyramids. + +### Lesson 7: State without mutation + +- [ ] Lesson complete + +**Goals** + +- Threading state through a fold: `foldl'` with a tuple or record accumulator. +- `scanl` for every intermediate state; `iterate` plus `!!`/`takeWhile` for repeated steps. +- `until` and fixpoint iteration: repeat a step until nothing changes. +- Cycle detection: store seen states in a `Map`, find the loop, skip ahead (a classic "run 1 billion steps" trick). +- Recognizing the `State` monad at a reading level (`get`, `put`, `modify`), without depending on it yet. + +**Bridge** + +- A fold with a record accumulator ≈ a Rust loop mutating a struct, written as a pure function `step :: State -> Input -> State`. +- `iterate step s0` ≈ an infinite Rust `std::iter::successors`. + +**Exercises** + +- [ ] Simulate a robot following `"RRUULD..."` instructions; return its final position and all visited positions. +- [ ] Find the first position visited twice. +- [ ] Apply a state transformation 1,000,000,000 times using cycle detection. + +**Check:** I can model any "simulate N steps" problem as a pure step function plus a fold or `iterate`. + +### Lesson 8: Practice session 2 + +- [ ] Lesson complete + +**Exercises** + +- [ ] 2022 Day 2 (Rock Paper Scissors), both parts: model the domain with ADTs and `Enum`. +- [ ] 2024 Day 2 (Red-Nosed Reports), both parts: windows, `all`, removing one element. +- [ ] 2021 Day 1 (Sonar Sweep), both parts: `zipWith` and sliding windows, ideally in two lines each. + +**Check:** I solve easy puzzles with idiomatic list and map functions, without manual recursion. + +--- + +## Unit 3: IO and basic parsing + +### Lesson 9: IO, do-notation and files + +- [ ] Lesson complete + +**Goals** + +- The `IO` type; `do`-notation; `<-` vs `let` inside `do`; `pure`/`return`. +- Reading input: `readFile`, `getContents`, `interact`; writing: `putStrLn`, `print`. +- Command-line arguments with `getArgs`; exiting with `System.Exit`. +- `mapM_`, `forM_`, `when`, `unless` from `Control.Monad`. +- An AoC runner: `cabal run aoc -- 2024 1` picks the day's module and input file. + +**Bridge** + +- `do` ≈ Rust `?`-chained code or OCaml `let*`; `<-` "unwraps" a monadic value. +- `interact :: (String -> String) -> IO ()` ≈ a whole stdin-to-stdout filter in one line. + +**Exercises** + +- [ ] `wc` clone: count lines, words and characters of a file given as an argument. +- [ ] Build the AoC runner: takes year and day, reads the input, prints part 1 and part 2 with timings. +- [ ] Print a usage message and exit with code 1 on bad arguments. + +**Check:** I can run any solved puzzle with one command, and my solution modules contain no IO. + +### Lesson 10: Text and robust input + +- [ ] Lesson complete + +**Goals** + +- Why `String` (`[Char]`) is slow, and when it's fine anyway (most AoC inputs are small). +- `Data.Text` and `Data.Text.IO` (qualified as `T`/`TIO`); `OverloadedStrings`; `T.pack`/`T.unpack`. +- Common operations: `T.lines`, `T.words`, `T.splitOn`, `T.strip`, `T.breakOn`, `T.chunksOf`, `T.isPrefixOf`, `T.replace`. +- Handling blank-line-separated blocks (`T.splitOn "\n\n"`), trailing newlines and Windows line endings. +- Handling IO errors: missing files, `try` with `IOException`, turning them into `Either`. + +**Bridge** + +- `String` ≈ a linked list of chars; `Text` ≈ Rust `String`/`&str`. +- `try (readFile p) :: IO (Either IOException String)` ≈ Rust `fs::read_to_string(p)` returning `Result`. + +**Exercises** + +- [ ] Port the runner to `Text` input. +- [ ] Parse blank-line-separated groups of numbers into `[[Int]]`. +- [ ] Read a `key = value` file into `Map Text Text`, ignoring blanks and `#` comments. +- [ ] Print a friendly error instead of crashing when an input file is missing. + +**Check:** I default to `Text` for input and never get tripped up by trailing newlines. + +### Lesson 11: Parsing without a library, and grids + +- [ ] Lesson complete + +**Goals** + +- Splitting and reading: `lines`, `words`, `T.splitOn`, `break`/`span` on a delimiter. +- `readMaybe` from `Text.Read`; `Data.Text.Read` (`decimal`, `signed`) for `Text`. +- `Data.Char`: `isDigit`, `isSpace`, `isAlpha`, `digitToInt`. +- Extracting all integers from a messy line (a very common AoC shortcut). +- Parsing character grids into `Map (Int, Int) Char` with `zip [0..]`. + +**Bridge** + +- `readMaybe s :: Maybe Int` ≈ Rust `s.parse::().ok()`. +- Grid parsing with nested `zip [0..]` ≈ Python `enumerate` inside `enumerate`. + +**Exercises** + +- [ ] `ints :: Text -> [Int]`: extract all (possibly negative) integers from `"x=-3, y=12 to 7"`. +- [ ] Parse lines like `move 3 from 1 to 2` into a `Command` type. +- [ ] Parse a character grid into `Map (Int, Int) Char`; find all coordinates of `#`. +- [ ] Parse a CSV-like file into records, reporting the line number of every malformed row. + +**Check:** I can turn any structured text into typed values with only `base`, `text` and `containers`. + +### Lesson 12: Practice session 3 + +- [ ] Lesson complete + +**Exercises** + +- [ ] 2022 Day 5 (Supply Stacks), both parts: a tricky two-section input with a drawn diagram. +- [ ] 2023 Day 2 (Cube Conundrum), both parts: nested separators. +- [ ] 2023 Day 1 (Trebuchet?!), both parts: string scanning with overlapping words. + +**Check:** I can parse irregular inputs by hand, and I know when that's getting too painful. + +--- + +## Unit 4: Parser combinators and terminal output + +### Lesson 13: Parser combinators, part 1 + +- [ ] Lesson complete + +**Goals** + +- The idea: a parser is a value, and parsers combine into bigger parsers. +- `Applicative` operators that make parsing readable: `<$>`, `<*>`, `*>`, `<*`, `<|>`. +- `megaparsec` basics: `Parsec Void Text`, `parse`, `errorBundlePretty`, `char`, `string`, `space`, `L.decimal`, `L.signed`, `sepBy`, `some`, `many`. +- Brief look at `ReadP` (in `base`, no dependency) for quick one-offs. + +**Bridge** + +- ≈ Rust `nom` or `chumsky`, OCaml `angstrom`. +- `Point <$> int <* char ',' <*> int` ≈ "parse an int, skip a comma, parse an int, build a Point". + +**Exercises** + +- [ ] Parse `"(3,4)"` into `Point 3 4`. +- [ ] Parse `"Game 12: 3 red, 5 blue; 1 green"` into a record with nested lists. +- [ ] Make a parser fail on purpose and read the `errorBundlePretty` output. + +**Check:** I can write a small combinator parser and explain what each operator does. + +### Lesson 14: Parser combinators, part 2 + +- [ ] Lesson complete + +**Goals** + +- Structuring a parser: lexeme and symbol helpers, whitespace handling, `try` and backtracking. +- `choice`, `optional`, `between`, `sepEndBy`, `manyTill`, `eof`, `anySingle`. +- Finding patterns inside noise: skipping junk until something parses. +- Parsing keywords into sum types; why `do`-notation also works inside parsers. +- A shared `AoC.Parse` module with my reusable helpers. + +**Bridge** + +- `try` ≈ explicit backtracking; megaparsec doesn't backtrack by default, for good error messages. + +**Exercises** + +- [ ] Parse arithmetic expressions with `+`, `*` and parentheses; evaluate them. +- [ ] Extract every valid `mul(a,b)` from a corrupted string (the core of 2024 Day 3). +- [ ] Parse a small INI file into `Map Text (Map Text Text)`. + +**Check:** I can parse a realistic, messy text format end to end with good error messages. + +### Lesson 15: Terminal output and debugging + +- [ ] Lesson complete + +**Goals** + +- `show` vs custom rendering; `Text.Printf.printf` with widths and precision (`%-10s %6.2f`). +- Padding and alignment: `T.justifyLeft`, `T.justifyRight`, `T.center`; computing column widths. +- Rendering a grid `Map (Int, Int) Char` back to text: essential for debugging simulations and for puzzles whose answer appears as letters on a grid. +- ANSI colors via escape codes (`"\ESC[31m"`) to highlight positions in a grid. +- `Debug.Trace` (`trace`, `traceShow`, `traceShowId`) for printing from pure code while debugging. + +**Bridge** + +- `printf "%-10s %5d\n" name n` ≈ Rust `println!("{:<10} {:>5}", name, n)`, Go `fmt.Printf`. +- `traceShowId x` ≈ Rust `dbg!(x)`. + +**Exercises** + +- [ ] Print a word-frequency result as an aligned table with a header and separator line. +- [ ] `renderGrid :: Map (Int, Int) Char -> Text`, with a highlighted set of positions in color. +- [ ] Animate a simulation from Lesson 7 in the terminal, frame by frame. +- [ ] Add per-part timing and nicely formatted output to the AoC runner. + +**Check:** I can visualize any grid or data structure in the terminal and debug pure code with `trace`. + +### Lesson 16: Practice session 4 + +- [ ] Lesson complete + +**Exercises** + +- [ ] 2024 Day 3 (Mull It Over), both parts, using megaparsec. +- [ ] 2023 Day 4 (Scratchcards), both parts: parsing plus a counting recurrence. +- [ ] 2022 Day 7 (No Space Left On Device), both parts: parse a terminal log into a tree or a `Map` of paths. + +**Check:** My default for any non-trivial input is a megaparsec parser written in minutes. + +--- + +## Unit 5: AoC techniques + +### Lesson 17: Grids and coordinates + +- [ ] Lesson complete + +**Goals** + +- Representations: `Map (Int, Int) Char`, `Set (Int, Int)` for sparse grids, `Data.Array` for dense ones; when to use which. +- Coordinate helpers: a `V2`-style point type with `Num` instance, neighbors (4 and 8), directions, bounds checks. +- Scanning in a direction until a condition holds; rotating and flipping grids with `transpose`/`reverse`. +- Flood fill and counting connected regions. + +**Bridge** + +- `Data.Array` ≈ an immutable `Vec`; `(//)` copies, so batch updates or use a `Map` for frequent changes. + +**Exercises** + +- [ ] Build `AoC.Grid` with `parseGrid`, `neighbors4`, `neighbors8`, `inBounds`, `renderGrid`. +- [ ] Count the regions of each letter in a grid with flood fill. +- [ ] Find all occurrences of a word in a grid in all 8 directions. + +**Check:** I have a grid module I trust, and grid puzzles feel routine. + +### Lesson 18: Search: BFS, DFS and Dijkstra + +- [ ] Lesson complete + +**Goals** + +- BFS with `Data.Sequence` as a queue and a `Set` of visited states. +- DFS via recursion or a list as a stack; enumerating all paths. +- Dijkstra with `Data.Set (cost, state)` as a priority queue (or the `psqueues` package). +- Searching over states, not just positions: `(position, direction, keysCollected)`. +- Generic search functions parameterized by a `next :: s -> [(s, cost)]` function. + +**Bridge** + +- `Data.Sequence` ≈ Rust `VecDeque`, but persistent; `Set.deleteFindMin` ≈ popping a `BinaryHeap` (with `Reverse`). + +**Exercises** + +- [ ] Shortest path through a maze with BFS. +- [ ] Count all distinct paths from start to end in a DAG. +- [ ] Shortest path in a weighted grid with Dijkstra, where turning costs extra. +- [ ] Generalize: `bfs :: Ord s => (s -> [s]) -> s -> Map s Int`. + +**Check:** I can write BFS and Dijkstra from memory, in a generic form I reuse. + +### Lesson 19: Memoization, recursion and number tricks + +- [ ] Lesson complete + +**Goals** + +- Memoization with a `Map` threaded through recursion, or the `State` monad for convenience. +- Lazy memoization: a lazily filled `Data.Array` or `Map` whose values reference each other (dynamic programming for free). +- Integer types: `Int` vs `Integer` (overflow!), `div`/`mod` vs `quot`/`rem`, `gcd`/`lcm`. +- Combinatorics helpers: subsequences, permutations, choosing k elements. +- Ranges and intervals: merging, splitting, counting overlaps without enumerating elements. + +**Bridge** + +- Lazy array DP ≈ a Python `@cache` decorator, without mutation. +- `Integer` ≈ Python's unbounded `int`; `Int` ≈ Rust `i64`, and it wraps silently on overflow. + +**Exercises** + +- [ ] Count ways to build a string from a set of patterns, using memoization. +- [ ] Solve a grid DP (min path sum) with a lazy array. +- [ ] Merge overlapping ranges and count the total length covered. +- [ ] Use `lcm` to find when several cycles line up. + +**Check:** When part 2 makes brute force impossible, I know which of these tools to reach for. + +### Lesson 20: Practice session 5 + +- [ ] Lesson complete + +**Exercises** + +- [ ] 2024 Day 4 (Ceres Search), both parts: grid scanning. +- [ ] 2022 Day 12 (Hill Climbing Algorithm), both parts: BFS. +- [ ] 2024 Day 10 (Hoof It), both parts: DFS and counting paths. + +**Check:** I solve medium grid and search puzzles with my own helper modules. + +--- + +## Unit 6: Performance, toolkit and mock days + +### Lesson 21: Performance + +- [ ] Lesson complete + +**Goals** + +- Compiling with `-O2`; why `cabal repl` is much slower than the compiled program. +- Strictness: `foldl'`, `Data.Map.Strict`, strict record fields (`!Int`), `BangPatterns`, `seq`. +- Faster containers: `Data.IntMap`/`IntSet`, `Data.Array.Unboxed`, `Data.Vector`. +- Encoding coordinates as a single `Int` key for speed. +- Basic profiling: `+RTS -s` for memory and GC stats. + +**Bridge** + +- Space leaks from laziness have no Rust equivalent; strict fields ≈ making sure values are computed, not just promised. + +**Exercises** + +- [ ] Take a slow solution from earlier and make it at least 10× faster; record each step's effect. +- [ ] Fix a deliberate space leak found with `+RTS -s`. + +**Check:** I can explain why a Haskell solution is slow and fix the common causes. + +### Lesson 22: Personal AoC toolkit + +- [ ] Lesson complete + +**Goals** + +- Consolidate reusable modules: `AoC.Parse`, `AoC.Grid`, `AoC.Search`, `AoC.Util` (counting, windows, ranges). +- A day template generator: creates `DayNN.hs`, an example test file and an input placeholder. +- Tests for the toolkit, so I can trust it under pressure. +- Optional: fetch inputs automatically with a session cookie (stored outside the repo). + +**Exercises** + +- [ ] Review all practice solutions; move repeated helpers into the toolkit. +- [ ] Write the day template generator. +- [ ] Re-solve one earlier puzzle using only the toolkit and time it. + +**Check:** Starting a new day takes one command, and common helpers are one import away. + +### Lesson 23: Mock puzzle day 1 + +- [ ] Lesson complete + +**Goals** + +- Simulate a real AoC day: read the puzzle, parse, solve part 1, then part 2, timing each phase. +- Hints only when stuck for more than 15 minutes. + +**Exercises** + +- [ ] 2024 Day 6 (Guard Gallivant), both parts: simulation, cycle detection, performance in part 2. +- [ ] Note the time spent on parsing vs logic vs debugging. + +**Check:** I can complete a mid-level puzzle independently. + +### Lesson 24: Mock puzzle day 2 and readiness check + +- [ ] Lesson complete + +**Exercises** + +- [ ] One unseen puzzle from 2023 or 2024 chosen by Claude, at a medium difficulty, solved under the same rules. +- [ ] Retrospective in `progress.md`: strengths, weak spots, what to review before the event starts. +- [ ] Check that the toolchain, runner, template and toolkit all work on a fresh clone. + +**Check:** I'm ready for Advent of Code. + +--- + +## During Advent of Code + +- Last year's event had 12 puzzles instead of the usual 25. Check the AoC website for this year's format and its rules on using AI tools. +- Try every puzzle alone first. Claude gives hints only (as defined in `CLAUDE.md`), never solutions while the puzzle is fresh. +- After solving, ask for a code review and a reference solution, and add new helpers to the toolkit. +- Keep logging in `progress.md`; add new techniques to `review.md`. + +--- + +## Reference + +### Essential imports + +```haskell +{-# LANGUAGE OverloadedStrings #-} + +import qualified Data.Map.Strict as M +import qualified Data.Set as S +import qualified Data.Sequence as Seq +import qualified Data.Text as T +import qualified Data.Text.IO as TIO +import Data.List (foldl', sortOn, group, sort, transpose, tails) +import Data.Ord (Down (..), comparing) +import Data.Maybe (mapMaybe, fromMaybe) +import Data.Char (isDigit, isSpace) +import Text.Read (readMaybe) +import Text.Printf (printf) +import Control.Monad (forM_, when, unless) +import Debug.Trace (traceShowId) +``` + +### Solution template + +```haskell +module AoC.Y2024.Day01 (solve) where + +import Data.Text (Text) + +data Input = Input -- puzzle-specific + +parse :: Text -> Input +parse = undefined + +part1 :: Input -> Int +part1 = undefined + +part2 :: Input -> Int +part2 = undefined + +solve :: Text -> (String, String) +solve raw = let x = parse raw in (show (part1 x), show (part2 x)) +``` + +### Resources + +- _Learn You a Haskell for Great Good!_ (quick syntax refresher) +- _Programming in Haskell_ by Graham Hutton (chapters on parsers and IO) +- Hoogle for searching functions by type +- megaparsec tutorial by Mark Karpov +- Past AoC puzzles at adventofcode.com, and r/adventofcode for discussions after solving