Advent of Code solutions in Rust

feat: day 21 of AoC 2020

+378 -2
+320
aoc_2020/src/day21.rs
··· 1 + use std::{ 2 + borrow::Borrow, 3 + collections::{BTreeMap, BTreeSet, HashMap}, 4 + }; 5 + 6 + use anyhow::bail; 7 + use aoc_companion::prelude::*; 8 + use aoc_utils::iter::IterUtils; 9 + use itertools::Itertools as _; 10 + 11 + pub(crate) struct Door<'a> { 12 + foods: HashMap<BTreeSet<Ingredient<'a>>, BTreeSet<Allergen<'a>>>, 13 + } 14 + 15 + impl<'input> Solution<'input> for Door<'input> { 16 + fn parse(input: &'input str) -> Result<Self> { 17 + input 18 + .lines() 19 + .map(|line| { 20 + let Some((ingredients, allergens)) = line.split_once(" (contains ") else { 21 + bail!("missing allergens in food spec"); 22 + }; 23 + let Some(allergens) = allergens.strip_suffix(')') else { 24 + bail!("missing closing parenthesis after allergen list"); 25 + }; 26 + Ok(( 27 + ingredients 28 + .split_ascii_whitespace() 29 + .map(Ingredient) 30 + .collect(), 31 + allergens.split(", ").map(Allergen).collect(), 32 + )) 33 + }) 34 + .try_collect() 35 + .map(|foods| Door { foods }) 36 + } 37 + 38 + fn part1(&self) -> usize { 39 + safe_ingredients(&self.foods).count() 40 + } 41 + 42 + fn part2(&self) -> String { 43 + let inv_mapping: BTreeMap<_, _> = infer_mapping(&self.foods) 44 + .into_iter() 45 + .map(|(a, i)| (i, a)) 46 + .collect(); 47 + 48 + inv_mapping.values().join(",") 49 + } 50 + } 51 + 52 + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 53 + struct Ingredient<'a>(&'a str); 54 + 55 + impl std::fmt::Display for Ingredient<'_> { 56 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 57 + f.write_str(self.0) 58 + } 59 + } 60 + 61 + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 62 + struct Allergen<'a>(&'a str); 63 + 64 + fn intersect<T: Copy + Ord>(other: &mut BTreeSet<T>, new: BTreeSet<T>) { 65 + *other = other.intersection(&new).copied().collect(); 66 + } 67 + 68 + fn infer_mapping<'a, Is, As>( 69 + foods: impl IntoIterator<Item = (Is, As)>, 70 + ) -> HashMap<Ingredient<'a>, Allergen<'a>> 71 + where 72 + Is: Borrow<BTreeSet<Ingredient<'a>>> + Ord, 73 + As: Borrow<BTreeSet<Allergen<'a>>> + Ord, 74 + { 75 + let mut relations = foods 76 + .into_iter() 77 + .map(|(i, a)| (a.borrow().clone(), i.borrow().clone())) 78 + .btree_merge(intersect); 79 + 80 + let mut solution = HashMap::new(); 81 + 82 + while !relations.is_empty() { 83 + dbg!(relations.len()); 84 + // Generate non-empty intersections 85 + let mut intersections = relations 86 + .iter() 87 + .tuple_combinations() 88 + .filter_map(|((lhs_a, lhs_i), (rhs_a, rhs_i))| { 89 + let int_a: BTreeSet<_> = lhs_a.intersection(rhs_a).copied().collect(); 90 + if int_a.is_empty() { 91 + return None; 92 + } 93 + let int_i = lhs_i.intersection(rhs_i).copied().collect(); 94 + Some((int_a, int_i)) 95 + }) 96 + .btree_merge(intersect); 97 + relations.append(&mut intersections); 98 + 99 + // Identify and eliminate unique relations 100 + while let Some((allergen, ingredient)) = 101 + relations.iter().find_map(|(allergens, ingredients)| { 102 + let allergen = allergens.iter().exactly_one().ok()?; 103 + let ingredient = ingredients 104 + .iter() 105 + .filter(|&i| !solution.contains_key(i)) 106 + .exactly_one() 107 + .ok()?; 108 + Some((*allergen, *ingredient)) 109 + }) 110 + { 111 + solution.insert(ingredient, allergen); 112 + 113 + relations = relations 114 + .into_iter() 115 + .map(|(mut allergens, mut ingredients)| { 116 + allergens.remove(&allergen); 117 + ingredients.remove(&ingredient); 118 + (allergens, ingredients) 119 + }) 120 + .filter(|(allergens, _)| !allergens.is_empty()) 121 + .btree_merge(intersect); 122 + } 123 + } 124 + 125 + solution 126 + } 127 + 128 + fn safe_ingredients<'a>( 129 + foods: &HashMap<BTreeSet<Ingredient<'a>>, BTreeSet<Allergen<'a>>>, 130 + ) -> impl Iterator<Item = Ingredient<'a>> { 131 + let mapping = infer_mapping(foods); 132 + foods 133 + .keys() 134 + .flatten() 135 + .copied() 136 + .filter(move |ingredient| !mapping.contains_key(ingredient)) 137 + } 138 + 139 + #[cfg(test)] 140 + mod tests { 141 + use std::collections::HashMap; 142 + 143 + use super::*; 144 + 145 + const EXAMPLE_INPUT: &str = "\ 146 + mxmxvkd kfcds sqjhc nhms (contains dairy, fish) 147 + trh fvjkl sbzzf mxmxvkd (contains dairy) 148 + sqjhc fvjkl (contains soy) 149 + sqjhc mxmxvkd sbzzf (contains fish)"; 150 + 151 + const EXAMPLE_FOODS: [(&[Ingredient], &[Allergen]); 4] = [ 152 + ( 153 + &[ 154 + Ingredient("mxmxvkd"), 155 + Ingredient("kfcds"), 156 + Ingredient("sqjhc"), 157 + Ingredient("nhms"), 158 + ], 159 + &[Allergen("dairy"), Allergen("fish")], 160 + ), 161 + ( 162 + &[ 163 + Ingredient("trh"), 164 + Ingredient("fvjkl"), 165 + Ingredient("sbzzf"), 166 + Ingredient("mxmxvkd"), 167 + ], 168 + &[Allergen("dairy")], 169 + ), 170 + ( 171 + &[Ingredient("sqjhc"), Ingredient("fvjkl")], 172 + &[Allergen("soy")], 173 + ), 174 + ( 175 + &[ 176 + Ingredient("sqjhc"), 177 + Ingredient("mxmxvkd"), 178 + Ingredient("sbzzf"), 179 + ], 180 + &[Allergen("fish")], 181 + ), 182 + ]; 183 + 184 + const EXAMPLE_MAPPING: [(Ingredient, Allergen); 3] = [ 185 + (Ingredient("mxmxvkd"), Allergen("dairy")), 186 + (Ingredient("sqjhc"), Allergen("fish")), 187 + (Ingredient("fvjkl"), Allergen("soy")), 188 + ]; 189 + 190 + const CUSTOM_FOODS: [(&[Ingredient], &[Allergen]); 6] = [ 191 + ( 192 + &[ 193 + Ingredient("A"), 194 + Ingredient("B"), 195 + Ingredient("C"), 196 + Ingredient("D"), 197 + Ingredient("E"), 198 + Ingredient("F"), 199 + Ingredient("G"), 200 + ], 201 + &[Allergen("fish"), Allergen("nuts"), Allergen("eggs")], 202 + ), 203 + ( 204 + &[ 205 + Ingredient("A"), 206 + Ingredient("B"), 207 + Ingredient("D"), 208 + Ingredient("E"), 209 + ], 210 + &[Allergen("fish"), Allergen("nuts")], 211 + ), 212 + ( 213 + &[ 214 + Ingredient("A"), 215 + Ingredient("B"), 216 + Ingredient("C"), 217 + Ingredient("F"), 218 + ], 219 + &[Allergen("fish"), Allergen("eggs")], 220 + ), 221 + ( 222 + &[ 223 + Ingredient("A"), 224 + Ingredient("C"), 225 + Ingredient("D"), 226 + Ingredient("G"), 227 + ], 228 + &[Allergen("fish"), Allergen("plutonium")], 229 + ), 230 + ( 231 + &[ 232 + Ingredient("B"), 233 + Ingredient("C"), 234 + Ingredient("D"), 235 + Ingredient("F"), 236 + Ingredient("G"), 237 + ], 238 + &[Allergen("nuts"), Allergen("plutonium")], 239 + ), 240 + ( 241 + &[Ingredient("C"), Ingredient("D"), Ingredient("E")], 242 + &[Allergen("eggs"), Allergen("plutonium")], 243 + ), 244 + ]; 245 + 246 + const CUSTOM_MAPPING: [(Ingredient, Allergen); 4] = [ 247 + (Ingredient("A"), Allergen("fish")), 248 + (Ingredient("B"), Allergen("nuts")), 249 + (Ingredient("C"), Allergen("eggs")), 250 + (Ingredient("D"), Allergen("plutonium")), 251 + ]; 252 + 253 + #[test] 254 + fn parse_example_input() { 255 + let Door { foods } = Door::parse(EXAMPLE_INPUT).unwrap(); 256 + assert_eq!( 257 + foods, 258 + HashMap::from_iter(EXAMPLE_FOODS.iter().map(|(k, v)| ( 259 + BTreeSet::from_iter(k.iter().copied()), 260 + BTreeSet::from_iter(v.iter().copied()) 261 + ))) 262 + ); 263 + } 264 + 265 + #[test] 266 + fn infer_example_mapping() { 267 + let foods = EXAMPLE_FOODS.iter().map(|(k, v)| { 268 + ( 269 + BTreeSet::from_iter(k.iter().copied()), 270 + BTreeSet::from_iter(v.iter().copied()), 271 + ) 272 + }); 273 + assert_eq!(infer_mapping(foods), HashMap::from(EXAMPLE_MAPPING)); 274 + } 275 + 276 + #[test] 277 + fn infer_custom_example_mapping() { 278 + let foods = CUSTOM_FOODS.iter().map(|(k, v)| { 279 + ( 280 + BTreeSet::from_iter(k.iter().copied()), 281 + BTreeSet::from_iter(v.iter().copied()), 282 + ) 283 + }); 284 + assert_eq!(infer_mapping(foods), HashMap::from(CUSTOM_MAPPING)); 285 + } 286 + 287 + #[test] 288 + fn list_safe_example_ingredients() { 289 + let foods = HashMap::from_iter(EXAMPLE_FOODS.iter().map(|(k, v)| { 290 + ( 291 + BTreeSet::from_iter(k.iter().copied()), 292 + BTreeSet::from_iter(v.iter().copied()), 293 + ) 294 + })); 295 + assert_eq!( 296 + BTreeSet::from_iter(safe_ingredients(&foods)), 297 + BTreeSet::from([ 298 + Ingredient("kfcds"), 299 + Ingredient("nhms"), 300 + Ingredient("sbzzf"), 301 + Ingredient("sbzzf"), 302 + Ingredient("trh"), 303 + ]) 304 + ) 305 + } 306 + 307 + #[test] 308 + fn list_safe_custom_example_ingredients() { 309 + let foods = HashMap::from_iter(CUSTOM_FOODS.iter().map(|(k, v)| { 310 + ( 311 + BTreeSet::from_iter(k.iter().copied()), 312 + BTreeSet::from_iter(v.iter().copied()), 313 + ) 314 + })); 315 + assert_eq!( 316 + BTreeSet::from_iter(safe_ingredients(&foods)), 317 + BTreeSet::from([Ingredient("E"), Ingredient("F"), Ingredient("G"),]) 318 + ) 319 + } 320 + }
+2 -2
aoc_2020/src/main.rs
··· 20 20 mod day18; 21 21 mod day19; 22 22 mod day20; 23 - // mod day21; 23 + mod day21; 24 24 // mod day22; 25 25 // mod day23; 26 26 // mod day24; ··· 51 51 door!(2020-12-18 ~> day18), 52 52 door!(2020-12-19 ~> day19), 53 53 door!(2020-12-20 ~> day20), 54 - // door!(2020-12-21 ~> day21), 54 + door!(2020-12-21 ~> day21), 55 55 // door!(2020-12-22 ~> day22), 56 56 // door!(2020-12-23 ~> day23), 57 57 // door!(2020-12-24 ~> day24),
+56
aoc_utils/src/iter.rs
··· 1 1 use std::{ 2 + collections::{BTreeMap, HashMap}, 2 3 iter::{FusedIterator, Sum}, 3 4 rc::Rc, 4 5 }; ··· 35 36 fallback_elem: Some(fallback_elem), 36 37 } 37 38 } 39 + 40 + fn btree_merge<K, V>(self, mut merge_fn: impl FnMut(&mut V, V)) -> BTreeMap<K, V> 41 + where 42 + Self: Sized + Iterator<Item = (K, V)>, 43 + K: Ord, 44 + { 45 + self.fold(BTreeMap::new(), |mut map, (k, v)| { 46 + use std::collections::btree_map::Entry::*; 47 + match map.entry(k) { 48 + Vacant(vacant_entry) => { 49 + vacant_entry.insert(v); 50 + } 51 + Occupied(mut occupied_entry) => merge_fn(occupied_entry.get_mut(), v), 52 + }; 53 + 54 + map 55 + }) 56 + } 57 + 58 + fn hash_merge<K, V>(self, mut merge_fn: impl FnMut(&mut V, V)) -> HashMap<K, V> 59 + where 60 + Self: Sized + Iterator<Item = (K, V)>, 61 + K: std::hash::Hash + Eq, 62 + { 63 + self.fold(HashMap::new(), |mut map, (k, v)| { 64 + use std::collections::hash_map::Entry::*; 65 + match map.entry(k) { 66 + Vacant(vacant_entry) => { 67 + vacant_entry.insert(v); 68 + } 69 + Occupied(mut occupied_entry) => merge_fn(occupied_entry.get_mut(), v), 70 + }; 71 + 72 + map 73 + }) 74 + } 38 75 } 39 76 40 77 impl<T> IterUtils for T where T: Iterator {} ··· 147 184 148 185 #[cfg(test)] 149 186 mod tests { 187 + use itertools::Itertools; 188 + 150 189 use super::*; 151 190 152 191 #[test] ··· 198 237 itertools::assert_equal(Few::<i32, 1>::default(), []); 199 238 itertools::assert_equal(Few::<i32, 2>::default(), []); 200 239 itertools::assert_equal(Few::<i32, 3>::default(), []); 240 + } 241 + 242 + #[test] 243 + fn merge_by_counts() { 244 + let pi_str = format!("{:.15}", core::f64::consts::PI); 245 + let digits_of_pi = pi_str.chars().filter_map(|c| c.to_digit(10)); 246 + let btree_map = digits_of_pi 247 + .clone() 248 + .map(|d| (d, 1)) 249 + .btree_merge(|c, i| *c += i); 250 + let hash_map = digits_of_pi 251 + .clone() 252 + .map(|d| (d, 1)) 253 + .hash_merge(|c, i| *c += i); 254 + let counts = digits_of_pi.counts(); 255 + assert_eq!(hash_map, counts); 256 + itertools::assert_equal(btree_map, hash_map.into_iter().sorted()); 201 257 } 202 258 }