day19 solution
parent
86f39513b0
commit
05861a06b0
|
@ -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<String, Vec<Rule>>;
|
||||||
|
|
||||||
|
fn parse(input: &str) -> (Rules, Vec<Part>) {
|
||||||
|
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::<Vec<_>>();
|
||||||
|
rules.insert(rname, new_rules);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut parts: Vec<Part> = 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,5 +18,6 @@ mod day15;
|
||||||
mod day16;
|
mod day16;
|
||||||
mod day17;
|
mod day17;
|
||||||
mod day18;
|
mod day18;
|
||||||
|
mod day19;
|
||||||
|
|
||||||
aoc_lib! { year = 2023 }
|
aoc_lib! { year = 2023 }
|
||||||
|
|
Loading…
Reference in New Issue