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