# 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