--- /dev/null
+use std::io::Read;
+use std::error::Error;
+use std::fs::File;
+
+use std::collections::HashMap;
+
+pub fn run(input_file: &str) -> Result<(), Box<dyn Error>> {
+ let mut f = File::open(input_file)?;
+ let mut input = String::new();
+ f.read_to_string(&mut input)?;
+
+ let res = run_part1(&input)?;
+ println!("{res}");
+
+ let res = run_part2(&input)?;
+ println!("{res}");
+
+ Ok(())
+}
+
+#[derive(Debug)]
+struct Puzzle {
+ workflows: HashMap<String, String>,
+ ratings: Vec<(u32, u32, u32, u32)>,
+}
+
+impl Puzzle {
+ pub fn new(input: &str) -> Self {
+ let mut workflows: HashMap<String, String> = HashMap::new();
+
+ let (workflows_str, ratings_str) = input.split_once("\n\n").unwrap();
+ workflows_str.lines()
+ .for_each(|l| {
+ let (key, rules) = l.split_once('{').unwrap();
+ let rules = &rules[..rules.len() - 1];
+ workflows.insert(key.to_string(), rules.to_string());
+ });
+
+ // (x, m, a, s)
+ let ratings: Vec<(u32, u32, u32, u32)> = ratings_str.lines()
+ .filter_map(|l| {
+ match l.is_empty() {
+ true => None,
+ false => {
+ let values = &mut l[1..l.len() - 1].split(',');
+ let x: u32 = values.next().unwrap().split_once('=').unwrap().1.parse().unwrap();
+ let m: u32 = values.next().unwrap().split_once('=').unwrap().1.parse().unwrap();
+ let a: u32 = values.next().unwrap().split_once('=').unwrap().1.parse().unwrap();
+ let s: u32 = values.next().unwrap().split_once('=').unwrap().1.parse().unwrap();
+ Some((x, m, a, s))
+ }
+ }
+ })
+ .collect();
+
+ Self {
+ workflows,
+ ratings,
+ }
+ }
+
+ pub fn travel_workflow(&self, (x, m, a, s): (u32, u32, u32, u32)) -> char {
+ //println!("(x,m,a,s): ({}, {}, {}, {})", x, m, a, s);
+ let mut key = "in";
+ while self.workflows.contains_key(key) {
+ //println!("key: {}", key);
+ let rules = self.workflows.get(key).unwrap();
+ //println!("rules: {}", rules);
+ for r in rules.split(',') {
+ //println!("r: {}", r);
+ if r.contains(':') {
+ let (cond, workflow) = r.split_once(':').unwrap();
+ let cat: char = (&cond[0..1]).parse().unwrap();
+ let op: char = (&cond[1..2]).parse().unwrap();
+ let value: u32 = (&cond[2..]).parse().unwrap();
+ //println!("cat: {}, {} , {}", cat, op, value);
+
+ let test_rule = match cat {
+ 'x' => compare(x, op, value),
+ 'm' => compare(m, op, value),
+ 'a' => compare(a, op, value),
+ 's' => compare(s, op, value),
+ _ => unreachable!(),
+ };
+ if test_rule {
+ key = workflow;
+ break;
+ }
+ }
+ else {
+ key = r;
+ }
+ }
+ }
+
+ (&key[0..1]).parse().unwrap()
+ }
+
+}
+
+fn compare(x: u32, op: char, value: u32) -> bool {
+ match op {
+ '<' => x < value,
+ '>' => x > value,
+ '=' => x == value,
+ _ => unreachable!(),
+ }
+}
+
+
+fn run_part1(input: &str) -> Result<u32, Box<dyn Error>> {
+ println!("Running day19 - part 1");
+
+ let puzzle = Puzzle::new(input);
+
+ let res = puzzle.ratings.iter()
+ .map(|r| (puzzle.travel_workflow(*r), r))
+ .filter_map(|(c, r)| {
+ match c {
+ 'R' => None,
+ 'A' => {
+ Some(r.0 + r.1 + r.2 + r.3)
+ },
+ _ => unreachable!(),
+ }
+ })
+ .sum();
+
+ Ok(res)
+}
+
+fn run_part2(input: &str) -> Result<u32, Box<dyn Error>> {
+ println!("Running day19 - part 2");
+ let res = 0;
+ Ok(res)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ static TEXT_INPUT: &str = "\
+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}";
+
+ #[test]
+ fn day19_part1() {
+ let res = run_part1(TEXT_INPUT);
+ assert_eq!(19114, res.unwrap());
+ }
+
+ #[test]
+ fn day19_part2() {
+ let res = run_part2(TEXT_INPUT);
+ assert_eq!(0, res.unwrap());
+ }
+}