From 2b921b5fb28c9dc03a939cd352d0bc6020394f2e Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Tue, 19 Dec 2023 01:01:36 -0800 Subject: [PATCH] day19: problem 2 solution --- 19/Cargo.lock | 85 +++++++++++++++++++++ 19/Cargo.toml | 1 + 19/src/main.rs | 203 ++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 280 insertions(+), 9 deletions(-) diff --git a/19/Cargo.lock b/19/Cargo.lock index a892a08..322ea61 100644 --- a/19/Cargo.lock +++ b/19/Cargo.lock @@ -2,6 +2,91 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "day19" version = "0.1.0" +dependencies = [ + "num", +] + +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] diff --git a/19/Cargo.toml b/19/Cargo.toml index 58edd67..6fadeba 100644 --- a/19/Cargo.toml +++ b/19/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +num = "0.4.1" diff --git a/19/src/main.rs b/19/src/main.rs index 3d85245..03a0bbf 100644 --- a/19/src/main.rs +++ b/19/src/main.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::fs::File; use std::io::{BufRead, BufReader, Lines}; +use std::ops::Range; use std::time::Instant; // BOILERPLATE @@ -26,6 +27,17 @@ fn main() { // 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, @@ -61,6 +73,33 @@ enum PredicateOperator { 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); @@ -90,11 +129,19 @@ impl RulePredicate { 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 { - println!("parsing {}", s); if let Some((predicate_s, action_s)) = s.split_once(':') { Self { pred: predicate_s.into(), @@ -112,6 +159,99 @@ impl From<&str> for Rule { } } +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, @@ -121,7 +261,6 @@ struct Workflow { impl From<&str> for Workflow { fn from(s: &str) -> Self { let (name_s, rest_s) = s.split_once('{').unwrap(); - println!("name: {} rest: {}", name_s, rest_s); let rules = rest_s.split_once('}').unwrap().0.split(',').map(|r| r.into()).collect(); Self { name: name_s.into(), @@ -133,11 +272,28 @@ impl Workflow { fn execute(&self, part: &Part) -> RuleAction { for r in &self.rules { if r.pred.check(part) { - return r.action.clone() + 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 { @@ -153,6 +309,19 @@ impl Workflows { } } } + + 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)] @@ -182,7 +351,6 @@ 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() { - println!("line: {}", line); if line != "" { let wf: Workflow = line.as_str().into(); wfs.0.insert(wf.name.clone(), wf); @@ -194,19 +362,36 @@ fn problem1(mut input: Lines) -> u64 { parts.push(line.as_str().into()); } - parts.iter().filter(|part| wfs.execute(part)).map(|part| part.0.values().sum::()).sum::() + parts + .iter() + .filter(|part| wfs.execute(part)) + .map(|part| part.0.values().sum::()) + .sum::() } // PROBLEM 2 solution -fn problem2(input: Lines) -> u64 { - 0 +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} @@ -234,6 +419,6 @@ hdj{m>838:A,pv} #[test] fn problem2_example() { let c = Cursor::new(EXAMPLE); - assert_eq!(problem2(c.lines()), 0); + assert_eq!(problem2(c.lines()), 167409079868000); } }