Advent of Code solutions in Rust

feat: day 6 of AoC 2025

+194 -2
+1
Cargo.lock
··· 149 149 "aoc_companion", 150 150 "aoc_utils", 151 151 "itertools", 152 + "ndarray", 152 153 "num-traits", 153 154 "thiserror", 154 155 "tokio",
+1
aoc_2025/Cargo.toml
··· 10 10 aoc_utils = { workspace = true } 11 11 anyhow = { workspace = true } 12 12 itertools = { workspace = true } 13 + ndarray = { workspace = true } 13 14 num-traits = { workspace = true } 14 15 thiserror = { workspace = true } 15 16 tokio = { workspace = true }
+190
aoc_2025/src/day06.rs
··· 1 + use aoc_companion::prelude::*; 2 + use itertools::Itertools as _; 3 + 4 + #[derive(Debug, PartialEq, Eq)] 5 + pub(crate) struct Door<'input> { 6 + operands: ndarray::Array2<&'input str>, 7 + operators: Vec<Op>, 8 + } 9 + 10 + impl<'input> Solution<'input> for Door<'input> { 11 + fn parse(input: &'input str) -> Result<Self> { 12 + let lines: Vec<_> = input.lines().collect(); 13 + let Some((operator_line, operand_lines)) = lines.split_last() else { 14 + anyhow::bail!("homework is empty"); 15 + }; 16 + let column_offsets: Vec<usize> = operator_line 17 + .as_bytes() 18 + .iter() 19 + .positions(|b| *b != b' ') 20 + .collect(); 21 + let operators = operator_line 22 + .split_ascii_whitespace() 23 + .map(|op| match op { 24 + "+" => Ok(Op::Add), 25 + "*" => Ok(Op::Mul), 26 + _ => Err(anyhow::anyhow!("invalid operator: {op:?}")), 27 + }) 28 + .try_collect()?; 29 + let operands = operand_lines 30 + .iter() 31 + .flat_map(|line| { 32 + column_offsets 33 + .iter() 34 + .tuple_windows() 35 + .map(|(&a, &b)| &line[a..(b - 1)]) 36 + .chain(std::iter::once(&line[*column_offsets.last().unwrap()..])) 37 + }) 38 + .collect(); 39 + 40 + Ok(Door { 41 + operands: ndarray::Array2::from_shape_vec( 42 + (operand_lines.len(), column_offsets.len()), 43 + operands, 44 + )?, 45 + operators, 46 + }) 47 + } 48 + 49 + fn part1(&self) -> Result<u64> { 50 + Ok(self.to_human_math()?.solutions().iter().sum()) 51 + } 52 + 53 + fn part2(&self) -> Result<u64> { 54 + Ok(self.to_cephalopod_math()?.solutions().iter().sum()) 55 + } 56 + } 57 + 58 + impl Door<'_> { 59 + fn to_human_math(&self) -> Result<Homework> { 60 + let operands = self 61 + .operands 62 + .columns() 63 + .into_iter() 64 + .map(|col| col.iter().map(|s| s.trim().parse()).try_collect()) 65 + .try_collect()?; 66 + Ok(Homework { 67 + operands, 68 + operators: self.operators.clone(), 69 + }) 70 + } 71 + 72 + fn to_cephalopod_math(&self) -> Result<Homework> { 73 + let operands = self 74 + .operands 75 + .columns() 76 + .into_iter() 77 + .rev() 78 + .map(|col| { 79 + let width = col.get(0).map(|s| s.len()).unwrap_or_default(); 80 + (0..width) 81 + .rev() 82 + .map(|i| { 83 + col.iter() 84 + .map(|s| { 85 + s.as_bytes() 86 + .get(i) 87 + .ok_or_else(|| anyhow::anyhow!("uneven column width")) 88 + }) 89 + .filter_ok(|b| b.is_ascii_digit()) 90 + .try_fold(0, |acc, b| -> Result<u64> { 91 + Ok(acc * 10 + u64::from(b? - b'0')) 92 + }) 93 + }) 94 + .try_collect() 95 + }) 96 + .try_collect()?; 97 + Ok(Homework { 98 + operands, 99 + operators: self.operators.iter().cloned().rev().collect(), 100 + }) 101 + } 102 + } 103 + 104 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 105 + enum Op { 106 + Add, 107 + Mul, 108 + } 109 + 110 + impl Op { 111 + fn neutral_element(&self) -> u64 { 112 + match self { 113 + Op::Add => 0, 114 + Op::Mul => 1, 115 + } 116 + } 117 + } 118 + 119 + struct Homework { 120 + operands: Vec<Vec<u64>>, 121 + operators: Vec<Op>, 122 + } 123 + 124 + impl Homework { 125 + fn solutions(&self) -> Vec<u64> { 126 + self.operands 127 + .iter() 128 + .zip(self.operators.iter()) 129 + .map(|(col, op)| { 130 + col.iter().fold( 131 + op.neutral_element(), 132 + match op { 133 + Op::Add => std::ops::Add::add, 134 + Op::Mul => std::ops::Mul::mul, 135 + }, 136 + ) 137 + }) 138 + .collect() 139 + } 140 + } 141 + 142 + #[cfg(test)] 143 + mod tests { 144 + use super::*; 145 + 146 + const EXAMPLE_INPUT: &str = "\ 147 + 123 328 51 64 148 + 45 64 387 23 149 + 6 98 215 314 150 + * + * + "; 151 + 152 + #[test] 153 + fn parse_example_input() { 154 + assert_eq!( 155 + Door::parse(EXAMPLE_INPUT).unwrap(), 156 + Door { 157 + operands: ndarray::array![ 158 + ["123", "328", " 51", "64 "], 159 + [" 45", "64 ", "387", "23 "], 160 + [" 6", "98 ", "215", "314"], 161 + ], 162 + operators: vec![Op::Mul, Op::Add, Op::Mul, Op::Add,] 163 + } 164 + ) 165 + } 166 + 167 + #[test] 168 + fn example_human_solutions() { 169 + assert_eq!( 170 + Door::parse(EXAMPLE_INPUT) 171 + .unwrap() 172 + .to_human_math() 173 + .unwrap() 174 + .solutions(), 175 + [33210, 490, 4243455, 401] 176 + ); 177 + } 178 + 179 + #[test] 180 + fn example_cephalopod_solutions() { 181 + assert_eq!( 182 + Door::parse(EXAMPLE_INPUT) 183 + .unwrap() 184 + .to_cephalopod_math() 185 + .unwrap() 186 + .solutions(), 187 + [1058, 3253600, 625, 8544] 188 + ); 189 + } 190 + }
+2 -2
aoc_2025/src/main.rs
··· 5 5 mod day03; 6 6 mod day04; 7 7 mod day05; 8 - // mod day06; 8 + mod day06; 9 9 // mod day07; 10 10 // mod day08; 11 11 // mod day09; ··· 23 23 door!(2025-12-03 ~> day03), 24 24 door!(2025-12-04 ~> day04), 25 25 door!(2025-12-05 ~> day05), 26 - // door!(2025-12-06 ~> day06), 26 + door!(2025-12-06 ~> day06), 27 27 // door!(2025-12-07 ~> day07), 28 28 // door!(2025-12-08 ~> day08), 29 29 // door!(2025-12-09 ~> day09),