Day 07 - Laboratories
Language: Rust
Problem https://adventofcode.com/2025/day/7
This day felt like one of those problems where the rules are simple, but the implications are annoying.
A beam starts at S and only moves downward. Empty spaces do nothing.
A splitter ^ kills the current beam and spawns two new ones, one step left and one step right.
So you spend most of the time just tracking what columns are alive as you move row by row.
Part 1: Part 1 was very “try something, adjust, try again”.
My first working mental model was: after each row, I only care about which beam columns exist. Not the whole path, not a full grid simulation, just the set of active columns.
So I did that:
- Parse the input into a char grid
- Find the starting column
S - Maintain a
HashSet<usize>of active beam columns - For each row:
- Create a new
HashSetfor the next state - If a beam hits
^, incrementsplitsand addc-1andc+1 - Otherwise the beam continues straight down
- Create a new
That rebuilds the set every row, which kept it very simple.
The core idea is:
for r in 1..h {
let row = &grid[r];
let mut new_beams = HashSet::new();
for c in 0..row.len() {
if row[c] == '^' && active_beams.contains(&c) {
splits += 1;
new_beams.insert(c - 1);
new_beams.insert(c + 1);
} else if active_beams.contains(&c) && row[c] != '^' {
new_beams.insert(c);
}
}
active_beams = new_beams;
}
It’s not elegant, but at least it’s readable and it works.
Part 2: Part 2 is where it gets interesting (and where I had to swallow my pride a bit).
Now it’s not “how many times do beams split”, it’s “how many timelines exist”, which is basically: Count how many ways you can end up in each column after processing all rows.
The key realization (which I didn’t come up with myself) is: you don’t track beams anymore, you track counts.
So instead of a HashSet of active columns, you keep a HashMap<column, count> where count
is how many timelines currently sit in this column.
Then each row is just a transition:
.: counts stay in the same column^: count splits into left and right neighbor columns
This is basically dynamic programming, just phrased as “timelines”.
for r in 1..h {
let mut next = HashMap::new();
for (&c, &count) in &timelines {
match grid[r][c] {
'.' => { *next.entry(c).or_insert(0) += count; }
'^' => {
*next.entry(c - 1).or_insert(0) += count;
*next.entry(c + 1).or_insert(0) += count;
}
_ => { *next.entry(c).or_insert(0) += count; }
}
}
timelines = next;
}
At the end, the answer is just the sum of all counts.
I genuinely liked this approach once I saw it. It’s one of those “oh… that’s obviously the right way” moments, but only after someone else shows you..
Final thoughts Part 1 felt like grinding through a simulation.
Part 2 was the real lesson: sometimes the solution is not about simulating more, but about changing what you track.
HashSet for “exists”, HashMap for “how many”. Same idea, different level of information.
Still hate how long it took me to see that, but it was a good punch in the face.
Solution: https://github.com/Elyrial/AdventOfCode/blob/main/src/solutions/year2025/day07.rs
No C writeup yet.