From 1d63601c9b2de29685b6054be19dbb0a75f5d35f Mon Sep 17 00:00:00 2001 From: Joao Conde <16060539+joao-conde@users.noreply.github.com> Date: Sat, 21 Sep 2024 23:15:34 +0100 Subject: [PATCH] refactor: day19 2023 --- 2023/src/bin/day19.rs | 300 ++++++++++++++++++++++-------------------- 1 file changed, 156 insertions(+), 144 deletions(-) diff --git a/2023/src/bin/day19.rs b/2023/src/bin/day19.rs index c177ceb..f2da386 100644 --- a/2023/src/bin/day19.rs +++ b/2023/src/bin/day19.rs @@ -1,10 +1,11 @@ use regex::Regex; -use std::{collections::HashMap, fmt::Display}; +use std::collections::HashMap; const PART_RE: &str = r"x=(?P\d*),m=(?P\d*),a=(?P\d*),s=(?P\d*)"; const WORKFLOW_RE: &str = r"(?P.*)\{(?P.*)\}"; -#[derive(Debug)] +type WorkflowId = String; + struct Part { x: usize, m: usize, @@ -12,40 +13,130 @@ struct Part { s: usize, } -#[derive(Debug)] +impl From<&str> for Part { + fn from(value: &str) -> Self { + let re = Regex::new(PART_RE).unwrap(); + let captures = re.captures(value).unwrap(); + Part { + x: captures["x"].parse().unwrap(), + m: captures["m"].parse().unwrap(), + a: captures["a"].parse().unwrap(), + s: captures["s"].parse().unwrap(), + } + } +} + +impl Part { + fn rating(&self, category: &Category) -> usize { + match category { + Category::X => self.x, + Category::M => self.m, + Category::A => self.a, + Category::S => self.s, + } + } + + fn total_rating(&self) -> usize { + self.x + self.m + self.a + self.s + } +} + +enum Category { + X, + M, + A, + S, +} + +impl From<&str> for Category { + fn from(value: &str) -> Self { + match value { + "x" => Category::X, + "m" => Category::M, + "a" => Category::A, + "s" => Category::S, + _ => unreachable!(), + } + } +} + struct Workflow { - name: String, + name: WorkflowId, rules: RuleTree, } -#[derive(Debug)] +impl From<&str> for Workflow { + fn from(value: &str) -> Self { + let re = Regex::new(WORKFLOW_RE).unwrap(); + let captures = re.captures(value).unwrap(); + + let name = captures["name"].to_string(); + let rules = RuleTree::from(&captures["rules"]); + + Workflow { name, rules } + } +} + +impl Workflow { + fn test(&self, workflows: &HashMap, part: &Part) -> Verdict { + self.rules.test(workflows, part) + } +} + enum RuleTree { Test { - operand1: String, + operand1: Category, operator: Operator, operand2: usize, true_branch: Box, false_branch: Box, }, - Accepted, - Rejected, - Workflow(String), + Workflow(WorkflowId), + Verdict(Verdict), } -#[derive(Debug)] -enum Operator { - GreaterThan, - LesserThan, -} +impl From<&str> for RuleTree { + fn from(value: &str) -> Self { + if value == "A" { + return RuleTree::Verdict(Verdict::Accepted); + } -impl Workflow { - fn test(&self, part: &Part) -> String { - self.rules.test(part) + if value == "R" { + return RuleTree::Verdict(Verdict::Rejected); + } + + let Some((rule, other_rules)) = value.split_once(',') else { + return RuleTree::Workflow(value.to_string()); + }; + + let (test, true_branch) = rule.split_once(':').unwrap(); + + let operator = if test.contains('>') { + Operator::GreaterThan + } else if test.contains('<') { + Operator::LesserThan + } else { + return RuleTree::Workflow(test.to_string()); + }; + + let (operand1, operand2) = test.split_once(&operator.to_string()).unwrap(); + let operand1 = Category::from(operand1); + let operand2 = operand2.parse().unwrap(); + let true_branch = Box::new(RuleTree::from(true_branch)); + let false_branch = Box::new(RuleTree::from(other_rules)); + + RuleTree::Test { + operand1, + operator, + operand2, + true_branch, + false_branch, + } } } impl RuleTree { - fn test(&self, part: &Part) -> String { + fn test(&self, workflows: &HashMap, part: &Part) -> Verdict { match self { RuleTree::Test { operand1, @@ -54,155 +145,78 @@ impl RuleTree { true_branch, false_branch, } => { - if operator.test(part.get(operand1), *operand2) { - true_branch.test(part) + if operator.test(part.rating(operand1), *operand2) { + true_branch.test(workflows, part) } else { - false_branch.test(part) + false_branch.test(workflows, part) } } - RuleTree::Accepted => "A".to_string(), - RuleTree::Rejected => "R".to_string(), - RuleTree::Workflow(name) => name.to_string(), + RuleTree::Workflow(name) => workflows[name].rules.test(workflows, part), + RuleTree::Verdict(Verdict::Accepted) => Verdict::Accepted, + RuleTree::Verdict(Verdict::Rejected) => Verdict::Rejected, } } } -impl Part { - fn get(&self, component: &str) -> usize { - match component { - "x" => self.x, - "m" => self.m, - "a" => self.a, - "s" => self.s, - _ => unreachable!(), - } - } - - fn rating(&self) -> usize { - self.x + self.m + self.a + self.s - } +#[derive(PartialEq)] +enum Verdict { + Accepted, + Rejected, } -impl Operator { - fn test(&self, operand1: usize, operand2: usize) -> bool { - match self { - Operator::GreaterThan => operand1 > operand2, - Operator::LesserThan => operand1 < operand2, - } - } +enum Operator { + GreaterThan, + LesserThan, } -impl Display for Operator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl ToString for Operator { + fn to_string(&self) -> String { match self { - Operator::GreaterThan => f.write_str(">"), - Operator::LesserThan => f.write_str("<"), - } - } -} - -impl From<&str> for Part { - fn from(value: &str) -> Self { - let re = Regex::new(PART_RE).unwrap(); - let captures = re.captures(value).unwrap(); - Part { - x: captures["x"].parse().unwrap(), - m: captures["m"].parse().unwrap(), - a: captures["a"].parse().unwrap(), - s: captures["s"].parse().unwrap(), + Operator::GreaterThan => ">".to_string(), + Operator::LesserThan => "<".to_string(), } } } -impl From<&str> for Workflow { - fn from(value: &str) -> Self { - let re = Regex::new(WORKFLOW_RE).unwrap(); - let captures = re.captures(value).unwrap(); - - let name = captures["name"].to_string(); - let rules = RuleTree::from(&captures["rules"]); - - Workflow { name, rules } - } -} - -impl From<&str> for RuleTree { - fn from(value: &str) -> Self { - if value == "A" { - RuleTree::Accepted - } else if value == "R" { - RuleTree::Rejected - } else { - let Some((rule, others)) = value.split_once(',') else { - return RuleTree::Workflow(value.to_string()); - }; - - let (test, true_branch) = rule.split_once(':').unwrap(); - - if !test.contains('>') && !test.contains('<') { - RuleTree::Workflow(test.to_string()) - } else { - let operator = if test.contains('>') { - Operator::GreaterThan - } else { - Operator::LesserThan - }; - - let (operand1, operand2) = test.split_once(&operator.to_string()).unwrap(); - let operand1 = operand1.to_string(); - let operand2 = operand2.parse().unwrap(); - let true_branch = Box::new(RuleTree::from(true_branch)); - let false_branch = Box::new(RuleTree::from(others)); - - RuleTree::Test { - operand1, - operator, - operand2, - true_branch, - false_branch, - } - } +impl Operator { + fn test(&self, operand1: usize, operand2: usize) -> bool { + match self { + Operator::GreaterThan => operand1 > operand2, + Operator::LesserThan => operand1 < operand2, } } } fn main() { let input = std::fs::read_to_string("input/day19").unwrap(); - let (workflows, parts) = input.split_once("\n\n").unwrap(); let parts: Vec = parts.lines().map(Part::from).collect(); - let workflows: HashMap = - workflows.lines().fold(HashMap::new(), |mut acc, line| { - let workflow = Workflow::from(line); - acc.insert(workflow.name.clone(), workflow); - acc - }); + let workflows = workflows.lines().fold(HashMap::new(), |mut acc, line| { + let workflow = Workflow::from(line); + acc.insert(workflow.name.clone(), workflow); + acc + }); let p1: usize = parts .iter() .filter(|p| accepted(&workflows, p)) - .map(|p| p.rating()) + .map(|p| p.total_rating()) .sum(); println!("Part1: {p1}"); let p2 = combinations(&workflows); println!("Part2: {p2}"); + + assert!(p1 == 383682); + assert!(p2 == 117954800808317); } -fn accepted(workflows: &HashMap, part: &Part) -> bool { - let mut workflow = &workflows["in"]; - loop { - let result = workflow.test(part); - match result.as_str() { - "A" => return true, - "R" => return false, - next_workflow => workflow = &workflows[next_workflow], - } - } +fn accepted(workflows: &HashMap, part: &Part) -> bool { + workflows["in"].test(workflows, part) == Verdict::Accepted } -fn combinations(workflows: &HashMap) -> usize { +fn combinations(workflows: &HashMap) -> usize { let mut combinations = 0; let mut queue = vec![( @@ -233,32 +247,30 @@ fn combinations(workflows: &HashMap) -> usize { }; let (mut tx, mut tm, mut ta, mut ts) = (x, m, a, s); - match operand1.as_str() { - "x" => tx = true_bounds(tx), - "m" => tm = true_bounds(tm), - "a" => ta = true_bounds(ta), - "s" => ts = true_bounds(ts), - _ => unreachable!(), + match operand1 { + Category::X => tx = true_bounds(tx), + Category::M => tm = true_bounds(tm), + Category::A => ta = true_bounds(ta), + Category::S => ts = true_bounds(ts), } let (mut fx, mut fm, mut fa, mut fs) = (x, m, a, s); - match operand1.as_str() { - "x" => fx = false_bounds(fx), - "m" => fm = false_bounds(fm), - "a" => fa = false_bounds(fa), - "s" => fs = false_bounds(fs), - _ => unreachable!(), + match operand1 { + Category::X => fx = false_bounds(fx), + Category::M => fm = false_bounds(fm), + Category::A => fa = false_bounds(fa), + Category::S => fs = false_bounds(fs), } queue.push((&true_branch, tx, tm, ta, ts)); queue.push((&false_branch, fx, fm, fa, fs)); } - RuleTree::Accepted => { + RuleTree::Workflow(name) => queue.push((&workflows[name].rules, x, m, a, s)), + RuleTree::Verdict(Verdict::Accepted) => { combinations += (x.1 + 1 - x.0) * (m.1 + 1 - m.0) * (a.1 + 1 - a.0) * (s.1 + 1 - s.0); } - RuleTree::Rejected => (), - RuleTree::Workflow(name) => queue.push((&workflows[name].rules, x, m, a, s)), + RuleTree::Verdict(Verdict::Rejected) => (), } }