From cdfecf821ce57490df05088b30ff566d694ea0e0 Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Tue, 19 Dec 2023 02:01:47 -0800 Subject: [PATCH] day19: refactoring and cleanup --- 19/Cargo.lock | 67 --------------- 19/Cargo.toml | 4 +- 19/src/main.rs | 220 +++++++++++++++++++++---------------------------- 3 files changed, 95 insertions(+), 196 deletions(-) diff --git a/19/Cargo.lock b/19/Cargo.lock index 322ea61..798dd61 100644 --- a/19/Cargo.lock +++ b/19/Cargo.lock @@ -12,73 +12,6 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 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", ] diff --git a/19/Cargo.toml b/19/Cargo.toml index 6fadeba..4b391e6 100644 --- a/19/Cargo.toml +++ b/19/Cargo.toml @@ -3,7 +3,5 @@ name = "day19" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -num = "0.4.1" +num-traits = "0.2.17" diff --git a/19/src/main.rs b/19/src/main.rs index 03a0bbf..dd416cd 100644 --- a/19/src/main.rs +++ b/19/src/main.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; use std::fs::File; use std::io::{BufRead, BufReader, Lines}; -use std::ops::Range; +use std::ops::{Range, IndexMut, Index}; use std::time::Instant; +use num_traits::Num; // BOILERPLATE type InputIter = Lines>; @@ -29,15 +30,6 @@ fn main() { 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, @@ -73,7 +65,61 @@ enum PredicateOperator { Gt(u64), } -fn range_overlap(r1: &Range, r2: &Range) -> Option> { +#[derive(Debug, Clone)] +struct XmasRanges { + ranges: [Vec>; 4], +} + +impl XmasRanges { + fn none() -> XmasRanges { + Self { + ranges: [vec![0..0], vec![0..0], vec![0..0], vec![0..0]], + } + } + fn all() -> XmasRanges { + Self { + ranges: [ + vec![INPUT_RANGE], + vec![INPUT_RANGE], + vec![INPUT_RANGE], + vec![INPUT_RANGE], + ], + } + } + fn idx(c: char) -> usize { + match c { + 'x' => 0, + 'm' => 1, + 'a' => 2, + 's' => 3, + c => panic!("`{}` is not a valid xmas char", c), + } + } + fn insert(&mut self, c: char, v: Vec>) { + self.ranges[Self::idx(c)] = v; + } + fn count_states(&self) -> u64 { + self.ranges + .iter() + .map(|ranges| ranges.iter().map(|range| range.end - range.start).sum::()) + .product() + } +} + +impl Index for XmasRanges { + type Output = Vec>; + fn index(&self, index: char) -> &Self::Output { + &self.ranges[Self::idx(index)] + } +} + +impl IndexMut for XmasRanges { + fn index_mut(&mut self, index: char) -> &mut Self::Output { + &mut self.ranges[Self::idx(index)] + } +} + +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()); @@ -85,7 +131,7 @@ fn range_overlap(r1: &Range, r2: &Range) -> Opti } } -fn range_exclude(keep: &Range, exclude: &Range) -> Vec> { +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 { @@ -130,13 +176,11 @@ impl RulePredicate { } } fn matching_range(&self) -> Range { - let res = match self.op { + 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 + } } } @@ -159,96 +203,37 @@ 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()) - } - } - } - }; + fn predicate_result(&self, ranges: XmasRanges) -> (XmasRanges, XmasRanges) { + if let PredicateOperator::Always = self.pred.op { + (ranges, XmasRanges::none()) + } else { + let (mut matching, mut unmatching) = (ranges.clone(), ranges.clone()); + let relevant_ranges = &ranges[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(), + ); + (matching, unmatching) + } + } + fn possible_ranges(&self, wfs: &Workflows, ranges: XmasRanges) -> (u64, XmasRanges) { + let (matching, unmatching) = self.predicate_result(ranges); + match &self.action { + RuleAction::Terminate(true) => (matching.count_states(), unmatching), + RuleAction::Terminate(false) => (0, unmatching), + RuleAction::Jump(wf) => (wfs.0[wf].possible_ranges(wfs, matching), unmatching), + } } } @@ -277,21 +262,13 @@ impl Workflow { } panic!("unhandled part {:?}", part); } - fn possible_ranges( - &self, - wfs: &Workflows, - mut ranges: HashMap>>, - ) -> u64 { + fn possible_ranges(&self, wfs: &Workflows, mut ranges: XmasRanges) -> 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); + let count; + (count, ranges) = r.possible_ranges(wfs, ranges); accum += count } - println!("Count of {}: {}", self.name, accum); accum } } @@ -311,16 +288,7 @@ 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 + self.0["in".into()].possible_ranges(self, XmasRanges::all()) } }