
Welcome to my Advent of Code 2025 series! Today we're solving Day 1's puzzle, which involves a combination lock with 100 positions (0-99).
The Problem
We have a dial that starts at position 50, and we need to follow a series of instructions to rotate it left (L) or right (R) by certain amounts. The goal is to count how many times the dial lands on position 0.
Understanding the Modulo Operation
Before diving into the solution, let's understand the key concept: modulo arithmetic.
When we rotate a dial, we need it to "wrap around". If we're at position 99 and move right by 1, we should end up at position 0, not 100. Similarly, if we're at position 0 and move left by 1, we should end up at position 99.
This is where modulo operations come in handy, but there's an important distinction in Rust between % and rem_euclid().
The Difference Between % and rem_euclid()
In Rust, % is the remainder operator, while rem_euclid() is the Euclidean remainder. They behave differently with negative numbers:
Using the % operator:
println!("{}", 5 % 3); // 2
println!("{}", -5 % 3); // -2 (negative!)
println!("{}", -1 % 100); // -1 (negative!)
Using rem_euclid():
println!("{}", 5_i32.rem_euclid(3)); // 2
println!("{}", (-5_i32).rem_euclid(3)); // 1 (positive!)
println!("{}", (-1_i32).rem_euclid(100)); // 99 (positive!)
For our dial problem, this difference is crucial:
- If we're at position 0 and move left by 1, we want position 99
- With
%:(0 - 1) % 100 = -1❌ (invalid position!) - With
rem_euclid():(0 - 1).rem_euclid(100) = 99✅ (correct!)
The key insight: The % operator can return negative results, which don't make sense for positions on a dial. The rem_euclid() method always returns a non-negative result in the range [0, modulus), which is exactly what we need for circular wrapping.
Part 1: Jump Directly
In the first part, we can jump directly to the final position after each instruction:
fn compute_password1(instructions: Vec<(char, i32)>) -> i32 {
let mut dial: i32 = 50;
let mut count: i32 = 0;
for (dir, dist) in instructions.iter() {
if *dir == 'L' {
dial = (dial - dist).rem_euclid(100);
} else {
dial = (dial + dist).rem_euclid(100);
}
if dial == 0 {
count += 1;
}
}
count
}
Here, we subtract for left moves and add for right moves, then use rem_euclid(100) to keep the result within 0-99.
Part 2: Step by Step
Part 2 requires us to check every single position we pass through, not just the final position:
fn compute_password2(instructions: Vec<(char, i32)>) -> i32 {
let mut dial: i32 = 50;
let mut count: i32 = 0;
for (dir, dist) in instructions.iter() {
if *dir == 'L' {
for _ in 0..*dist {
dial = (dial - 1).rem_euclid(100);
if dial == 0 {
count += 1;
}
}
} else {
for _ in 0..*dist {
dial = (dial + 1).rem_euclid(100);
if dial == 0 {
count += 1;
}
}
}
}
count
}
Instead of jumping directly, we move one position at a time and check if we hit 0 after each step.
Parsing the Input
The input file contains instructions like L25, R30, etc. We parse these into tuples of direction and distance:
fn parsing(filename: &str) -> Vec<(char, i32)> {
let content = fs::read_to_string(filename)
.expect("Failed to read file");
content
.split('\n')
.map(|s| s.trim().to_string())
.map(|s| {
let (first, rest) = s.split_at(1);
let direction = first.chars().next().unwrap();
let value: i32 = rest.parse()
.expect("Expected a number");
(direction, value)
})
.collect()
}
Key Takeaways
- Modulo arithmetic is essential for circular/wrapping problems
- Use
rem_euclid()in Rust, not%, when you need non-negative results for circular wrapping - The
%operator can return negative numbers, which break circular logic - Part 1 vs Part 2 shows the difference between checking final states vs intermediate states
- Always consider whether you need to track every step or just the destination
Happy coding, and see you tomorrow for Day 2! 🎄
You can find the complete code on my GitHub repository
Top comments (0)