1
0
Fork 0

day(19): add solution

Signed-off-by: Matej Focko <me@mfocko.xyz>
This commit is contained in:
Matej Focko 2023-12-19 10:42:06 +01:00
parent 2e889073cd
commit 3c813855e3
Signed by: mfocko
GPG key ID: 7C47D46246790496
2 changed files with 359 additions and 0 deletions

17
samples/day19.txt Normal file
View 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
View 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);