diff --git a/2023/src/day19.rs b/2023/src/day19.rs new file mode 100644 index 0000000..0c01b2e --- /dev/null +++ b/2023/src/day19.rs @@ -0,0 +1,251 @@ +use aoc_parse::{parser, prelude::*}; +use aoc_runner_derive::aoc; +use std::collections::HashMap; + +struct Part { + x: u64, + m: u64, + a: u64, + s: u64, +} + +impl Part { + fn sum(&self) -> u64 { + self.x + self.m + self.a + self.s + } + + fn val_for(&self, field: &str) -> u64 { + match field { + "x" => self.x, + "m" => self.m, + "a" => self.a, + "s" => self.s, + n @ _ => panic!("bad val_for field {}", n), + } + } +} + +#[derive(Eq, PartialEq, Copy, Clone)] +enum RuleOp { + NextState, + LessThan, + GreaterThan, +} + +struct Rule { + op: RuleOp, + field: String, + count: u64, + dest: String, +} + +impl Rule { + fn next_state(&self) -> bool { + self.op == RuleOp::NextState + } + + fn match_lt(&self, part: &Part) -> bool { + self.op == RuleOp::LessThan && part.val_for(&self.field) < self.count + } + + fn match_gt(&self, part: &Part) -> bool { + self.op == RuleOp::GreaterThan && part.val_for(&self.field) > self.count + } +} + +type Rules = HashMap>; + +fn parse(input: &str) -> (Rules, Vec) { + let mut rules: Rules = HashMap::new(); + let rparse = parser!(string(lower+) "{" string(any_char+) "}"); + let ltparse = parser!(string(lower) "<" u64 ":" string(alpha+)); + let gtparse = parser!(string(lower) ">" u64 ":" string(alpha+)); + for line in input.lines() { + if line.is_empty() { + break; + } + let (rname, raw_rules) = rparse.parse(line).unwrap(); + let new_rules = raw_rules + .split(",") + .map(|t| { + if t.contains("<") { + let (f, c, d) = ltparse.parse(t).unwrap(); + Rule { + op: RuleOp::LessThan, + field: f, + count: c, + dest: d, + } + } else if t.contains(">") { + let (f, c, d) = gtparse.parse(t).unwrap(); + Rule { + op: RuleOp::GreaterThan, + field: f, + count: c, + dest: d, + } + } else { + Rule { + op: RuleOp::NextState, + field: String::from("f"), + count: 0, + dest: String::from(t), + } + } + }) + .collect::>(); + rules.insert(rname, new_rules); + } + + let mut parts: Vec = vec![]; + let pparse = parser!("{x=" u64 ",m=" u64 ",a=" u64 ",s=" u64 "}"); + let mut skipped = false; + for line in input.lines() { + if !skipped { + if line.is_empty() { + skipped = true; + } + continue; + } + let (x, m, a, s) = pparse.parse(line).unwrap(); + parts.push(Part { x, m, a, s }); + } + + (rules, parts) +} + +fn filter_part(rules: &Rules, part: &Part) -> u64 { + let mut b = rules.get("in").unwrap(); + loop { + let mut found = false; + for r in b.iter() { + if r.match_lt(part) || r.match_gt(part) || r.next_state() { + match r.dest.as_str() { + "A" => return part.sum(), + "R" => return 0, + _ => { + found = true; + b = rules.get(&r.dest).unwrap(); + break; + } + } + } + } + if !found { + break; + } + } + 0 +} + +#[aoc(day19, part1)] +fn part1(input: &str) -> u64 { + let (rules, parts) = parse(input); + parts.iter().map(|p| filter_part(&rules, &p)).sum() +} + +fn combinations(weights: [(u64, u64); 4]) -> u64 { + (0..4) + .into_iter() + .map(|i| weights[i].0.abs_diff(weights[i].1)) + .product() +} + +fn windex(field: &str) -> usize { + match field { + "x" => 0, + "m" => 1, + "a" => 2, + "s" => 3, + _ => panic!("bad field"), + } +} + +fn split_lt(count: u64, input: (u64, u64)) -> ((u64, u64), (u64, u64)) { + let one = input.0..input.1.min(count); + let two = count..input.1; + + ((one.start, one.end), (two.start, two.end)) +} + +fn split_gt(count: u64, input: (u64, u64)) -> ((u64, u64), (u64, u64)) { + let one = (count + 1)..input.1; + let two = input.0..(count + 1); + + ((one.start, one.end), (two.start, two.end)) +} + +fn traverse_rules(rules: &Rules, cur: &str, mut weights: [(u64, u64); 4]) -> u64 { + let mut c = 0; + for r in rules.get(cur).unwrap().iter() { + match r.op { + RuleOp::NextState => { + c += match r.dest.as_str() { + "A" => combinations(weights), + "R" => 0, + _ => traverse_rules(rules, &r.dest, weights), + } + } + RuleOp::LessThan => { + let i = windex(&r.field); + let mut next_weights = weights.clone(); + let (next_range, cur_range) = split_lt(r.count, weights[i]); + weights[i] = cur_range; + next_weights[i] = next_range; + c += match r.dest.as_str() { + "A" => combinations(next_weights), + "R" => 0, + _ => traverse_rules(rules, &r.dest, next_weights), + } + } + RuleOp::GreaterThan => { + let i = windex(&r.field); + let mut next_weights = weights.clone(); + let (next_range, cur_range) = split_gt(r.count, weights[i]); + weights[i] = cur_range; + next_weights[i] = next_range; + c += match r.dest.as_str() { + "A" => combinations(next_weights), + "R" => 0, + _ => traverse_rules(rules, &r.dest, next_weights), + } + } + } + } + c +} + +#[aoc(day19, part2)] +fn part2(input: &str) -> u64 { + let (rules, _parts) = parse(input); + traverse_rules(&rules, "in", [(1, 4001); 4]) +} + +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE_DATA: &'static str = r#"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 sample_data() { + assert_eq!(part1(&SAMPLE_DATA), 19114); + assert_eq!(part2(&SAMPLE_DATA), 167409079868000); + } +} diff --git a/2023/src/lib.rs b/2023/src/lib.rs index dc153f3..596c2be 100644 --- a/2023/src/lib.rs +++ b/2023/src/lib.rs @@ -18,5 +18,6 @@ mod day15; mod day16; mod day17; mod day18; +mod day19; aoc_lib! { year = 2023 }