Day 1 - Secret Entrance
Created on Monday, December 1, 2025.
2025 Day 1
Here we go! I had to clear down a bunch of left over Intcode Day 5 stuff in order to get ready to do this.
So, a simple looking problem to start day one with. Rotate a dial left or right through 0-99 using "clock arithmatic", which feels mathmatically simple.
We can just parse the input, probably mapping right to positive numbers and left to negative numbers. We can add 99 to number, modulo 99 and we should get the right result. We can use a map to turn from our start number, through each turn, and then filter that stream by the predicate of whether they are 0.
First, to parse the numbers, it's just like normal, split by lines, and then we're going to match against strings and use guard clauses to separate left from right. This is because Rust is UTF compatible, which makes getting "the first character" from a string non-trivial. The guard clauses works well to do what we want without lots of weird casting. If it starts R, parse the number, if it starts L, return the negative of the number.
This gives us:
_input
.lines()
.map(|line| line.trim())
.map(|s| match s {
_ if s.starts_with('R') => s[1..].parse::<i32>().expect("Failed to parse integer"),
_ if s.starts_with('L') => -s[1..].parse::<i32>().expect("Failed to parse integer"),
_ => panic!("Invalid direction"),
})
.collect()
Next we want to sort the rotation in a simple function that takes current position of the dial, and applies the next turn.
fn rotate_dial(start: i32, turn: i32) -> i32 {
(start + turn + 100) % 100
}
Now, I more or less know that I can just use fold on this to start with 50, and that will give us our end position. But we care what positions we pass through, which is a slightly awkward mapping. Instead I decided to just do a for loop. I think this could be done better with a fold or reduce somehow, but my brain won't tell me how, so we end up with this.
fn execute_instructions(start: i32, instructions: &Vec<i32>) -> Vec<i32> {
let mut dial = start;
let mut results = Vec::new();
for &instruction in instructions {
dial = rotate_dial(dial, instruction);
results.push(dial);
}
results
}
That lets me write a simple test to confirm that we get the sequence outlined in my tests
fn test_execute_instructions() {
let input = "L68
L30
R48
L5
R60
L55
L1
L99
R14
L82";
assert_eq!(
execute_instructions(50, &parse_input(&input)),
vec![82, 52, 0, 95, 55, 0, 99, 0, 14, 32]
);
}
And that means our part 1 can just be a filter over that returned list, counting the occurences of 0.
execute_instructions(50, input)
.into_iter()
.filter(|&x| x == 0)
.count()
Tests work, lets give this a whirl.
Part 2
Oh interesting, so we're not counting whether the number is on 0, but whether the rotation causes us to pass 0.
How many times does it pass 0 feels mathmatically tricky. I know that if it's at 99 and I do +2, it passes 0 once. If I do plus 102, it passes 0 twice. But I don't think it's necessarily just as simple as "num/100", because how do I know if 99+2 passes 0?
I suspect we need to do two tests for each rotation, firstly how many times does the dial completely rotate. Secondly did it pass 0 as part of the final actual move.
If we have the sequence of moves, then we can map them based on these two tests, firstly rotation/100, which gives us number of times it fully rotated, and then secondly, if 99+position+rotation is >199 or < 99, then we can count an extra rotation.
Finally, we'll need to account for the edge cases, if it ends on 0, we need to count that. Lets give that a bit of a try with a part 2 version of execute instructions, so
fn execute_instructions_part2(start: i32, instructions: &Vec<i32>) -> Vec<i32> {
let mut dial = start;
let mut results = Vec::new();
for &instruction in instructions {
let mut passes = 0;
passes += instruction.abs() / 100;
if dial != 0 && (dial+(instruction%100) >= 100 || dial+(instruction%100) <= 0) {
passes += 1;
}
dial = rotate_dial(dial, instruction);
results.push(passes);
}
results
This passes all the tests I can think of, including some from coworkers, but isn't working at the moment.
It's also really ugly, and I feel like I'm combining too many thoughts at once, so I wondered if I should refactor it.
I wonder if instead of trying to calculate the number of passes, I wonder if we turn the dial 100 at a time until it's within the 0-99 range, and then dealing with the remainder?
let mut dial = start;
let mut results = Vec::new();
for &instruction in instructions {
let mut total = instruction;
let mut passes = 0;
while total.abs() >= 100 {
passes += 1;
total -= total.signum() * 100;
}
let target = dial + total;
if target > 99 || (dial != 0 && target <= 0) {
passes += 1;
}
dial = rotate_dial(dial, instruction);
results.push(passes);
}
results
But that still gives me the wrong answer and after some debugging I can't quite work out what the issue is.
To reddit, and found this on todays reddit https://www.reddit.com/r/adventofcode/comments/1pbhkil/2025_day_1_part_1_til_rust_is_remainder/
Yup, I'd forgotten that in rust -1 % 100 does not operate the way I think it does. Instead we should be doing -1.rem_euclid(100) for the sensible answer. Pop that in, all my tests continue to pass, but now the end answer is the right one.
Do I know what test I'd have used to get that right? Probably some variations of the rotate dial test, but the examples I've tried seem to pass with either, so there's clearly an edge case that I'm missing somewhere.
Oh well, rem_euclid solved it, not terribly satisfying, but at least it's done.
Addendum
Found the bug. I was testing things like start at 10, rotate left 30, which was handled by my code, but not start at 10, rotate left 320 which was not accounted for correctly with my implementation of rotate dial. With new test cases in place, we can see what the issue is, and rem_euclid does the correct thing!
Next
Day 2 - Repeating digits in really big numbers
Previous