day19: problem 2 solution
This commit is contained in:
		
							
								
								
									
										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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user