Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cj81499/advent-of-code/llms.txt

Use this file to discover all available pages before exploring further.

Advent of Code 2021 sent participants on an underwater adventure to retrieve Santa’s sleigh keys from the ocean floor. Of the 25 days published, 21 days are fully solved (both parts), with Day 19 (Beacon Scanner) unsolved and Days 23 and 25 missing their second part. All solved days include passing tests and type-checked implementations. The year features a satisfying mix of simulation puzzles, recursive algorithms, and binary packet parsing.

Progress

DayTitlePart 1Part 2Tests
01Sonar Sweep
02Dive!
03Binary Diagnostic
04Giant Squid
05Hydrothermal Venture
06Lanternfish
07The Treachery of Whales
08Seven Segment Search
09Smoke Basin
10Syntax Scoring
11Dumbo Octopus
12Passage Pathing
13Transparent Origami
14Extended Polymerization
15Chiton
16Packet Decoder
17Trick Shot
18Snailfish
19Beacon Scanner
20Trench Map
21Dirac Dice
22Reactor Reboot
23Amphipod
24Arithmetic Logic Unit
25Sea Cucumber

Notable Solutions

Day 11 — Dumbo Octopus

The grid is stored as a dict[Point, int] mapping (x, y) coordinates to energy levels. Each step increments every cell, then propagates flash energy to all eight neighbours of any cell whose value exceeds 9. Part 1 counts total flashes over 100 steps; Part 2 uses itertools.count to find the first step where every octopus flashes simultaneously.
def step(grid: Grid) -> tuple[int, Grid]:
    new_grid = {p: n + 1 for p, n in grid.items()}
    flashed = set[Point]()
    while new_flashes := {p for p, n in new_grid.items() if p not in flashed and n > 9}:
        for adj_p in itertools.chain.from_iterable(map(adj, new_flashes)):
            if adj_p in new_grid:
                new_grid[adj_p] += 1
        flashed |= new_flashes
    new_grid = {p: 0 if p in flashed else n for p, n in new_grid.items()}
    return len(flashed), new_grid

Day 16 — Packet Decoder

The solution models the BITS transmission as a class hierarchy: an abstract Packet base with LiteralValuePacket and OperatorPacket subclasses. Each packet type implements an evaluate() method, so the full expression tree can be evaluated with a single recursive call. Part 1 sums all version numbers by walking the tree; Part 2 evaluates the root packet.

Day 21 — Dirac Dice

Part 1 simulates the deterministic 100-sided die directly. Part 2 uses top-down memoization via @functools.cache on a frozen State dataclass. All 27 possible three-roll combinations (3 dice × 3 faces) are explored at each turn, and win counts are accumulated recursively without re-computing duplicate states.
@functools.cache
def helper(state: State) -> list[int]:
    wins = [0, 0]
    for rolls in itertools.product(range(1, 4), range(1, 4), range(1, 4)):
        new_pos = (state.active_pos - 1 + sum(rolls)) % 10 + 1
        new_score = state.active_score + new_pos
        if new_score >= 21:
            wins[state.active] += 1
        else:
            result = helper(State(state.inactive_pos, state.inactive_score,
                                  new_pos, new_score, (state.active + 1) % 2))
            wins[0] += result[0]
            wins[1] += result[1]
    return wins
Puzzle inputs are personal and are not stored in this repository. Solutions fetch input at runtime using aocd (aocd.data).

Build docs developers (and LLMs) love