From 00000350de379e66e14b71e649ca077a3a4449d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Jane=C5=BEi=C4=8D?= Date: Tue, 19 Dec 2023 17:23:31 +0100 Subject: [PATCH] wip: day 19 --- data/examples/19.txt | 17 ++ src/bin/19.rs | 387 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 404 insertions(+) create mode 100644 data/examples/19.txt create mode 100644 src/bin/19.rs diff --git a/data/examples/19.txt b/data/examples/19.txt new file mode 100644 index 000000000..e5b5d64 --- /dev/null +++ b/data/examples/19.txt @@ -0,0 +1,17 @@ +px{a<2006:qkq,m>2090:A,rfg} +pv{a>1716:R,A} +lnx{m>1548:A,A} +rfg{s<537:gd,x>2440:R,A} +qs{s>3448:A,lnx} +qkq{x<1416:A,crn} +crn{x>2662:A,R} +in{s<1351:px,qqz} +qqz{s>2770:qs,m<1801:hdj,R} +gd{a>3333:R,R} +hdj{m>838:A,pv} + +{x=787,m=2655,a=1222,s=2876} +{x=1679,m=44,a=2067,s=496} +{x=2036,m=264,a=79,s=2244} +{x=2461,m=1339,a=466,s=291} +{x=2127,m=1623,a=2188,s=1013} diff --git a/src/bin/19.rs b/src/bin/19.rs new file mode 100644 index 000000000..81b1e65 --- /dev/null +++ b/src/bin/19.rs @@ -0,0 +1,387 @@ +use std::{ + collections::HashMap, + ops::{Index, IndexMut}, + str::FromStr, +}; + +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +enum Gear { + X, + M, + A, + S, +} + +#[derive(Debug)] +struct GearParseError; + +impl TryFrom<&str> for Gear { + type Error = GearParseError; + fn try_from(value: &str) -> Result { + use Gear::*; + Ok(match value { + "x" => X, + "m" => M, + "a" => A, + "s" => S, + _ => return Err(GearParseError), + }) + } +} + +#[derive(Debug, Hash, PartialEq, Eq)] +enum Comparator { + Lt, + Gt, +} + +#[derive(Debug)] +struct ComparatorParseError; + +impl TryFrom for Comparator { + type Error = ComparatorParseError; + fn try_from(value: char) -> Result { + use Comparator::*; + Ok(match value { + '<' => Lt, + '>' => Gt, + _ => return Err(ComparatorParseError), + }) + } +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +enum Resolver { + Accepted, + Rejected, + Delegated(String), +} + +impl From<&str> for Resolver { + fn from(value: &str) -> Self { + use Resolver::*; + match value { + "A" => Accepted, + "R" => Rejected, + x => Delegated(x.to_string()), + } + } +} + +#[derive(Debug, Hash, PartialEq, Eq)] +struct Workflow { + workflows: Vec, +} + +#[derive(Debug)] +struct ParseWorkflowError; + +impl TryFrom<&str> for Workflow { + type Error = ParseWorkflowError; + fn try_from(value: &str) -> Result { + Ok(Self { + workflows: value.split(',').filter_map(|x| x.try_into().ok()).collect(), + }) + } +} + +#[derive(Debug, Hash, PartialEq, Eq)] +enum WorkflowInner { + Resolver(Resolver), + Rule((Gear, Comparator, usize, Resolver)), +} + +#[derive(Debug)] +struct ParseWorkflowInnerError; + +impl TryFrom<&str> for WorkflowInner { + type Error = ParseWorkflowInnerError; + fn try_from(value: &str) -> Result { + if !value.contains(':') { + return Ok(WorkflowInner::Resolver(Resolver::from(value))); + } + + let (rest, resolver) = value.split_once(':').unwrap(); + let resolver = Resolver::from(resolver); + + let (gear, number) = rest.split_once(|x| x == '<' || x == '>').unwrap(); + let gear = Gear::try_from(gear).map_err(|_| ParseWorkflowInnerError)?; + let number = number.parse().map_err(|_| ParseWorkflowInnerError)?; + + let comparator = if value.contains('<') { '<' } else { '>' }; + let comparator = Comparator::try_from(comparator).map_err(|_| ParseWorkflowInnerError)?; + + Ok(WorkflowInner::Rule((gear, comparator, number, resolver))) + } +} + +#[derive(Debug)] +struct Xmas { + x: usize, + m: usize, + a: usize, + s: usize, +} + +#[derive(Debug)] +struct ParseXmasError; + +impl FromStr for Xmas { + type Err = ParseXmasError; + fn from_str(s: &str) -> Result { + let s = s.strip_prefix('{').ok_or(ParseXmasError)?; + let s = s.strip_suffix('}').ok_or(ParseXmasError)?; + let xmas: Vec<_> = s + .split(',') + .filter_map(|x| x.split_once('=')) + .map(|x| x.1) + .filter_map(|x| x.parse().ok()) + .collect(); + + Ok(Self { + x: xmas[0], + m: xmas[1], + a: xmas[2], + s: xmas[3], + }) + } +} + +impl Index for Xmas { + type Output = usize; + fn index(&self, index: Gear) -> &Self::Output { + use Gear::*; + match index { + X => &self.x, + M => &self.m, + A => &self.a, + S => &self.s, + } + } +} + +impl IndexMut for Xmas { + fn index_mut(&mut self, index: Gear) -> &mut Self::Output { + use Gear::*; + match index { + X => &mut self.x, + M => &mut self.m, + A => &mut self.a, + S => &mut self.s, + } + } +} + +impl Xmas { + fn sum(&self) -> usize { + self.x + self.m + self.a + self.s + } + + fn apply(&self, workflow: &Workflow) -> Resolver { + for w in workflow.workflows.iter() { + match w { + WorkflowInner::Resolver(x) => { + return x.clone(); + } + WorkflowInner::Rule((g, c, n, r)) => { + let is_match = match c { + Comparator::Gt => self[*g] > *n, + Comparator::Lt => self[*g] < *n, + }; + if is_match { + return r.clone(); + } + } + } + } + + unreachable!() + } +} + +#[derive(Debug, Clone, Copy)] +struct XmasRange { + x: (usize, usize), + m: (usize, usize), + a: (usize, usize), + s: (usize, usize), +} + +impl Index for XmasRange { + type Output = (usize, usize); + fn index(&self, index: Gear) -> &Self::Output { + use Gear::*; + match index { + X => &self.x, + M => &self.m, + A => &self.a, + S => &self.s, + } + } +} + +impl IndexMut for XmasRange { + fn index_mut(&mut self, index: Gear) -> &mut Self::Output { + use Gear::*; + match index { + X => &mut self.x, + M => &mut self.m, + A => &mut self.a, + S => &mut self.s, + } + } +} + +impl XmasRange { + fn size(&self) -> usize { + (self.x.1 - self.x.0) + * (self.m.1 - self.m.0) + * (self.a.1 - self.a.0) + * (self.s.1 - self.s.0) + } + + fn divide(self, workflow: &Workflow) -> Vec<(Self, Resolver)> { + let mut processed = Vec::new(); + let mut rest = Vec::new(); + + for w in workflow.workflows.iter() { + let mut new_rest = Vec::new(); + + while let Some(xmas_range) = rest.pop() { + match w { + WorkflowInner::Resolver(r) => processed.push((xmas_range, r.clone())), + WorkflowInner::Rule((g, c, n, r)) => { + let compare = |x: usize, y: usize| match c { + Comparator::Gt => x > y, + Comparator::Lt => x < y, + }; + + let mut min_ok = usize::MAX; + let mut max_ok = usize::MIN; + let mut min_e = usize::MAX; + let mut max_e = usize::MIN; + + for i in self[*g].0..=self[*g].1 { + if compare(i, *n) { + max_ok = if max_ok < i { i } else { max_ok }; + min_ok = if min_ok > i { i } else { min_ok }; + } else { + max_e = if max_e < i { i } else { max_e }; + min_e = if min_e > i { i } else { min_e }; + } + } + + if min_e > max_e { + let mut r = self; + r[*g] = (min_e, max_e); + new_rest.push(r); + } + if min_ok > max_ok { + let mut p = self; + p[*g] = (min_ok, max_ok); + processed.push((p, r.clone())); + } + } + } + } + + rest = new_rest; + } + + processed + } +} + +fn build_map(input: &str) -> HashMap<&str, Workflow> { + let mut map = HashMap::new(); + for (s, w) in input + .split("\n\n") + .next() + .unwrap() + .lines() + .filter_map(|x| x.strip_suffix('}')) + .filter_map(|x| x.split_once('{')) + .filter_map(|(s, w)| Workflow::try_from(w).ok().map(|x| (s, x))) + { + map.insert(s, w); + } + + map +} + +pub fn part_one(input: &str) -> Option { + let map = build_map(input); + let xmas_vec: Vec = input + .split("\n\n") + .nth(1)? + .lines() + .filter_map(|x| x.parse().ok()) + .collect(); + + let mut total = 0; + + 'outer: for xmas in xmas_vec.into_iter() { + let mut workflow = &map["in"]; + 'apply: loop { + match xmas.apply(workflow) { + Resolver::Accepted => break 'apply, + Resolver::Rejected => continue 'outer, + Resolver::Delegated(x) => workflow = &map[x.as_str()], + } + } + + total += xmas.sum(); + } + + Some(total) +} + +pub fn part_two(input: &str) -> Option { + let map = build_map(input); + + let mut finished = Vec::new(); + let mut stack = vec![( + XmasRange { + x: (0, 4000), + m: (0, 4000), + a: (0, 4000), + s: (0, 4000), + }, + Resolver::Delegated("in".to_string()), + )]; + + while let Some((xmas_range, resolver)) = stack.pop() { + match resolver { + Resolver::Accepted => finished.push(xmas_range), + Resolver::Rejected => { + continue; + } + Resolver::Delegated(x) => { + let workflow = &map[x.as_str()]; + stack.append(&mut xmas_range.divide(workflow)) + } + } + } + + Some(finished.iter().map(XmasRange::size).sum()) +} +aoc::solution!(19); + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_part_one() { + assert_eq!( + part_one(&aoc::template::read_file("examples", 19)), + Some(19114) + ); + } + #[test] + fn test_part_two() { + assert_eq!( + part_two(&aoc::template::read_file("examples", 19)), + Some(167409079868000) + ); + } +}