Created
December 6, 2025 20:17
-
-
Save icub3d/3e85e6815e8a2b29315cf104575b5e49 to your computer and use it in GitHub Desktop.
Solution for Advent of Code 2025 Day 6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::time::Instant; | |
| // TODO we could read numbers from the right hand side of the "grid" and when we get an operator, we simply perform that operation on the stack of numbers and then add it to our total. | |
| // This would involve p1 being different. We just have a vec of iterators of all the "numbers" and "operators" and solve one column at a time. | |
| const INPUT: &[u8] = include_bytes!("inputs/day06.txt"); | |
| #[derive(Debug)] | |
| enum Operator { | |
| Add, | |
| Mul, | |
| } | |
| impl From<u8> for Operator { | |
| fn from(value: u8) -> Self { | |
| match value { | |
| b'*' => Operator::Mul, | |
| _ => Operator::Add, | |
| } | |
| } | |
| } | |
| // We use the operator lines to delineate where blocks of equations to solve exist. | |
| fn chunk_operators(op: &[u8]) -> impl Iterator<Item = &[u8]> { | |
| let mut remainder = op; | |
| std::iter::from_fn(move || { | |
| // Are we done? | |
| if remainder.is_empty() { | |
| return None; | |
| } | |
| // Find the position of the next operator (or end). | |
| let end = remainder | |
| .iter() | |
| .enumerate() | |
| .skip(1) | |
| .find(|(_, b)| **b != b' ') | |
| .map(|(idx, _)| idx) | |
| .unwrap_or(remainder.len()); | |
| // We'll return out chunk and process the remainder next. | |
| let chunk = &remainder[..end]; | |
| remainder = &remainder[end..]; | |
| Some(chunk) | |
| }) | |
| } | |
| // Parse the given input by making blocks of equations and passing the blocks of numbers to the given mapper. | |
| fn parse<F>(input: &[u8], mut mapper: F) -> impl Iterator<Item = (Vec<usize>, Operator)> | |
| where | |
| F: FnMut(&[&[u8]]) -> Vec<usize>, | |
| { | |
| let grid = input | |
| .split(|b| *b == b'\n') | |
| .filter(|l| !l.is_empty()) | |
| .collect::<Vec<_>>(); | |
| let len = grid.len(); | |
| // use our operator to determine the size of each block. | |
| chunk_operators(grid[len - 1]).scan(0, move |cur, op| { | |
| // Get the block from each of the rest of the lines. | |
| let numbers = grid[..len - 1] | |
| .iter() | |
| .map(|line| &line[*cur..*cur + op.len()]) | |
| .collect::<Vec<_>>(); | |
| // Update our scanner and return the mapped numbers and the operator. | |
| *cur += op.len(); | |
| let op = Operator::from(op[0]); | |
| Some((mapper(&numbers), op)) | |
| }) | |
| } | |
| // Our mapper here is just to read all the "lines" and turn them into numbers. | |
| fn p1_mapper(block: &[&[u8]]) -> Vec<usize> { | |
| block | |
| .iter() | |
| .map(|n| { | |
| n.trim_ascii() | |
| .iter() | |
| .fold(0, |acc, b| acc * 10 + (b - b'0') as usize) | |
| }) | |
| .collect::<Vec<_>>() | |
| } | |
| fn p1(input: &[u8]) -> usize { | |
| // Get our operations and sum the results. | |
| parse(input, p1_mapper) | |
| .map(|(numbers, op)| match op { | |
| Operator::Add => numbers.iter().sum::<usize>(), | |
| Operator::Mul => numbers.iter().product(), | |
| }) | |
| .sum() | |
| } | |
| // A helper function to turn a vertical slice of a block into a number. | |
| fn make_vertical_number(values: &[&[u8]], index: usize) -> usize { | |
| let mut v = 0; | |
| for vv in values { | |
| if vv[index] == b' ' { | |
| continue; | |
| } | |
| v = v * 10 + (vv[index] - b'0') as usize; | |
| } | |
| v | |
| } | |
| // Our mapper here should make the vertical numbers but we should ignore the empty line at the end (if we have one). | |
| fn p2_mapper(block: &[&[u8]]) -> Vec<usize> { | |
| (0..block[0].len()) | |
| .filter(|i| !block.iter().all(|vv| vv[*i] == b' ')) | |
| .map(|i| make_vertical_number(block, i)) | |
| .collect::<Vec<_>>() | |
| } | |
| fn p2(input: &[u8]) -> usize { | |
| // Do the same as p1 but with the vertical mapper. | |
| parse(input, p2_mapper) | |
| .map(|(numbers, op)| match op { | |
| Operator::Add => numbers.iter().sum::<usize>(), | |
| Operator::Mul => numbers.iter().product(), | |
| }) | |
| .sum() | |
| } | |
| fn main() { | |
| let now = Instant::now(); | |
| let solution = p1(INPUT); | |
| println!("p1 {:?} {}", now.elapsed(), solution); | |
| let now = Instant::now(); | |
| let solution = p2(INPUT); | |
| println!("p2 {:?} {}", now.elapsed(), solution); | |
| } | |
| #[cfg(test)] | |
| mod tests { | |
| use super::*; | |
| const INPUT: &[u8] = | |
| "123 328 51 64 \n 45 64 387 23 \n 6 98 215 314\n* + * + ".as_bytes(); | |
| #[test] | |
| fn test_p1() { | |
| assert_eq!(p1(INPUT), 4277556); | |
| } | |
| #[test] | |
| fn test_p2() { | |
| assert_eq!(p2(INPUT), 3263827); | |
| } | |
| } |
Author
Yeah, today was definitely a lifetime struggle for me! :) I love your solution though. It goes along with a couple I saw that I really liked. The idea that you can use the operand columns as the "point to do the math" is really cool and likely much more efficient than what I have.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Liked the video as usual, you got the sort of iterating and mapping type thing I was thinking about in my head that I abandoned since it was midnight and I knew I could do it silly simple with a grid and one pass walking backwards from the "bottom right" of the string.
https://github.com/EdgeCaseBerg/advent-of-code/blob/master/2025/day06/src/main.rs#L55-L98
I initially tried to do a calc like row * height + column to compute an index into a slice but ran intro trouble with out of bounds issues. Again, it was late at night so I figured just do the simple thing and move on later.
I also kind of wanted to have a list of iterates to .next on to take chunks, but fighting with lifetimes made me give that idea up for the same reason as the others.