use std::collections::HashMap; use std::fs::File; use std::io::{BufRead, BufReader, Lines}; use std::ops::Range; use std::time::Instant; // BOILERPLATE type InputIter = Lines>; fn get_input() -> InputIter { let f = File::open("input").unwrap(); let br = BufReader::new(f); br.lines() } fn main() { let start = Instant::now(); let ans1 = problem1(get_input()); let duration = start.elapsed(); println!("Problem 1 solution: {} [{}s]", ans1, duration.as_secs_f64()); let start = Instant::now(); let ans2 = problem2(get_input()); let duration = start.elapsed(); println!("Problem 2 solution: {} [{}s]", ans2, duration.as_secs_f64()); } // DATA const INPUT_RANGE: Range = 1..4001; fn empty_counters() -> HashMap>> { HashMap::from([ ('x', vec![INPUT_RANGE]), ('m', vec![INPUT_RANGE]), ('a', vec![INPUT_RANGE]), ('s', vec![INPUT_RANGE]), ]) } #[derive(Debug)] struct RulePredicate { op: PredicateOperator, var: char, } #[derive(Debug, Clone)] enum RuleAction { Terminate(bool), Jump(String), } impl From<&str> for RuleAction { fn from(s: &str) -> Self { match s { "A" => Self::Terminate(true), "R" => Self::Terminate(false), s => Self::Jump(s.into()), } } } #[derive(Debug)] struct Rule { pred: RulePredicate, action: RuleAction, } #[derive(Debug)] enum PredicateOperator { Always, Lt(u64), Gt(u64), } fn range_overlap(r1: &Range, r2: &Range) -> Option> { let new_start = std::cmp::max(r1.start, r2.start); let new_end = std::cmp::min(r1.end - T::one(), r2.end - T::one()); if new_start <= std::cmp::min(r1.end - T::one(), r2.end - T::one()) && new_end >= std::cmp::max(r1.start, r2.start) { Some(new_start..new_end + T::one()) } else { None } } fn range_exclude(keep: &Range, exclude: &Range) -> Vec> { let mut residual = Vec::new(); if let Some(overlap) = range_overlap(keep, exclude) { if keep.start < overlap.start { residual.push(keep.start..overlap.start); } if keep.end > overlap.end { residual.push(overlap.end..keep.end); } } else { residual.push(keep.clone()); } residual } impl From<&str> for PredicateOperator { fn from(s: &str) -> Self { let (op_s, val_s) = s.split_at(1); match op_s { "<" => PredicateOperator::Lt(val_s.parse().unwrap()), ">" => PredicateOperator::Gt(val_s.parse().unwrap()), s => panic!("unknown operator {}", s), } } } impl From<&str> for RulePredicate { fn from(s: &str) -> Self { let (var_s, pred_s) = s.split_at(1); Self { op: pred_s.into(), var: var_s.chars().next().unwrap(), } } } impl RulePredicate { fn check(&self, part: &Part) -> bool { match self.op { PredicateOperator::Always => true, PredicateOperator::Gt(val) => part.0[&self.var] > val, PredicateOperator::Lt(val) => part.0[&self.var] < val, } } fn matching_range(&self) -> Range { let res = match self.op { PredicateOperator::Always => INPUT_RANGE, PredicateOperator::Gt(val) => val + 1..INPUT_RANGE.end, PredicateOperator::Lt(val) => INPUT_RANGE.start..val, }; println!(" matching range for predicate {:?}: {:?}", self, res); res } } impl From<&str> for Rule { fn from(s: &str) -> Self { if let Some((predicate_s, action_s)) = s.split_once(':') { Self { pred: predicate_s.into(), action: action_s.into(), } } else { Self { pred: RulePredicate { op: PredicateOperator::Always, var: '\0', }, action: s.into(), } } } } fn count_states(ranges: HashMap>>) -> u64 { ['x', 'm', 'a', 's'].iter().map(|c| ranges[c].iter().map(|r| r.end - r.start).sum::()).product() } impl Rule { // Returns (matching_ranges, unmatching_ranges for next rule) fn possible_ranges( &self, wfs: &Workflows, ranges: HashMap>>, ) -> (u64, HashMap>>) { return match &self.action { RuleAction::Terminate(true) => { if let PredicateOperator::Always = self.pred.op { // Always predicate is terminating and returns empty ranges (count_states(ranges), empty_counters()) } else { // other predicates will pop up the stack and return unmatched ranges let (mut matching, mut unmatching) = (ranges.clone(), ranges.clone()); if let Some(relevant_ranges) = ranges.get(&(self.pred.var)){ println!(" relevant: {:?}", relevant_ranges); matching.insert( self.pred.var, relevant_ranges .iter() .filter_map(|range| range_overlap(range, &self.pred.matching_range())) .collect(), ); unmatching.insert( self.pred.var, relevant_ranges.iter().flat_map(|range| range_exclude(range, &self.pred.matching_range())).collect()); println!(" matching: {:?}", matching); (count_states(matching), unmatching) } else { // relevant_ranges is empty so this is a failed state with no possibilities for one of the values // probably we should never get here (0, empty_counters()) } } } RuleAction::Terminate(false) => { if let PredicateOperator::Always = self.pred.op { // Always predicate is terminating, with false returns 0 count and empty ranges (0, empty_counters()) } else { let (mut matching, mut unmatching) = (ranges.clone(), ranges.clone()); if let Some(relevant_ranges) = ranges.get(&(self.pred.var)){ matching.insert( self.pred.var, relevant_ranges .iter() .filter_map(|range| range_overlap(range, &self.pred.matching_range())) .collect(), ); unmatching.insert( self.pred.var, relevant_ranges.iter().flat_map(|range| range_exclude(range, &self.pred.matching_range())).collect()); (0, unmatching) } else { // relevant_ranges is empty so this is a failed state with no possibilities for one of the values // probably we should never get here (0, empty_counters()) } } } RuleAction::Jump(wf) => { if let PredicateOperator::Always = self.pred.op { // always predicate before a jump will always jump, so has no unmatching ranges (wfs.0[wf].possible_ranges(wfs, ranges), empty_counters()) } else { let (mut matching, mut unmatching) = (ranges.clone(), ranges.clone()); if let Some(relevant_ranges) = ranges.get(&(self.pred.var)){ matching.insert( self.pred.var, relevant_ranges .iter() .filter_map(|range| range_overlap(range, &self.pred.matching_range())) .collect(), ); unmatching.insert( self.pred.var, relevant_ranges.iter().flat_map(|range| range_exclude(range, &self.pred.matching_range())).collect()); (wfs.0[wf].possible_ranges(wfs, matching), unmatching) } else { // no relevant ranges = no possible continuations (0, empty_counters()) } } } }; } } #[derive(Debug)] struct Workflow { name: String, rules: Vec, } impl From<&str> for Workflow { fn from(s: &str) -> Self { let (name_s, rest_s) = s.split_once('{').unwrap(); let rules = rest_s.split_once('}').unwrap().0.split(',').map(|r| r.into()).collect(); Self { name: name_s.into(), rules, } } } impl Workflow { fn execute(&self, part: &Part) -> RuleAction { for r in &self.rules { if r.pred.check(part) { return r.action.clone(); } } panic!("unhandled part {:?}", part); } fn possible_ranges( &self, wfs: &Workflows, mut ranges: HashMap>>, ) -> u64 { let mut accum = 0u64; println!("Entering {} with ranges {:?}", self.name, ranges); for r in &self.rules { println!(" evaluating rule: {:?} with {:?}", r, ranges); let (count, next_ranges) = r.possible_ranges(wfs, ranges); ranges = next_ranges; println!(" result of {:?}: count<{}> remaining<{:?}>", r, count, ranges); accum += count } println!("Count of {}: {}", self.name, accum); accum } } impl Workflows { fn execute(&self, part: &Part) -> bool { let mut action = RuleAction::Jump("in".into()); loop { match &action { RuleAction::Terminate(b) => return *b, RuleAction::Jump(j) => { let next_workflow = &self.0[j]; action = next_workflow.execute(part) } } } } fn count_possible_states(&self) -> u64 { let ranges = HashMap::from([ ('x', vec![INPUT_RANGE]), ('m', vec![INPUT_RANGE]), ('a', vec![INPUT_RANGE]), ('s', vec![INPUT_RANGE]), ]); let possible_ranges = self.0["in".into()].possible_ranges(self, ranges); println!("possible_ranges: {:?}", possible_ranges); possible_ranges } } #[derive(Debug)] struct Workflows(HashMap); #[derive(Debug)] struct Part(HashMap); impl From<&str> for Part { fn from(s: &str) -> Self { let (_, vars_s) = s.split_once('{').unwrap(); let vars = vars_s.split_once('}').unwrap().0.split(','); let mut part = HashMap::new(); for var in vars { let (name, val) = var.split_once('=').unwrap(); part.insert(name.chars().next().unwrap(), val.parse().unwrap()); } Self(part) } } type Parts = Vec; // PROBLEM 1 solution fn problem1(mut input: Lines) -> u64 { let mut wfs = Workflows(HashMap::new()); let mut parts: Parts = Vec::new(); while let Some(Ok(line)) = input.next() { if line != "" { let wf: Workflow = line.as_str().into(); wfs.0.insert(wf.name.clone(), wf); } else { break; } } while let Some(Ok(line)) = input.next() { parts.push(line.as_str().into()); } parts .iter() .filter(|part| wfs.execute(part)) .map(|part| part.0.values().sum::()) .sum::() } // PROBLEM 2 solution fn problem2(mut input: Lines) -> u64 { let mut wfs = Workflows(HashMap::new()); let mut parts: Parts = Vec::new(); while let Some(Ok(line)) = input.next() { if line != "" { let wf: Workflow = line.as_str().into(); wfs.0.insert(wf.name.clone(), wf); } else { break; } } while let Some(Ok(line)) = input.next() { parts.push(line.as_str().into()); } wfs.count_possible_states() } #[cfg(test)] mod tests { use crate::*; use std::io::Cursor; const EXAMPLE: &str = &"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}"; #[test] fn problem1_example() { let c = Cursor::new(EXAMPLE); assert_eq!(problem1(c.lines()), 19114); } #[test] fn problem2_example() { let c = Cursor::new(EXAMPLE); assert_eq!(problem2(c.lines()), 167409079868000); } }