--- /dev/null
+use std::error::Error;
+use std::path::Path;
+use std::collections::HashMap;
+
+#[derive(Copy, Clone, Debug)]
+struct Gate<'a> {
+ in1: &'a str,
+ in2: &'a str,
+ op: &'a str,
+}
+
+impl<'a> Gate<'a> {
+ pub fn new(in1: &'a str, in2: &'a str, op: &'a str) -> Self {
+ Self { in1, in2, op }
+ }
+
+ fn solve(&self, wires: &HashMap<&str, u8>) -> u8 {
+ if !wires.contains_key(&self.in1) && !wires.contains_key(&self.in2) {
+ unreachable!();
+ }
+ let w1 = wires.get(&self.in1).unwrap();
+ let w2 = wires.get(&self.in2).unwrap();
+ match self.op {
+ "AND" => w1 & w2,
+ "OR" => w1 | w2,
+ "XOR" => w1 ^ w2,
+ _ => unreachable!()
+ }
+ }
+}
+
+struct Puzzle<'a> {
+ wires: HashMap<&'a str, u8>,
+ gates: HashMap<&'a str, Gate<'a>>,
+}
+
+impl<'a> Puzzle<'a> {
+ pub fn new(input: &'a str) -> Self {
+ let (wires, gates) = input.split_once("\n\n").unwrap();
+ let wires = wires.lines()
+ .map(|l| (&l[0..3], l[l.len()-1..].parse::<u8>().unwrap()))
+ .collect();
+ let gates = gates.lines()
+ .map(|l| {
+ let mut it = l.split_ascii_whitespace();
+ let in1 = it.next().unwrap();
+ let op = it.next().unwrap();
+ let in2 = it.next().unwrap();
+ it.next();
+ let res = it.next().unwrap();
+ (res, Gate::new(in1, in2, op))
+ })
+ .collect();
+ Self { wires, gates }
+ }
+
+ fn solve_gate(&mut self, out: &'a str) {
+ match self.wires.get(out) {
+ None => {
+ let g = *self.gates.get(out).unwrap();
+ match self.wires.get(g.in1) {
+ None => self.solve_gate(g.in1),
+ _ => {}
+ }
+ match self.wires.get(g.in2) {
+ None => self.solve_gate(g.in2),
+ _ => {},
+ }
+ let res = g.solve(&self.wires);
+ self.wires.insert(out, res);
+ },
+ _ => {}
+ }
+ }
+
+ fn solve(&mut self) -> u64 {
+ let mut z_gates: Vec<&str> = self.gates.keys()
+ .filter(|k| k.starts_with("z"))
+ .map(|k| *k)
+ .collect();
+ z_gates.sort_by(|a, b| a.cmp(&b));
+ z_gates.iter().for_each(|k| { self.solve_gate(k); });
+ let res = z_gates.iter().enumerate()
+ .map(|(i, z_gate)| {
+ let z = *self.wires.get(z_gate).unwrap();
+ let z = z as u64;
+ z << i
+ })
+ .sum();
+ res
+ }
+}
+
+fn run_part1(input: &str) -> Result<u64, Box<dyn Error>> {
+ println!("Running {} - part 1", get_day());
+
+ let mut puzzle = Puzzle::new(input);
+ Ok(puzzle.solve())
+}
+
+fn run_part2(input: &str) -> Result<u32, Box<dyn Error>> {
+ println!("Running {} - part 2", get_day());
+
+ Ok(0)
+}
+
+pub fn run(input: &str) -> Result<(), Box<dyn Error>> {
+ let res = run_part1(input)?;
+ println!("{res}");
+
+ let res = run_part2(input)?;
+ println!("{res}");
+
+ Ok(())
+}
+
+fn get_day() -> String {
+ let filename = file!();
+ Path::new(filename).file_stem().unwrap().to_str().unwrap().to_string()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ static TEXT_INPUT_0: &str = "\
+x00: 1
+x01: 1
+x02: 1
+y00: 0
+y01: 1
+y02: 0
+
+x00 AND y00 -> z00
+x01 XOR y01 -> z01
+x02 OR y02 -> z02";
+ static TEXT_INPUT: &str = "\
+x00: 1
+x01: 0
+x02: 1
+x03: 1
+x04: 0
+y00: 1
+y01: 1
+y02: 1
+y03: 1
+y04: 1
+
+ntg XOR fgs -> mjb
+y02 OR x01 -> tnw
+kwq OR kpj -> z05
+x00 OR x03 -> fst
+tgd XOR rvg -> z01
+vdt OR tnw -> bfw
+bfw AND frj -> z10
+ffh OR nrd -> bqk
+y00 AND y03 -> djm
+y03 OR y00 -> psh
+bqk OR frj -> z08
+tnw OR fst -> frj
+gnj AND tgd -> z11
+bfw XOR mjb -> z00
+x03 OR x00 -> vdt
+gnj AND wpb -> z02
+x04 AND y00 -> kjc
+djm OR pbm -> qhw
+nrd AND vdt -> hwm
+kjc AND fst -> rvg
+y04 OR y02 -> fgs
+y01 AND x02 -> pbm
+ntg OR kjc -> kwq
+psh XOR fgs -> tgd
+qhw XOR tgd -> z09
+pbm OR djm -> kpj
+x03 XOR y03 -> ffh
+x00 XOR y04 -> ntg
+bfw OR bqk -> z06
+nrd XOR fgs -> wpb
+frj XOR qhw -> z04
+bqk OR frj -> z07
+y03 OR x01 -> nrd
+hwm AND bqk -> z03
+tgd XOR rvg -> z12
+tnw OR pbm -> gnj";
+
+ #[test]
+ fn test_part1() {
+ assert_eq!(4, run_part1(TEXT_INPUT_0).unwrap());
+ assert_eq!(2024, run_part1(TEXT_INPUT).unwrap());
+ }
+
+ #[test]
+ fn test_part2() {
+ assert_eq!(0, run_part2(TEXT_INPUT).unwrap());
+ }
+}