Secret Entrance
2026-01-12 Original Prompt December is was here, which means it’s Advent of Code season! There were some big changes this year, and I’m not just talking about the puzzles. Late last year, my wife and I adopted a baby! So as you can imagine, my December was totally shot as far as coding (or really thinking at all) went.
But I love AoC and writing these little posts. And now that my Favorite Media of the Year post is done, I’ve got just a bit of time on my hands.
I’ll be surprised if I get close to finishing all 12 of these puzzles, but let’s see where we (eventually) get. Either way, thanks for at least reading what I do publish!
If it’s your first time, here’s the gist for these posts (copied from last year):
There are a few goals:
- improve understanding of what’s available in the Python standard library
- produce clear, readable code
- get comfortable with non-trivial programming concepts
Readers are expected to have a basic understanding of programming fundamentals. Advent of Code isn’t the place to learn programming from scratch (Exercism’s bootcamp is a better place for that).
Each of my solutions will use my GitHub template, which handles some basic input parsing. It also has a good Ruff setup for linting and formatting. Feel free to fork this an adapt it for your needs! I didn’t include any utility functions in the template itself, but you’re encouraged to write and use your own. I’ve got a few (like for graph traversal) that I’ll reference in my solutions, but you can take whatever approach you’d like.
Let’s get to it!
Part 1
Toady relies on two basic concepts:
- strings are iterables
- the modulo (
%) operator
First, let’s parse.
Remember, I’m using a custom class to parse the input into a list of strings. That template is available to copy if you’d like a similar setup!
class Solution(StrSplitSolution): def part_1(self) -> int: for line in self.input: direction, *raw_distance = line distance = int("".join(raw_distance)) if direction == "L": distance *= -1We’re taking advantage of the fact that Python’s strings are iterables. So direction, *raw_distance = line grabs the first character and all the rest separately (using the spread operator, *), which is perfect for our use case. We also treat L rotations as negative, since they cause the number to go down.
Though we’re not actually tracking our position anywhere yet. Let’s fix that:
class Solution(StrSplitSolution): def part_1(self) -> int: current = 50 num_zeroes = 0
for line in self.input: ...
current = (current + distance) % 100
if current == 0: num_zeroes += 1
return num_zeroesWe update our position, current, with . The real key is the aforementioned %, which lets us cleanly wrap values outside the 0-100 range to be inside that range instead.
Every time we land on exactly 0, we increment a counter, eventually returning it for our answer! Nothing much to see here yet, just a nice little warmup.
Part 2
Little bit trickier now! Instead of using modulo to figure out where we eventually land, we have to know if/when we’re crossing 0, which means a bit of extra math.
I took a couple of passes on this, using Python’s floor division (//) to figure out how many times we passed 0. But I kept hitting little edge cases and as the code grew in complexity, my answers weren’t correct. Maybe I’m still thinking a little less clearly than I thought.
Luckily, we can still do it the simple way way and see if that works. We’ll make a big list of all the numbers we cross while rotating and count the zeroes. Listen, if it’s stupid and it works, then it’s not that stupid.
All it takes are some small tweaks to our code:
class Solution(StrSplitSolution): def part_1(self) -> int: def part_2(self) -> int: current = 50 num_zeroes = 0
for line in self.input: direction, *raw_distance = line distance = int("".join(raw_distance)) if direction == "L": distance *= -1 step = -1 if direction == "L" else 1 distance *= step
num_zeroes += [ i % 100 for i in range(current, current + distance, step) ].count(0)
current = (current + distance) % 100
if current == 0: num_zeroes += 1
return num_zeroesThe only tricky bit is generating the range. While it’s most commonly constructed with a single argument (e.g. range(10)), it actually supports start, stop, and step arguments too. This lets us easily create reverse ranges by specifying a step (e.g. the distance we move between each item in the range) of -1 when rotating left.
When our range is built correctly, we just count up the zeroes and arrive at our answer!