Advent of Code solutions in Rust

feat: day 11 of AoC 2025

+222 -2
+220
aoc_2025/src/day11.rs
··· 1 + use std::collections::{HashMap, HashSet}; 2 + 3 + use anyhow::bail; 4 + use aoc_companion::prelude::*; 5 + use itertools::Itertools as _; 6 + 7 + pub(crate) struct Door { 8 + connections: HashMap<Device, HashSet<Device>>, 9 + } 10 + 11 + impl<'input> Solution<'input> for Door { 12 + fn parse(input: &'input str) -> Result<Self> { 13 + input 14 + .lines() 15 + .map(|line| { 16 + let Some((this_name, connected_names)) = line.split_once(": ") else { 17 + bail!("expected \": \" delimiter, found {line:?}"); 18 + }; 19 + let connected_devices = connected_names 20 + .split_ascii_whitespace() 21 + .map(Device::new) 22 + .try_collect()?; 23 + Ok((Device::new(this_name)?, connected_devices)) 24 + }) 25 + .try_collect() 26 + .map(|connections| Door { connections }) 27 + } 28 + 29 + fn part1(&self) -> usize { 30 + number_of_paths(YOU, &self.connections) 31 + } 32 + 33 + fn part2(&self) -> usize { 34 + number_of_problematic_paths(SVR, &self.connections).num_both 35 + } 36 + } 37 + 38 + #[derive(Clone, Copy, PartialEq, Eq, Hash)] 39 + struct Device([u8; 3]); 40 + 41 + impl std::fmt::Debug for Device { 42 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 43 + let s = str::from_utf8(&self.0).expect("device names should be valid UTF-8"); 44 + f.debug_tuple("Device").field(&s).finish() 45 + } 46 + } 47 + 48 + impl Device { 49 + fn new(name: &str) -> Result<Self> { 50 + if name.len() != 3 { 51 + bail!( 52 + "device name {name:?} needs to be exactly three bytes, but is {} bytes: {:x?}", 53 + name.len(), 54 + name.as_bytes() 55 + ); 56 + } 57 + let mut bytes = [0; 3]; 58 + bytes.copy_from_slice(name.as_bytes()); 59 + Ok(Self(bytes)) 60 + } 61 + } 62 + 63 + const OUT: Device = Device(*b"out"); 64 + const YOU: Device = Device(*b"you"); 65 + const SVR: Device = Device(*b"svr"); 66 + const DAC: Device = Device(*b"dac"); 67 + const FFT: Device = Device(*b"fft"); 68 + 69 + fn number_of_paths(from: Device, connections: &HashMap<Device, HashSet<Device>>) -> usize { 70 + if from == OUT { 71 + return 1; 72 + } 73 + 74 + connections[&from] 75 + .iter() 76 + .map(|&to| number_of_paths(to, connections)) 77 + .sum() 78 + } 79 + 80 + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] 81 + struct PathStats { 82 + num_neither: usize, 83 + num_only_dac: usize, 84 + num_only_fft: usize, 85 + num_both: usize, 86 + } 87 + 88 + impl std::iter::Sum for PathStats { 89 + fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { 90 + iter.fold(Self::default(), |acc, res| Self { 91 + num_neither: acc.num_neither + res.num_neither, 92 + num_only_dac: acc.num_only_dac + res.num_only_dac, 93 + num_only_fft: acc.num_only_fft + res.num_only_fft, 94 + num_both: acc.num_both + res.num_both, 95 + }) 96 + } 97 + } 98 + 99 + fn number_of_problematic_paths( 100 + start: Device, 101 + connections: &HashMap<Device, HashSet<Device>>, 102 + ) -> PathStats { 103 + aoc_utils::cache::cached(|from, recurse| { 104 + if from == OUT { 105 + return PathStats { 106 + num_neither: 1, 107 + ..Default::default() 108 + }; 109 + } 110 + 111 + connections[&from] 112 + .iter() 113 + .map(|&to| recurse(to)) 114 + .map(|stats| match from { 115 + DAC => PathStats { 116 + num_neither: 0, 117 + num_only_dac: stats.num_neither, 118 + num_only_fft: 0, 119 + num_both: stats.num_only_fft, 120 + }, 121 + FFT => PathStats { 122 + num_neither: 0, 123 + num_only_dac: 0, 124 + num_only_fft: stats.num_neither, 125 + num_both: stats.num_only_dac, 126 + }, 127 + _ => stats, 128 + }) 129 + .sum() 130 + })(start) 131 + } 132 + 133 + #[cfg(test)] 134 + mod tests { 135 + use super::*; 136 + 137 + const FIRST_EXAMPLE_INPUT: &str = "\ 138 + aaa: you hhh 139 + you: bbb ccc 140 + bbb: ddd eee 141 + ccc: ddd eee fff 142 + ddd: ggg 143 + eee: out 144 + fff: out 145 + ggg: out 146 + hhh: ccc fff iii 147 + iii: out"; 148 + 149 + fn first_example_connections() -> HashMap<Device, HashSet<Device>> { 150 + HashMap::from([ 151 + ( 152 + Device(*b"aaa"), 153 + HashSet::from([Device(*b"you"), Device(*b"hhh")]), 154 + ), 155 + ( 156 + Device(*b"you"), 157 + HashSet::from([Device(*b"bbb"), Device(*b"ccc")]), 158 + ), 159 + ( 160 + Device(*b"bbb"), 161 + HashSet::from([Device(*b"ddd"), Device(*b"eee")]), 162 + ), 163 + ( 164 + Device(*b"ccc"), 165 + HashSet::from([Device(*b"ddd"), Device(*b"eee"), Device(*b"fff")]), 166 + ), 167 + (Device(*b"ddd"), HashSet::from([Device(*b"ggg")])), 168 + (Device(*b"eee"), HashSet::from([Device(*b"out")])), 169 + (Device(*b"fff"), HashSet::from([Device(*b"out")])), 170 + (Device(*b"ggg"), HashSet::from([Device(*b"out")])), 171 + ( 172 + Device(*b"hhh"), 173 + HashSet::from([Device(*b"ccc"), Device(*b"fff"), Device(*b"iii")]), 174 + ), 175 + (Device(*b"iii"), HashSet::from([Device(*b"out")])), 176 + ]) 177 + } 178 + 179 + const SECOND_EXAMPLE_INPUT: &str = "\ 180 + svr: aaa bbb 181 + aaa: fft 182 + fft: ccc 183 + bbb: tty 184 + tty: ccc 185 + ccc: ddd eee 186 + ddd: hub 187 + hub: fff 188 + eee: dac 189 + dac: fff 190 + fff: ggg hhh 191 + ggg: out 192 + hhh: out"; 193 + 194 + #[test] 195 + fn parse_example_input() { 196 + assert_eq!( 197 + Door::parse(FIRST_EXAMPLE_INPUT).unwrap().connections, 198 + first_example_connections(), 199 + ); 200 + } 201 + 202 + #[test] 203 + fn find_number_of_paths_to_out() { 204 + assert_eq!(number_of_paths(YOU, &first_example_connections()), 5); 205 + } 206 + 207 + #[test] 208 + fn find_number_of_problematic_paths_to_out() { 209 + let Door { connections } = Door::parse(SECOND_EXAMPLE_INPUT).unwrap(); 210 + assert_eq!( 211 + number_of_problematic_paths(SVR, &connections), 212 + PathStats { 213 + num_neither: 2, 214 + num_only_dac: 2, 215 + num_only_fft: 2, 216 + num_both: 2 217 + } 218 + ) 219 + } 220 + }
+2 -2
aoc_2025/src/main.rs
··· 10 10 mod day08; 11 11 mod day09; 12 12 mod day10; 13 - // mod day11; 13 + mod day11; 14 14 // mod day12; 15 15 16 16 use aoc_companion::prelude::*; ··· 28 28 door!(2025-12-08 ~> day08), 29 29 door!(2025-12-09 ~> day09), 30 30 door!(2025-12-10 ~> day10), 31 - // door!(2025-12-11 ~> day11), 31 + door!(2025-12-11 ~> day11), 32 32 // door!(2025-12-12 ~> day12), 33 33 ]) 34 34 .await