Dazbo's Advent of Code solutions, written in Python
Modular ArithmeticInteger Division
Welcome to Advent of Code 2025! I normally expect the first day to be a warm-up, but this one was a little tricky. Edge cases that were not included in the example input caught me out for a while.
We need to get into a safe to retrieve a password. The safe has a dial with numbers 0 through 99 arranged in a circle. The dial starts pointing at 50. As you turn the dial, it makes a small click at each number position.
The input contains rotation instructions, one per line. Each instruction starts with:
L (left/toward lower numbers) or R (right/toward higher numbers)For example:
L68
L30
R48
L5
R60
The dial wraps around: turning left from 0 goes to 99, and turning right from 99 goes to 0.
Example rotations:
11, rotating R8 → points at 1919, rotating L19 → points at 05, rotating L10 → points at 95What’s the actual password to open the door?
The password is the number of times the dial ends at position 0 after any rotation.
This is straightforward - we just need to track the dial position and count how many times we land on 0 after completing each rotation.
We can use modular arithmetic to handle the circular dial:
(position + steps) % 100(position - steps) % 100Here’s the core logic:
def part1(data: list[str], start: int = 50, clicks: int = 100):
zero_counter = 0
curr_pos = start
for instruction in data:
direction = instruction[0]
steps = int(instruction[1:])
# Convert LEFT to equivalent RIGHT rotation
if direction == "L":
steps = clicks - steps
curr_pos = (curr_pos + steps) % clicks
if curr_pos == 0:
zero_counter += 1
return zero_counter
Why convert LEFT to RIGHT? This simplifies the logic. A left rotation of n steps is equivalent to a right rotation of 100 - n steps on a dial with 100 positions.
Using password method 0x434C49434B, what is the password to open the door?
Now we need to count every click that lands on 0, not just the final position after each rotation. This includes:
0For example, R1000 from position 50 would cross 0 ten times before returning to 50.
The most straightforward way to solve this is to simply simulate every single click of the dial. If we’re told to rotate 60 times, we update the position 60 times, checking for 0 at each step.
def part2(data: list[str], start: int = 50, dial_nums: int = 100) -> int:
zero_counter = 0
curr_pos = start
for instruction in data:
direction = instruction[0]
clicks = int(instruction[1:])
for _ in range(clicks): # simulate EVERY click
if direction == "R":
curr_pos = (curr_pos + 1) % dial_nums
else: # direction == "L"
curr_pos = (curr_pos - 1) % dial_nums
if curr_pos == 0:
zero_counter += 1
return zero_counter
This works perfectly fine for the given input. The dial only has 100 positions, and the number of rotations isn’t huge. It runs in about 0.02 seconds.
But what if the dial had a billion positions? Or if we had to rotate billions of times? The simulation approach is O(N) where N is the total number of clicks. We can do better - O(1) per instruction - using math.
For RIGHT rotations: We can calculate how many times we wrap around.
zero_counter += (curr_pos + clicks) // dial_nums
curr_pos = (curr_pos + clicks) % dial_nums
For LEFT rotations: This is slightly trickier to handle the wrap-around logic correctly.
if direction == "L":
new_pos = (curr_pos - clicks) % dial_nums
if curr_pos == 0:
# Starting at 0: only count complete loops (not the starting position)
zero_counter += clicks // dial_nums
elif clicks > curr_pos:
# We cross 0 at least once during the CCW rotation
zero_counter += ((clicks - curr_pos - 1) // dial_nums) + 1
# If we also END on 0, count that final landing
if new_pos == 0:
zero_counter += 1
elif clicks == curr_pos:
# We land exactly on 0
zero_counter += 1
curr_pos = new_pos
This approach runs in 0.002 seconds - an order of magnitude faster!
Getting the math solution correct for Part 2 was a journey! Here’s what happened:
00The formula ((steps - curr_pos - 1) // clicks) + 1 correctly counts how many times we cross the 0 boundary, but when we end exactly on 0, that final landing wasn’t being counted.
Added a check after calculating crossings for LEFT rotations:
elif steps > curr_pos:
zero_counter += ((steps - curr_pos - 1) // clicks) + 1
# If we also END on 0, count that final landing
if new_pos == 0:
zero_counter += 1
The results look something like this:
Part 1: 1081
Part 2: 6689
Execution time: 0.002 seconds
Both parts run in just 2 milliseconds with the optimized solution!