From 2070a6b72620666f0baf41704c6f2f33fc386b5f Mon Sep 17 00:00:00 2001 From: Bert Peters Date: Tue, 19 Dec 2023 19:16:02 +0100 Subject: [PATCH] Implement 2023 day 19 part 1 --- 2023/src/day19.rs | 198 +++++++++++++++++++++++++++++++++++++++- 2023/src/samples/19.txt | 17 ++++ 2 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 2023/src/samples/19.txt diff --git a/2023/src/day19.rs b/2023/src/day19.rs index 7c1760f..414ba9d 100644 --- a/2023/src/day19.rs +++ b/2023/src/day19.rs @@ -1,7 +1,201 @@ -pub fn part1(_input: &[u8]) -> anyhow::Result { - anyhow::bail!("Not implemented") +use std::ops::Index; + +use nom::branch::alt; +use nom::bytes::complete::tag; +use nom::bytes::complete::take; +use nom::bytes::complete::take_until1; +use nom::bytes::complete::take_while1; +use nom::combinator::map; +use nom::multi::fold_many1; +use nom::multi::many1; +use nom::sequence::delimited; +use nom::sequence::preceded; +use nom::sequence::separated_pair; +use nom::sequence::terminated; +use nom::sequence::tuple; +use nom::IResult; + +use crate::common::parse_input; + +const RULES_LEN: usize = 26 * 26 * 26; + +#[derive(Clone, Copy)] +struct Item([u16; 4]); + +impl Item { + fn rating_sum(self) -> u32 { + self.0.into_iter().map(u32::from).sum() + } + + fn passes(&self, rules: &[Rule]) -> bool { + let mut pos = convert_name(b"in"); + + 'outer: loop { + let rule = &rules[pos as usize]; + + for &(condition, end) in &rule.checks { + let is_valid = match condition { + Condition::Greater(c, val) => self[c] > val, + Condition::Less(c, val) => self[c] < val, + }; + + if is_valid { + match end { + RuleEnd::Reject => return false, + RuleEnd::Accept => return true, + RuleEnd::Next(new_pos) => { + pos = new_pos; + continue 'outer; + } + } + } + } + + match rule.end { + RuleEnd::Reject => return false, + RuleEnd::Accept => return true, + RuleEnd::Next(new_pos) => { + pos = new_pos; + continue 'outer; + } + } + } + } +} + +impl Index for Item { + type Output = u16; + + fn index(&self, index: u8) -> &Self::Output { + static FALLBACK: u16 = 0; + + match index { + b'x' => &self.0[0], + b'm' => &self.0[1], + b'a' => &self.0[2], + b's' => &self.0[3], + other => { + debug_assert!(false, "Invalid index: {}", other as char); + &FALLBACK + } + } + } +} + +fn parse_item(i: &[u8]) -> IResult<&[u8], Item> { + use nom::character::complete::u16; + map( + tuple(( + preceded(tag("{x="), u16), + preceded(tag(",m="), u16), + preceded(tag(",a="), u16), + delimited(tag(",s="), u16, tag("}\n")), + )), + |(x, m, a, s)| Item([x, m, a, s]), + )(i) +} + +#[derive(Default, Copy, Clone)] +enum RuleEnd { + #[default] + Reject, + Accept, + Next(u16), +} + +#[derive(Clone, Copy)] +enum Condition { + Greater(u8, u16), + Less(u8, u16), +} + +#[derive(Default, Clone)] +struct Rule { + checks: Vec<(Condition, RuleEnd)>, + end: RuleEnd, +} + +// Base 26 at it again, but now in lowercase +fn convert_name(name: &[u8]) -> u16 { + name.iter() + .fold(0, |cur, &c| cur * 26 + u16::from(c - b'a')) +} + +fn parse_rule(i: &[u8]) -> IResult<&[u8], (u16, Rule)> { + fn convert_end(i: &[u8]) -> RuleEnd { + match i { + b"A" => RuleEnd::Accept, + b"R" => RuleEnd::Reject, + other => RuleEnd::Next(convert_name(other)), + } + } + + let (i, name) = map(take_while1(|c: u8| c.is_ascii_alphabetic()), convert_name)(i)?; + let (i, _) = tag("{")(i)?; + let (i, checks) = many1(terminated( + map( + tuple(( + take::<_, &[u8], _>(1usize), + alt((tag("<"), tag(">"))), + nom::character::complete::u16, + preceded(tag(":"), take_until1(",")), + )), + |(index, cmp, val, dest)| { + let condition = if cmp[0] == b'<' { + Condition::Less(index[0], val) + } else { + debug_assert_eq!(cmp[0], b'>'); + Condition::Greater(index[0], val) + }; + + (condition, convert_end(dest)) + }, + ), + tag(","), + ))(i)?; + + let (i, end) = map(take_until1("}\n"), convert_end)(i)?; + Ok((&i[2..], (name, Rule { checks, end }))) +} + +fn parse_text(i: &[u8]) -> IResult<&[u8], (Box<[Rule]>, Vec)> { + separated_pair( + fold_many1( + parse_rule, + || vec![Rule::default(); RULES_LEN].into_boxed_slice(), + |mut rules, (index, rule)| { + rules[index as usize] = rule; + rules + }, + ), + tag("\n"), + many1(parse_item), + )(i) +} + +pub fn part1(input: &[u8]) -> anyhow::Result { + let (rules, items) = parse_input(input, parse_text)?; + + let passing = items + .iter() + .filter_map(|c| c.passes(&rules).then(|| c.rating_sum())) + .sum::(); + + Ok(passing.to_string()) } pub fn part2(_input: &[u8]) -> anyhow::Result { anyhow::bail!("Not implemented") } + +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE: &[u8] = include_bytes!("samples/19.txt"); + + #[test] + fn sample_part1() { + assert_eq!("19114", part1(SAMPLE).unwrap()); + } +} diff --git a/2023/src/samples/19.txt b/2023/src/samples/19.txt new file mode 100644 index 0000000..e5b5d64 --- /dev/null +++ b/2023/src/samples/19.txt @@ -0,0 +1,17 @@ +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}