Advent of Code - Day 01: Spinning Dials and Surprising Zeros
Today’s puzzle turned out to be a great example of how a seemingly trivial problem hides a neat little twist — and how visualizing your algorithm can completely change how you think about it.
The setup: You’re given a giant dial with 100 positions (0–99). You start at position 50. Then you receive instructions like:
L10
R47
L3
... Each one telling you to rotate the dial left or right by a number of clicks.
Easy enough.
Until you look at what you’re actually asked to compute.
Part 1: Only Count the Landing Spots
Part 1 asks:
How many times do you land on position 0 after completing a full move?
Not during the spin — only the final position of each instruction matters.
That leads to a straightforward simulation:
Parse each instruction
Update the position with modular arithmetic
Count how many times the result is exactly zero
It’s simple, fast, and very “AoC warm-up” territory.
I generated a small animation so you can actually see the dial as each instruction resolves. Here’s Part 1 running through my input:

Part 2: Every Click Counts
Part 2 changes the rules entirely:
Instead of only counting where you end, count every time the dial passes through position 0 — even during long spins.
This can blow up drastically. A single instruction like R5000000 might pass zero tens of thousands of times
A naive simulation would be:
for each move:
for each click:
pos = (pos ± 1) mod 100
if pos == 0: count += 1 …and that obviously does not scale.
Fortunately, after staring at the problem long enough (and a bit of scratch-paper modular math), you can derive a constant-time formula for each move:
The first time you’d hit 0 depends on your starting position and direction
After that, you hit 0 every full rotation
Number of hits = 1 + (remaining_distance // 100)
This makes Part 2 extremely fast.
But since pure math is boring, here’s the per-click animated version so you can see the dial spinning like a malfunctioning slot machine:

And yes — the animation is absolutely useless for solving the puzzle efficiently, but extremely useful for understanding what’s going on.
On to tomorrow’s puzzle!
The Code
In my Github repo you can find my cleaned up python solution, an elixir version I made after the python solution and the code I used for making the visualization
Python solution:
from typing import Iterable, List, Tuple, Literal
Direction = Literal["L", "R"]
Move = Tuple[Direction, int]
DIAL_SIZE = 100
START_POS = 50
def parse_moves(raw: str) -> List[Move]:
moves: List[Move] = []
for line in raw.splitlines():
line = line.strip()
if not line:
continue
direction = line[0]
if direction not in ("L", "R"):
raise ValueError(f"Invalid direction: {direction}")
distance = int(line[1:])
moves.append((direction, distance))
return moves
def move(pos: int, direction: Direction, distance: int) -> int:
if direction == "R":
return (pos + distance) % DIAL_SIZE
else: # "L"
return (pos - distance) % DIAL_SIZE
def compute_password(moves: Iterable[Move]) -> int:
pos = START_POS
hits_zero = 0
for direction, distance in moves:
pos = move(pos, direction, distance)
if pos == 0:
hits_zero += 1
return hits_zero
def count_zero_hits_during_rotation(pos: int, direction: Direction, distance: int) -> int:
if distance <= 0:
return 0
if direction == "R":
first_step = (DIAL_SIZE - pos) % DIAL_SIZE
else: # "L"
first_step = pos % DIAL_SIZE
# If first_step == 0, it means we'd hit 0 after a full turn
if first_step == 0:
first_step = DIAL_SIZE
if distance < first_step:
return 0
return 1 + (distance - first_step) // DIAL_SIZE
def compute_password_method_click(moves: Iterable[Move]) -> int:
pos = START_POS
hits_zero = 0
for direction, distance in moves:
hits_zero += count_zero_hits_during_rotation(pos, direction, distance)
pos = move(pos, direction, distance)
return hits_zero
if __name__ == "__main__":
with open("input", "r", encoding="utf-8") as f:
raw = f.read()
moves = parse_moves(raw)
print(f"Part 1: {compute_password(moves)}")
print(f"Part 2: {compute_password_method_click(moves)}")