init
This commit is contained in:
commit
2f620a2fe7
2 files changed
+745
No files matched your search
@@ -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<T>` / OCaml `'a option`. `Either e a` ≈ `Result<T, E>` (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::<Result<Vec<_>, _>>()`.
|
||||
- `>>=` 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::<i32>().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
|
||||
Reference in new issue
Block a user