Skip to content

Advent of Code 2025 Day 1: Secret Entrance

from advent import parse, atom, Callable

input: list[tuple[str, int]] = [(line[0], int(line[1:])) for line in parse(1, atom)]

part1 = lambda dial, _: 1 if dial == 0 else 0
part2 = lambda _, clicks: clicks

def solve(part: Callable[[int, int], int], count: int = 0, dial: int = 50) -> int:
    for dir, n in input:
        clicks, dial = divmod(dial + (n if dir == 'R' else -n), 100)
        count += part(dial, abs(clicks))

    return count

assert solve(part1) == 1154
assert solve(part2) == 6819

Day 1

Input Parsing

The input for Day 1 consists of rotation instructions for a safe. The first character is either L or R, and is followed by an integer indicating how many clicks to rotate. For example: R37 or L872. parse(1, atom) provides the following:

----------------------------------------------------------------------------------------------------
app/day01.txt ➜ 19048 chars, 4629 lines; first 3 lines:
----------------------------------------------------------------------------------------------------
R49
R27
R22
----------------------------------------------------------------------------------------------------
parse(1) ➜ 4629 entries:
----------------------------------------------------------------------------------------------------
('R49', 'R27', 'R22', 'R5', 'R6', 'R10', 'L13', 'L37', 'R17', 'R6', 'L ... L20', 'R7', 'L34', 'L15')
----------------------------------------------------------------------------------------------------

We'll use a list comprehension to create a list of 2-tuples containing a direction and the number of clicks, such as [('R', 49), ('L', 13), ...]

[(line[0], int(line[1:])) for line in parse(1, atom)]

Solution

The key concept for today's puzzle is modular arithmetic. There are 100 numbers on the dial of the safe, so we'll be doing modulo 100 arithmetic to wrap around from 99 to 0 when rotating right, or 0 to 99 when rotating left. We'll also need the remainder to know how many times we've wrapped around.

The divmod built-in Python function allows us to compute a // b and a % b with a single call, and we need both of those results.

For each of the rotation instructions, we add/subtract the indicated number from the current dial position, and call divmod(<result>, 100). The absolute value of the first result (quotient) is the number of clicks after which the position was at zero, and the second result (position mod 100) is the new current position of the dial.

For part 1, we only care about whether the new position is zero. For part 2, we want the number of clicks after which zero was displayed. These two different needs are encapsulated in the two lambda functions, part1 and part2.

The Fine Print

I haven't told you the full story though! There is a subtlety that needs to be handled to have an accurate solution. Neither my sample input, nor the real input, exercised these edge cases, so I took advantage of that fact to simplify my code. However, for left rotations, if our starting position is zero, and our ending position is non-zero, we need to add 1 to our quotient, and if our starting position is non-zero, and our ending position is zero, we need to subtract 1 from our quotient. Here is the code to handle these edge cases

End