From 3c813855e3b64f6f9ca6bac22cc9a82ae3c0ec11 Mon Sep 17 00:00:00 2001 From: Matej Focko Date: Tue, 19 Dec 2023 10:42:06 +0100 Subject: [PATCH] day(19): add solution Signed-off-by: Matej Focko --- samples/day19.txt | 17 +++ src/bin/day19.rs | 342 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 359 insertions(+) create mode 100644 samples/day19.txt create mode 100644 src/bin/day19.rs diff --git a/samples/day19.txt b/samples/day19.txt new file mode 100644 index 0000000..e5b5d64 --- /dev/null +++ b/samples/day19.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/day19.rs b/src/bin/day19.rs new file mode 100644 index 0000000..582decb --- /dev/null +++ b/src/bin/day19.rs @@ -0,0 +1,342 @@ +use std::{cmp, collections::HashMap, str::FromStr}; + +use aoc_2023::*; + +type Output1 = i64; +type Output2 = Output1; + +#[derive(Debug, Clone, Copy)] +struct Part { + x: i64, + m: i64, + a: i64, + s: i64, +} + +impl FromStr for Part { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + let s = &s[1..s.len() - 1]; + + let (x, m, a, s) = s + .split(',') + .map(|var| var.split_once('=').unwrap().1.parse().unwrap()) + .collect_tuple() + .unwrap(); + + Ok(Self { x, m, a, s }) + } +} + +impl Part { + fn get(&self, v: char) -> i64 { + match v { + 'x' => self.x, + 'm' => self.m, + 'a' => self.a, + 's' => self.s, + _ => unreachable!("unknown rating"), + } + } + + fn rating(&self) -> i64 { + self.x + self.m + self.a + self.s + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum Action { + Accept, + Reject, + Forward(String), +} + +impl FromStr for Action { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + Ok(match s { + "A" => Action::Accept, + "R" => Action::Reject, + next => Action::Forward(next.to_owned()), + }) + } +} + +enum Rule { + Conditional(char, cmp::Ordering, i64, Action), + Passthrough(Action), +} + +impl FromStr for Rule { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + Ok(match s.split_once(':') { + Some((condition, action)) => { + let var = condition.chars().nth(0).unwrap(); + let sign = match condition.chars().nth(1).unwrap() { + '<' => cmp::Ordering::Less, + '>' => cmp::Ordering::Greater, + _ => unreachable!("not supported comparison"), + }; + let value = condition[2..].parse().unwrap(); + + Rule::Conditional(var, sign, value, action.parse().unwrap()) + } + None => Rule::Passthrough(s.parse().unwrap()), + }) + } +} + +struct Workflow { + rules: Vec, +} + +impl FromStr for Workflow { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + let rules = s.split(',').map(|r| r.parse().unwrap()).collect_vec(); + + Ok(Self { rules }) + } +} + +impl Workflow { + fn evaluate<'a>(&'a self, p: &Part) -> &'a Action { + for r in &self.rules { + match r { + Rule::Conditional(v, sign, value, action) => { + if p.get(*v).cmp(value) == *sign { + return action; + } + } + Rule::Passthrough(action) => { + return action; + } + } + } + + unreachable!("there's always passthrough at the end") + } +} + +#[derive(Debug, Clone, Copy)] +struct VarRange { + min: i64, + max: i64, +} + +impl Default for VarRange { + fn default() -> Self { + Self { min: 1, max: 4000 } + } +} + +impl VarRange { + fn len(&self) -> i64 { + self.max - self.min + 1 + } + + fn sat(&self, sign: &cmp::Ordering, value: &i64) -> VarRange { + match sign { + cmp::Ordering::Less => Self { + max: value - 1, + ..*self + }, + cmp::Ordering::Greater => Self { + min: value + 1, + ..*self + }, + _ => unreachable!("unsupported comparison"), + } + } + + fn unsat(&self, sign: &cmp::Ordering, value: &i64) -> VarRange { + match sign { + cmp::Ordering::Less => Self { + min: *value, + ..*self + }, + cmp::Ordering::Greater => Self { + max: *value, + ..*self + }, + _ => unreachable!("unsupported comparison"), + } + } +} + +#[derive(Debug, Clone, Copy, Default)] +struct PartRange { + x: VarRange, + m: VarRange, + a: VarRange, + s: VarRange, +} + +impl PartRange { + fn len(&self) -> i64 { + self.x.len() * self.m.len() * self.a.len() * self.s.len() + } + + fn sat(&self, r: &Rule) -> PartRange { + match r { + Rule::Conditional(var, sign, value, _) => match var { + 'x' => Self { + x: self.x.sat(sign, value), + ..*self + }, + 'm' => Self { + m: self.m.sat(sign, value), + ..*self + }, + 'a' => Self { + a: self.a.sat(sign, value), + ..*self + }, + 's' => Self { + s: self.s.sat(sign, value), + ..*self + }, + _ => unreachable!("unknown variable"), + }, + _ => unreachable!(), + } + } + + fn unsat(&self, r: &Rule) -> PartRange { + match r { + Rule::Conditional(var, sign, value, _) => match var { + 'x' => Self { + x: self.x.unsat(sign, value), + ..*self + }, + 'm' => Self { + m: self.m.unsat(sign, value), + ..*self + }, + 'a' => Self { + a: self.a.unsat(sign, value), + ..*self + }, + 's' => Self { + s: self.s.unsat(sign, value), + ..*self + }, + _ => unreachable!("unknown variable"), + }, + _ => unreachable!(), + } + } +} + +struct Day19 { + workflows: HashMap, + parts: Vec, +} + +impl Day19 { + fn accepts(&self, p: &Part) -> bool { + let mut workflow = "in"; + loop { + match self.workflows[workflow].evaluate(p) { + Action::Accept => { + return true; + } + Action::Reject => { + return false; + } + Action::Forward(next) => { + workflow = &next; + } + } + } + } + + fn dfs<'a>( + &'a self, + visited: &mut HashMap<&'a str, bool>, + w: &'a str, + mut prange: PartRange, + ) -> i64 { + if visited.contains_key(w) { + return 0; + } + + let mut accepted = 0; + visited.insert(w, true); + + let workflow = &self.workflows[w]; + for r in &workflow.rules { + match r { + c @ Rule::Conditional(_, _, _, action) => { + match action { + Action::Accept => { + let satisfied = prange.sat(c); + accepted += satisfied.len(); + } + Action::Forward(nw) => { + accepted += self.dfs(visited, nw, prange.sat(c)); + } + _ => {} + } + + prange = prange.unsat(c); + } + Rule::Passthrough(Action::Accept) => { + accepted += prange.len(); + } + Rule::Passthrough(Action::Forward(nw)) => { + accepted += self.dfs(visited, nw, prange); + } + _ => {} + } + } + + accepted + } +} + +impl Solution for Day19 { + fn new>(pathname: P) -> Self { + let input = file_to_string(pathname); + let (workflows, parts) = input.split_once("\n\n").unwrap(); + + let workflows = workflows + .lines() + .map(|l| { + let (name, rules) = l.split_once('{').unwrap(); + + let name = name.to_owned(); + let workflow = rules[..rules.len() - 1].parse::().unwrap(); + + (name, workflow) + }) + .collect(); + + let parts = parts.lines().map(|l| l.parse().unwrap()).collect_vec(); + Self { workflows, parts } + } + + fn part_1(&mut self) -> Output1 { + self.parts + .iter() + .filter_map(|p| self.accepts(p).then_some(p.rating())) + .sum() + } + + fn part_2(&mut self) -> Output2 { + let mut visited = HashMap::new(); + + self.dfs(&mut visited, "in", PartRange::default()) + } +} + +fn main() -> Result<()> { + Day19::main() +} + +test_sample!(day_19, Day19, 19114, 167409079868000);