Advent of Code 2025 – Day 1: Modular Arithmetic

By


The problem setting

Welcome to Advent of Code 2025! In this post I will give some thoughts about the first day’s problem.

In this problem we have 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. There is a sequence of instructions which tell you which direction (L=left or R=right) and how many steps to rotate. For example, if you have a sequence of instructions L68, L30, R48 then the dials will move as follows:

  • The dial starts by pointing at 50.
  • The dial is rotated L68 to point at 82.
  • The dial is rotated L30 to point at 52.
  • The dial is rotated R48 to point at 0.

If the dial ends at position \(0\), I will call it a “hit”. In part 1, you need to count how many “hits” after the all the rotations. In part 2, you need to count all the hits + how many times the dial passed through \(0\) during the rotations (which I will call a “pass”). The full description of the problem can be found in https://adventofcode.com/2025/day/1

Modular arithmetic

For this problem, it is natural to use modular arithmetic. Let me briefly explain if you are not familiar with this concept.

For a given integer \( m\geq 1\), we say \( a \equiv b \pmod m \) if \( m | (a-b) \). (this notation means \( m \) divides \( (a-b) \) or equivalently, \( (a-b) \) is divisible by \( m \) )

In other words, \( a \equiv b \pmod m \) if \(a\) and \(b\) have the same remainder when dividing \(m\). This notion is closely connected to the group \( \mathbb{Z}_m =\{0, 1, 2, …, m-1 \}\) mentioned is another post, which is the group of remainders of division by \(m\) with addition operation. The clock is an example of modular arithmetic with \(m=12\).

  • \( 13 \equiv 1 \pmod{12} \) since \(13-1=12\) is divisible by \(12\)
  • \( 11 \equiv -1 \pmod{12} \) since \(11- (-1)=12\) is divisible by \(12\)
  • \( -5 \equiv 7 \pmod{12} \) since \(-5- 7=-12\) is divisible by \(12\)
  • \( 36 \equiv 0 \pmod{12} \) since \(36\) is divisible by \(12\)
The clock represents modular arithmetic with \(m=12\).

Modular arithmetic also explains why we can access the last element of a list by index \(-1\).

With this notion, the dial is just a modular arithmetic with \( m=100\). To compute modular arithmetic in Python, we simply use the binary operation \(\%\), where \(a\%b\) returns the remainder of the division \(a/b\).

A solution for part 2

Part 2 is a bit more challenging. For the right (R) direction, the computation is quite clear. The confusion mainly occurs in case of L-direction. Let’s consider some scenarios in the L-direction:

1. If the current position is \(0\), the instruction L300 should give \(3\) ( including \(2\) ‘pass’ during the rotation + \(1\) ‘hit’ at the end). In this case, the total hit+passes of the instruction L\(s\) (\(s\)=steps) is given by \(s//100\), that is, the integer part of the division \(s/100\).

2. If the current position (let’s call it \(p\)) is not \(0\). There are two cases for \(s\):

  • if \(s<p\), the left rotation cannot reach \(0\).
  • if \(s \geq p\), then there is at least \(1\) hit or pass at \(0\). Mathematically, we decompose the rotation L\(s\) as

L\(s\)= L\(p\) + L\((s-p)\)

This notation is understood as follow: first, we rotate left \(p\) steps to arrive at \(0\), which gives \(1\) “hit”. Then, starting from position \(0\), we do the rest \((s-p)\) steps. This goes back the the first case.

Here are the pseudocode for part 2:

count = 0
curr_pos = 50
for every (dir,step):
if dir == 'R':
count + (curr_pos + step) // 100 --> count
(curr_pos + step) % 100 --> curr_pos
if dir == 'L':
#if starting from 0, only count complete circles
if curr_pos==0:
count + step//100 --> count
else:
#count complete circles + if end point is 0
if step >= curr_pos:
count + (step-curr_pos) // 100+1 --> count
#update new position
(curr_pos - step) % 100 --> curr_pos
return count

Note that in case of R-rotation, the formula (curr_pos + step) // 100 takes into account all passes and hit (ending at \(0\)), no need for a separate check if the new position is \(0\).

For more details, refer to my solution for AoC 2025 in https://github.com/giang-to/Advent-of-Code-2025.

Discover more from

Subscribe now to keep reading and get access to the full archive.

Continue reading