day19: problem 2 solution
This commit is contained in:
parent
a5dea64b32
commit
2b921b5fb2
85
19/Cargo.lock
generated
85
19/Cargo.lock
generated
@ -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",
|
||||
]
|
||||
|
@ -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"
|
||||
|
203
19/src/main.rs
203
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<u64> = 1..4001;
|
||||
|
||||
fn empty_counters() -> HashMap<char, Vec<Range<u64>>> {
|
||||
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<T: num::Num + Ord + Copy>(r1: &Range<T>, r2: &Range<T>) -> Option<Range<T>> {
|
||||
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<T: num::Num + Ord + Copy>(keep: &Range<T>, exclude: &Range<T>) -> Vec<Range<T>> {
|
||||
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<u64> {
|
||||
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<char, Vec<Range<u64>>>) -> u64 {
|
||||
['x', 'm', 'a', 's'].iter().map(|c| ranges[c].iter().map(|r| r.end - r.start).sum::<u64>()).product()
|
||||
}
|
||||
|
||||
impl Rule {
|
||||
// Returns (matching_ranges, unmatching_ranges for next rule)
|
||||
fn possible_ranges(
|
||||
&self,
|
||||
wfs: &Workflows,
|
||||
ranges: HashMap<char, Vec<Range<u64>>>,
|
||||
) -> (u64, HashMap<char, Vec<Range<u64>>>) {
|
||||
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<char, Vec<Range<u64>>>,
|
||||
) -> 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<T: BufRead>(mut input: Lines<T>) -> 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<T: BufRead>(mut input: Lines<T>) -> u64 {
|
||||
parts.push(line.as_str().into());
|
||||
}
|
||||
|
||||
parts.iter().filter(|part| wfs.execute(part)).map(|part| part.0.values().sum::<u64>()).sum::<u64>()
|
||||
parts
|
||||
.iter()
|
||||
.filter(|part| wfs.execute(part))
|
||||
.map(|part| part.0.values().sum::<u64>())
|
||||
.sum::<u64>()
|
||||
}
|
||||
|
||||
// PROBLEM 2 solution
|
||||
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
|
||||
0
|
||||
fn problem2<T: BufRead>(mut input: Lines<T>) -> 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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user