AoC23/src/day19.rs

444 lines
11 KiB
Rust

use std::{
collections::{HashMap, VecDeque},
ops::Range,
};
use itertools::Itertools;
use regex::Regex;
pub fn run() {
let input = include_str!("../input/day19.txt");
// let input = 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}
// "#;
// let input = r#"
// in{a>2000:A,R}
// {x=0,m=0,a=0,s=0}
// "#;
let (workflows, parts) = parse(input);
let accepted_sum: usize = parts
.iter()
.filter(|p| run_part(&workflows, p) == WorkflowResult::Accept)
.map(|p| p.sum() as usize)
.sum();
dbg!(accepted_sum);
dbg!(run_ranges(&workflows, PartRange::full()));
}
fn parse(s: &str) -> (HashMap<&str, Workflow<'_>>, Vec<Part>) {
let (workflows, parts) = s.trim().split_once("\n\n").unwrap();
let mut workflows_map = HashMap::new();
for l in workflows.lines() {
let l = l.trim();
let brace = l.find("{").unwrap();
let name = l.get(..brace).unwrap();
let workflow = l.get((brace + 1)..(l.len() - 1)).unwrap().into();
workflows_map.insert(name, workflow);
}
(
workflows_map,
parts.lines().map(|l| l.trim().into()).collect(),
)
}
fn run_part<'a>(workflows: &'a HashMap<&'a str, Workflow<'a>>, part: &Part) -> WorkflowResult<'a> {
let mut cur = "in";
loop {
let workflow = &workflows[cur];
let result = workflow.run(part);
match result {
WorkflowResult::Workflow(s) => cur = s,
_ => return result,
}
}
}
fn run_ranges<'a>(workflows: &'a HashMap<&'a str, Workflow<'a>>, range: PartRange) -> usize {
let mut ranges_to_process = VecDeque::from([(range, "in")]);
let mut accepted = vec![];
while let Some((r, id)) = ranges_to_process.pop_front() {
let results = workflows[id].partition_range(r);
for (range, result) in results {
match result {
WorkflowResult::Accept => accepted.push(range),
WorkflowResult::Reject => (),
WorkflowResult::Workflow(id) => ranges_to_process.push_back((range, id)),
}
}
}
accepted.into_iter().map(|r| r.product()).sum()
}
#[derive(Debug, Clone)]
struct Workflow<'a> {
rules: Vec<Rule<'a>>,
otherwise: WorkflowResult<'a>,
}
impl<'a> From<&'a str> for Workflow<'a> {
fn from(value: &'a str) -> Self {
let mut rules = vec![];
let mut otherwise = None;
let mut parts = value.split(",").peekable();
while let Some(s) = parts.next() {
if parts.peek().is_none() {
otherwise = Some(WorkflowResult::from(s));
} else {
rules.push(Rule::from(s));
}
}
Self {
rules,
otherwise: otherwise.unwrap(),
}
}
}
#[derive(Debug, Clone)]
struct Rule<'a> {
prop: Prop,
op: Op,
rhs: u32,
result: WorkflowResult<'a>,
}
impl<'a> From<&'a str> for Rule<'a> {
fn from(value: &'a str) -> Self {
let re = Regex::new("^([xmas])([<>])(\\d+):(\\w+)$").unwrap();
let captures = re.captures(value).unwrap();
Self {
prop: captures.get(1).unwrap().as_str().into(),
op: captures.get(2).unwrap().as_str().into(),
rhs: captures.get(3).unwrap().as_str().parse().unwrap(),
result: captures.get(4).unwrap().as_str().into(),
}
}
}
#[derive(Debug, Clone, Copy)]
enum Prop {
X,
M,
A,
S,
}
impl From<&str> for Prop {
fn from(value: &str) -> Self {
match value {
"x" => Self::X,
"m" => Self::M,
"a" => Self::A,
"s" => Self::S,
_ => panic!("invalid prop {}", value),
}
}
}
#[derive(Debug, Clone, Copy)]
enum Op {
LessThan,
GreaterThan,
}
impl From<&str> for Op {
fn from(value: &str) -> Self {
match value {
"<" => Op::LessThan,
">" => Op::GreaterThan,
_ => panic!("invalid op {}", value),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum WorkflowResult<'a> {
Accept,
Reject,
Workflow(&'a str),
}
impl<'a> From<&'a str> for WorkflowResult<'a> {
fn from(value: &'a str) -> Self {
match value {
"A" => Self::Accept,
"R" => Self::Reject,
_ => Self::Workflow(value),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
struct Part {
x: u32,
m: u32,
a: u32,
s: u32,
}
impl From<&str> for Part {
fn from(value: &str) -> Self {
let values = value
.strip_prefix("{")
.unwrap()
.strip_suffix("}")
.unwrap()
.split(",");
let mut part: Part = Default::default();
for s in values {
let field = match s.get(0..1) {
Some("x") => &mut part.x,
Some("m") => &mut part.m,
Some("a") => &mut part.a,
Some("s") => &mut part.s,
s => panic!("invalid field {:?}", s),
};
*field = s.get(2..).unwrap().parse().unwrap();
}
part
}
}
impl Part {
fn sum(&self) -> u32 {
self.x + self.m + self.a + self.s
}
fn get(&self, prop: Prop) -> u32 {
match prop {
Prop::X => self.x,
Prop::M => self.m,
Prop::A => self.a,
Prop::S => self.s,
}
}
}
impl<'a> Rule<'a> {
fn test(&self, part: &Part) -> bool {
let lhs = part.get(self.prop);
match self.op {
Op::LessThan => lhs < self.rhs,
Op::GreaterThan => lhs > self.rhs,
}
}
}
impl<'a> Workflow<'a> {
fn run(&self, part: &Part) -> WorkflowResult<'_> {
for rule in self.rules.iter() {
if rule.test(part) {
return rule.result;
}
}
self.otherwise
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct PartRange {
x: Range<u32>,
m: Range<u32>,
a: Range<u32>,
s: Range<u32>,
}
impl PartRange {
fn full() -> Self {
Self {
x: 1..4001,
m: 1..4001,
a: 1..4001,
s: 1..4001,
}
}
fn prop(&self, prop: Prop) -> &Range<u32> {
match prop {
Prop::X => &self.x,
Prop::M => &self.m,
Prop::A => &self.a,
Prop::S => &self.s,
}
}
fn with(&self, prop: Prop, range: Range<u32>) -> Self {
let mut copy = self.clone();
match prop {
Prop::X => copy.x = range,
Prop::M => copy.m = range,
Prop::A => copy.a = range,
Prop::S => copy.s = range,
}
copy
}
fn product(&self) -> usize {
self.x.len() * self.m.len() * self.a.len() * self.s.len()
}
}
impl<'a> Rule<'a> {
fn partition_range(&self, range: PartRange) -> Partition {
let prop_range = range.prop(self.prop);
match self.op {
Op::LessThan => {
if self.rhs > prop_range.end {
Partition::matched(range)
} else if self.rhs < prop_range.start {
Partition::failed(range)
} else {
let matched = range.with(self.prop, prop_range.start..self.rhs);
let failed = range.with(self.prop, self.rhs..prop_range.end);
Partition::both(matched, failed)
}
}
Op::GreaterThan => {
if self.rhs < prop_range.start {
Partition::matched(range)
} else if self.rhs > prop_range.end {
Partition::failed(range)
} else {
let matched = range.with(self.prop, (self.rhs + 1)..prop_range.end);
let failed = range.with(self.prop, prop_range.start..(self.rhs + 1));
Partition::both(matched, failed)
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Partition {
matched: Option<PartRange>,
failed: Option<PartRange>,
}
impl Partition {
fn both(matched: PartRange, failed: PartRange) -> Self {
Self {
matched: Some(matched),
failed: Some(failed),
}
}
fn matched(matched: PartRange) -> Self {
Self {
matched: Some(matched),
failed: None,
}
}
fn failed(failed: PartRange) -> Self {
Self {
matched: None,
failed: Some(failed),
}
}
}
impl<'a> Workflow<'a> {
fn partition_range(&self, range: PartRange) -> Vec<(PartRange, WorkflowResult)> {
let mut results = vec![];
let mut cur = Some(range);
for rule in self.rules.iter() {
let partitioned = rule.partition_range(cur.unwrap());
if let Some(matched) = partitioned.matched {
results.push((matched, rule.result));
}
if let Some(failed) = partitioned.failed {
cur = Some(failed);
} else {
cur = None;
break;
}
}
if let Some(r) = cur {
results.push((r, self.otherwise));
}
results
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rule_partition() {
let rule = Rule {
prop: Prop::X,
op: Op::LessThan,
rhs: 2000,
result: WorkflowResult::Accept,
};
let range = PartRange::full();
assert_eq!(
rule.partition_range(range.clone()),
Partition::both(
range.with(Prop::X, 1..2000),
range.with(Prop::X, 2000..4001)
)
);
}
#[test]
fn test_workflow_partition() {
let workflow: Workflow = "a<2006:qkq,m>2090:A,rfg".into();
let results = workflow.partition_range(PartRange::full());
assert_eq!(
results,
vec![
(
PartRange {
x: 1..4001,
m: 1..4001,
a: 1..2006,
s: 1..4001,
},
WorkflowResult::Workflow("qkq")
),
(
PartRange {
x: 1..4001,
m: 2090..4001,
a: 2006..4001,
s: 1..4001,
},
WorkflowResult::Accept
),
(
PartRange {
x: 1..4001,
m: 1..2090,
a: 2006..4001,
s: 1..4001,
},
WorkflowResult::Workflow("rfg")
)
]
);
}
}