day(19): add solution
Signed-off-by: Matej Focko <me@mfocko.xyz>
This commit is contained in:
parent
2e889073cd
commit
3c813855e3
2 changed files with 359 additions and 0 deletions
17
samples/day19.txt
Normal file
17
samples/day19.txt
Normal file
|
@ -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}
|
342
src/bin/day19.rs
Normal file
342
src/bin/day19.rs
Normal file
|
@ -0,0 +1,342 @@
|
||||||
|
use std::{cmp, collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
|
use aoc_2023::*;
|
||||||
|
|
||||||
|
type Output1 = i64;
|
||||||
|
type Output2 = Output1;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct Part {
|
||||||
|
x: i64,
|
||||||
|
m: i64,
|
||||||
|
a: i64,
|
||||||
|
s: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Part {
|
||||||
|
type Err = &'static str;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let s = &s[1..s.len() - 1];
|
||||||
|
|
||||||
|
let (x, m, a, s) = s
|
||||||
|
.split(',')
|
||||||
|
.map(|var| var.split_once('=').unwrap().1.parse().unwrap())
|
||||||
|
.collect_tuple()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Ok(Self { x, m, a, s })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Part {
|
||||||
|
fn get(&self, v: char) -> i64 {
|
||||||
|
match v {
|
||||||
|
'x' => self.x,
|
||||||
|
'm' => self.m,
|
||||||
|
'a' => self.a,
|
||||||
|
's' => self.s,
|
||||||
|
_ => unreachable!("unknown rating"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rating(&self) -> i64 {
|
||||||
|
self.x + self.m + self.a + self.s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
enum Action {
|
||||||
|
Accept,
|
||||||
|
Reject,
|
||||||
|
Forward(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Action {
|
||||||
|
type Err = &'static str;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s {
|
||||||
|
"A" => Action::Accept,
|
||||||
|
"R" => Action::Reject,
|
||||||
|
next => Action::Forward(next.to_owned()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Rule {
|
||||||
|
Conditional(char, cmp::Ordering, i64, Action),
|
||||||
|
Passthrough(Action),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Rule {
|
||||||
|
type Err = &'static str;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s.split_once(':') {
|
||||||
|
Some((condition, action)) => {
|
||||||
|
let var = condition.chars().nth(0).unwrap();
|
||||||
|
let sign = match condition.chars().nth(1).unwrap() {
|
||||||
|
'<' => cmp::Ordering::Less,
|
||||||
|
'>' => cmp::Ordering::Greater,
|
||||||
|
_ => unreachable!("not supported comparison"),
|
||||||
|
};
|
||||||
|
let value = condition[2..].parse().unwrap();
|
||||||
|
|
||||||
|
Rule::Conditional(var, sign, value, action.parse().unwrap())
|
||||||
|
}
|
||||||
|
None => Rule::Passthrough(s.parse().unwrap()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Workflow {
|
||||||
|
rules: Vec<Rule>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Workflow {
|
||||||
|
type Err = &'static str;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let rules = s.split(',').map(|r| r.parse().unwrap()).collect_vec();
|
||||||
|
|
||||||
|
Ok(Self { rules })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Workflow {
|
||||||
|
fn evaluate<'a>(&'a self, p: &Part) -> &'a Action {
|
||||||
|
for r in &self.rules {
|
||||||
|
match r {
|
||||||
|
Rule::Conditional(v, sign, value, action) => {
|
||||||
|
if p.get(*v).cmp(value) == *sign {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rule::Passthrough(action) => {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable!("there's always passthrough at the end")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct VarRange {
|
||||||
|
min: i64,
|
||||||
|
max: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for VarRange {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { min: 1, max: 4000 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VarRange {
|
||||||
|
fn len(&self) -> i64 {
|
||||||
|
self.max - self.min + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sat(&self, sign: &cmp::Ordering, value: &i64) -> VarRange {
|
||||||
|
match sign {
|
||||||
|
cmp::Ordering::Less => Self {
|
||||||
|
max: value - 1,
|
||||||
|
..*self
|
||||||
|
},
|
||||||
|
cmp::Ordering::Greater => Self {
|
||||||
|
min: value + 1,
|
||||||
|
..*self
|
||||||
|
},
|
||||||
|
_ => unreachable!("unsupported comparison"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unsat(&self, sign: &cmp::Ordering, value: &i64) -> VarRange {
|
||||||
|
match sign {
|
||||||
|
cmp::Ordering::Less => Self {
|
||||||
|
min: *value,
|
||||||
|
..*self
|
||||||
|
},
|
||||||
|
cmp::Ordering::Greater => Self {
|
||||||
|
max: *value,
|
||||||
|
..*self
|
||||||
|
},
|
||||||
|
_ => unreachable!("unsupported comparison"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
struct PartRange {
|
||||||
|
x: VarRange,
|
||||||
|
m: VarRange,
|
||||||
|
a: VarRange,
|
||||||
|
s: VarRange,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartRange {
|
||||||
|
fn len(&self) -> i64 {
|
||||||
|
self.x.len() * self.m.len() * self.a.len() * self.s.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sat(&self, r: &Rule) -> PartRange {
|
||||||
|
match r {
|
||||||
|
Rule::Conditional(var, sign, value, _) => match var {
|
||||||
|
'x' => Self {
|
||||||
|
x: self.x.sat(sign, value),
|
||||||
|
..*self
|
||||||
|
},
|
||||||
|
'm' => Self {
|
||||||
|
m: self.m.sat(sign, value),
|
||||||
|
..*self
|
||||||
|
},
|
||||||
|
'a' => Self {
|
||||||
|
a: self.a.sat(sign, value),
|
||||||
|
..*self
|
||||||
|
},
|
||||||
|
's' => Self {
|
||||||
|
s: self.s.sat(sign, value),
|
||||||
|
..*self
|
||||||
|
},
|
||||||
|
_ => unreachable!("unknown variable"),
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unsat(&self, r: &Rule) -> PartRange {
|
||||||
|
match r {
|
||||||
|
Rule::Conditional(var, sign, value, _) => match var {
|
||||||
|
'x' => Self {
|
||||||
|
x: self.x.unsat(sign, value),
|
||||||
|
..*self
|
||||||
|
},
|
||||||
|
'm' => Self {
|
||||||
|
m: self.m.unsat(sign, value),
|
||||||
|
..*self
|
||||||
|
},
|
||||||
|
'a' => Self {
|
||||||
|
a: self.a.unsat(sign, value),
|
||||||
|
..*self
|
||||||
|
},
|
||||||
|
's' => Self {
|
||||||
|
s: self.s.unsat(sign, value),
|
||||||
|
..*self
|
||||||
|
},
|
||||||
|
_ => unreachable!("unknown variable"),
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Day19 {
|
||||||
|
workflows: HashMap<String, Workflow>,
|
||||||
|
parts: Vec<Part>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Day19 {
|
||||||
|
fn accepts(&self, p: &Part) -> bool {
|
||||||
|
let mut workflow = "in";
|
||||||
|
loop {
|
||||||
|
match self.workflows[workflow].evaluate(p) {
|
||||||
|
Action::Accept => {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Action::Reject => {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Action::Forward(next) => {
|
||||||
|
workflow = &next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs<'a>(
|
||||||
|
&'a self,
|
||||||
|
visited: &mut HashMap<&'a str, bool>,
|
||||||
|
w: &'a str,
|
||||||
|
mut prange: PartRange,
|
||||||
|
) -> i64 {
|
||||||
|
if visited.contains_key(w) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut accepted = 0;
|
||||||
|
visited.insert(w, true);
|
||||||
|
|
||||||
|
let workflow = &self.workflows[w];
|
||||||
|
for r in &workflow.rules {
|
||||||
|
match r {
|
||||||
|
c @ Rule::Conditional(_, _, _, action) => {
|
||||||
|
match action {
|
||||||
|
Action::Accept => {
|
||||||
|
let satisfied = prange.sat(c);
|
||||||
|
accepted += satisfied.len();
|
||||||
|
}
|
||||||
|
Action::Forward(nw) => {
|
||||||
|
accepted += self.dfs(visited, nw, prange.sat(c));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
prange = prange.unsat(c);
|
||||||
|
}
|
||||||
|
Rule::Passthrough(Action::Accept) => {
|
||||||
|
accepted += prange.len();
|
||||||
|
}
|
||||||
|
Rule::Passthrough(Action::Forward(nw)) => {
|
||||||
|
accepted += self.dfs(visited, nw, prange);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accepted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Solution<Output1, Output2> for Day19 {
|
||||||
|
fn new<P: AsRef<Path>>(pathname: P) -> Self {
|
||||||
|
let input = file_to_string(pathname);
|
||||||
|
let (workflows, parts) = input.split_once("\n\n").unwrap();
|
||||||
|
|
||||||
|
let workflows = workflows
|
||||||
|
.lines()
|
||||||
|
.map(|l| {
|
||||||
|
let (name, rules) = l.split_once('{').unwrap();
|
||||||
|
|
||||||
|
let name = name.to_owned();
|
||||||
|
let workflow = rules[..rules.len() - 1].parse::<Workflow>().unwrap();
|
||||||
|
|
||||||
|
(name, workflow)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let parts = parts.lines().map(|l| l.parse().unwrap()).collect_vec();
|
||||||
|
Self { workflows, parts }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part_1(&mut self) -> Output1 {
|
||||||
|
self.parts
|
||||||
|
.iter()
|
||||||
|
.filter_map(|p| self.accepts(p).then_some(p.rating()))
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part_2(&mut self) -> Output2 {
|
||||||
|
let mut visited = HashMap::new();
|
||||||
|
|
||||||
|
self.dfs(&mut visited, "in", PartRange::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
Day19::main()
|
||||||
|
}
|
||||||
|
|
||||||
|
test_sample!(day_19, Day19, 19114, 167409079868000);
|
Loading…
Reference in a new issue