Advent of Code solutions in Rust

feat: day 16 of AoC 2020

+230 -2
+228
aoc_2020/src/day16.rs
··· 1 + use std::{collections::HashSet, ops::RangeInclusive}; 2 + 3 + use anyhow::{Context, anyhow, bail}; 4 + use aoc_companion::prelude::*; 5 + use itertools::Itertools as _; 6 + 7 + pub(crate) struct Door<'input> { 8 + rules: Vec<Rule<'input>>, 9 + my_ticket: Vec<u64>, 10 + nearby_tickets: Vec<Vec<u64>>, 11 + } 12 + 13 + impl<'input> Solution<'input> for Door<'input> { 14 + fn parse(input: &'input str) -> Result<Self> { 15 + let [rules, my_ticket, nearby_tickets] = 16 + aoc_utils::array::from_iter_exact(input.split("\n\n")) 17 + .map_err(|v| anyhow!("expected three sections, got {} sections", v.len()))?; 18 + let rules = rules.lines().map(parse_rule).try_collect()?; 19 + let my_ticket = my_ticket 20 + .strip_prefix("your ticket:\n") 21 + .ok_or_else(|| anyhow!("missing introducer \"your ticket:\""))? 22 + .split(',') 23 + .map(str::parse) 24 + .try_collect() 25 + .with_context(|| anyhow!("failed to parse my ticket"))?; 26 + let nearby_tickets = nearby_tickets 27 + .strip_prefix("nearby tickets:\n") 28 + .ok_or_else(|| anyhow!("missing introducer \"nearby tickets:\""))? 29 + .lines() 30 + .map(|ticket| { 31 + ticket 32 + .split(',') 33 + .map(str::parse) 34 + .try_collect() 35 + .with_context(|| anyhow!("failed to parse nearby ticket {ticket:?}")) 36 + }) 37 + .try_collect()?; 38 + Ok(Self { 39 + rules, 40 + my_ticket, 41 + nearby_tickets, 42 + }) 43 + } 44 + 45 + fn part1(&self) -> u64 { 46 + self.nearby_tickets 47 + .iter() 48 + .flatten() 49 + .filter(|&&x| !is_valid_for_any_field(x, &self.rules)) 50 + .sum() 51 + } 52 + 53 + fn part2(&self) -> Result<u64> { 54 + let mapping = find_field_mapping(self.valid_tickets(), &self.rules)?; 55 + Ok(self 56 + .rules 57 + .iter() 58 + .enumerate() 59 + .filter(|(_, rule)| rule.field_name.starts_with("departure")) 60 + .map(|(i, _)| self.my_ticket[mapping[i]]) 61 + .product()) 62 + } 63 + } 64 + 65 + impl Door<'_> { 66 + fn valid_tickets(&self) -> impl Iterator<Item = &[u64]> { 67 + self.nearby_tickets 68 + .iter() 69 + .filter(|ticket| { 70 + ticket 71 + .iter() 72 + .all(|&x| is_valid_for_any_field(x, &self.rules)) 73 + }) 74 + .map(Vec::as_slice) 75 + } 76 + } 77 + 78 + #[derive(Clone, Debug, PartialEq, Eq)] 79 + struct Rule<'input> { 80 + field_name: &'input str, 81 + domain: [RangeInclusive<u64>; 2], 82 + } 83 + 84 + fn parse_rule<'input>(s: &'input str) -> Result<Rule<'input>> { 85 + let Some((field_name, ranges)) = s.split_once(':') else { 86 + bail!("missing colon in rule {s:?}"); 87 + }; 88 + Ok(Rule { 89 + field_name, 90 + domain: aoc_utils::array::try_from_iter_exact(ranges.trim_start().split("or").map(|r| { 91 + r.trim() 92 + .split_once('-') 93 + .ok_or_else(|| anyhow!("missing dash in range {r:?}")) 94 + .and_then(|(from, to)| Ok(from.parse()?..=to.parse()?)) 95 + .with_context(|| anyhow!("failed to parse range {r:?}")) 96 + }))? 97 + .map_err(|v| anyhow!("expected exactly two ranges, got {}", v.len()))?, 98 + }) 99 + } 100 + 101 + fn is_valid_for_any_field(x: u64, rules: &[Rule]) -> bool { 102 + rules 103 + .iter() 104 + .flat_map(|rule| rule.domain.iter()) 105 + .any(|r| r.contains(&x)) 106 + } 107 + 108 + fn find_field_mapping<'a>( 109 + tickets: impl IntoIterator<Item = &'a [u64]>, 110 + rules: &[Rule], 111 + ) -> Result<Vec<usize>> { 112 + let mut possible_indices = vec![HashSet::<_>::from_iter(0..rules.len()); rules.len()]; 113 + 114 + for ticket in tickets { 115 + for (i, field) in ticket.iter().enumerate() { 116 + for (rule_indices, rule) in possible_indices.iter_mut().zip(rules) { 117 + if !rule.domain.iter().any(|r| r.contains(field)) { 118 + rule_indices.remove(&i); 119 + } 120 + } 121 + } 122 + } 123 + 124 + let mut res = vec![None; rules.len()]; 125 + while let Some((i, s)) = possible_indices 126 + .iter() 127 + .enumerate() 128 + .find(|(_, s)| s.len() == 1) 129 + { 130 + let j = *s.iter().next().unwrap(); 131 + res[i] = Some(j); 132 + for p in &mut possible_indices { 133 + p.remove(&j); 134 + } 135 + } 136 + 137 + res.into_iter() 138 + .map(|o| o.ok_or_else(|| anyhow!("field mapping ambiguous"))) 139 + .try_collect() 140 + } 141 + 142 + #[cfg(test)] 143 + mod tests { 144 + use super::*; 145 + 146 + const EXAMPLE_INPUT: &str = "\ 147 + class: 1-3 or 5-7 148 + row: 6-11 or 33-44 149 + seat: 13-40 or 45-50 150 + 151 + your ticket: 152 + 7,1,14 153 + 154 + nearby tickets: 155 + 7,3,47 156 + 40,4,50 157 + 55,2,20 158 + 38,6,12"; 159 + 160 + const EXAMPLE_RULES: &[Rule] = &[ 161 + Rule { 162 + field_name: "class", 163 + domain: [1..=3, 5..=7], 164 + }, 165 + Rule { 166 + field_name: "row", 167 + domain: [6..=11, 33..=44], 168 + }, 169 + Rule { 170 + field_name: "seat", 171 + domain: [13..=40, 45..=50], 172 + }, 173 + ]; 174 + 175 + const EXAMPLE_MY_TICKET: [u64; 3] = [7, 1, 14]; 176 + 177 + const EXAMPLE_NEARBY_TICKETS: &[[u64; 3]] = 178 + &[[7, 3, 47], [40, 4, 50], [55, 2, 20], [38, 6, 12]]; 179 + 180 + const SECOND_EXAMPLE_INPUT: &str = "\ 181 + class: 0-1 or 4-19 182 + row: 0-5 or 8-19 183 + seat: 0-13 or 16-19 184 + 185 + your ticket: 186 + 11,12,13 187 + 188 + nearby tickets: 189 + 3,9,18 190 + 15,1,5 191 + 5,14,9"; 192 + 193 + #[test] 194 + fn parse_example_input() { 195 + let Door { 196 + rules, 197 + my_ticket, 198 + nearby_tickets, 199 + } = Door::parse(EXAMPLE_INPUT).unwrap(); 200 + itertools::assert_equal(&rules, EXAMPLE_RULES); 201 + assert_eq!(my_ticket, EXAMPLE_MY_TICKET); 202 + itertools::assert_equal(&nearby_tickets, EXAMPLE_NEARBY_TICKETS); 203 + } 204 + 205 + #[test] 206 + fn find_invalid_values_in_example() { 207 + itertools::assert_equal( 208 + EXAMPLE_NEARBY_TICKETS 209 + .iter() 210 + .flatten() 211 + .filter(|&&x| !is_valid_for_any_field(x, EXAMPLE_RULES)), 212 + &[4, 55, 12], 213 + ); 214 + } 215 + 216 + #[test] 217 + fn find_field_mapping_in_second_example() { 218 + let Door { 219 + rules, 220 + my_ticket: _, 221 + nearby_tickets, 222 + } = Door::parse(SECOND_EXAMPLE_INPUT).unwrap(); 223 + assert_eq!( 224 + find_field_mapping(nearby_tickets.iter().map(Vec::as_slice), &rules).unwrap(), 225 + [1, 0, 2] 226 + ); 227 + } 228 + }
+2 -2
aoc_2020/src/main.rs
··· 15 15 mod day13; 16 16 mod day14; 17 17 mod day15; 18 - // mod day16; 18 + mod day16; 19 19 // mod day17; 20 20 // mod day18; 21 21 // mod day19; ··· 46 46 door!(2020-12-13 ~> day13), 47 47 door!(2020-12-14 ~> day14), 48 48 door!(2020-12-15 ~> day15), 49 - // door!(2020-12-16 ~> day16), 49 + door!(2020-12-16 ~> day16), 50 50 // door!(2020-12-17 ~> day17), 51 51 // door!(2020-12-18 ~> day18), 52 52 // door!(2020-12-19 ~> day19),