1
0
Fork 0

day19 solution

main
Andrew Coleman 2024-01-10 11:27:06 -05:00
parent 86f39513b0
commit 05861a06b0
2 changed files with 252 additions and 0 deletions

251
2023/src/day19.rs Normal file
View File

@ -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);
}
}

View File

@ -18,5 +18,6 @@ mod day15;
mod day16;
mod day17;
mod day18;
mod day19;
aoc_lib! { year = 2023 }