use core::fmt; use std::{collections::HashMap, iter::zip}; use nom::{ branch::alt, bytes::complete::tag, character::complete::{self, alpha1, multispace1}, multi::separated_list1, sequence::tuple, IResult, Parser, }; #[derive(Debug, Clone)] enum Instruction { Assign(u16, String), Connect(String, String), Not(String, String), And(String, String, String), OneAnd(String, String), Or(String, String, String), Lshift(u16, String, String), Rshift(u16, String, String), } impl fmt::Display for Instruction { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Instruction::Assign(value, wire) => write!(f, "{} -> {}", value, wire), Instruction::Connect(source, target) => write!(f, "{} -> {}", source, target), Instruction::Not(source, target) => write!(f, "NOT {} -> {}", source, target), Instruction::And(source1, source2, target) => { write!(f, "{} AND {} -> {}", source1, source2, target) } Instruction::OneAnd(source, target) => write!(f, "1 AND {} -> {}", source, target), Instruction::Or(source1, source2, target) => { write!(f, "{} OR {} -> {}", source1, source2, target) } Instruction::Lshift(value, source, target) => { write!(f, "{} LSHIFT {} -> {}", source, value, target) } Instruction::Rshift(value, source, target) => { write!(f, "{} RSHIFT {} -> {}", source, value, target) } } } } #[derive(Debug, Clone, Copy)] enum Port { Noop, Not, And, Or, Lshift(u16), Rshift(u16), } impl fmt::Display for Port { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Port::Noop => write!(f, "Noop"), Port::Not => write!(f, "Not"), Port::And => write!(f, "And"), Port::Or => write!(f, "Or"), Port::Lshift(_) => write!(f, "Lshift"), Port::Rshift(_) => write!(f, "Rshift"), } } } fn parse_input(input: &str) -> IResult<&str, Vec> { let (input, result) = separated_list1( multispace1, alt(( tuple((complete::u16::<&str, _>, tag(" -> "), alpha1)) .map(|(val, _, target)| Instruction::Assign(val, target.to_string())), tuple((alpha1::<&str, _>, tag(" -> "), alpha1)).map(|(source, _, target)| { Instruction::Connect(source.to_string(), target.to_string()) }), tuple((tag("NOT "), alpha1::<&str, _>, tag(" -> "), alpha1)).map( |(_, source, _, target)| Instruction::Not(source.to_string(), target.to_string()), ), tuple((alpha1::<&str, _>, tag(" AND "), alpha1, tag(" -> "), alpha1)).map( |(source_1, _, source_2, _, target)| { Instruction::And( source_1.to_string(), source_2.to_string(), target.to_string(), ) }, ), tuple((tag("1 AND "), alpha1::<&str, _>, tag(" -> "), alpha1)).map( |(_, source, _, target)| { Instruction::OneAnd(source.to_string(), target.to_string()) }, ), tuple((alpha1::<&str, _>, tag(" OR "), alpha1, tag(" -> "), alpha1)).map( |(source_1, _, source_2, _, target)| { Instruction::Or( source_1.to_string(), source_2.to_string(), target.to_string(), ) }, ), tuple(( alpha1::<&str, _>, tag(" LSHIFT "), complete::u16, tag(" -> "), alpha1, )) .map(|(source, _, shift_amount, _, target)| { Instruction::Lshift(shift_amount, source.to_string(), target.to_string()) }), tuple(( alpha1::<&str, _>, tag(" RSHIFT "), complete::u16, tag(" -> "), alpha1, )) .map(|(source, _, shift_amount, _, target)| { Instruction::Rshift(shift_amount, source.to_string(), target.to_string()) }), )), )(input)?; Ok((input, result)) } type Node = (Port, Vec, Option); fn solve_first(instructions: &[Instruction], output_wire: &str) -> (u16, HashMap) { // Because the graph is centered around the edges instead of nodes, we have to go through a lot of gymnastics to build it let mut signals = HashMap::::new(); // For OneAnd operator signals.insert("one".to_string(), (Port::Noop, vec![], Some(1))); for instruction in instructions { match instruction { Instruction::Assign(value, wire) => { signals.insert(wire.to_owned(), (Port::Noop, vec![], Some(*value))); } Instruction::Connect(source, target) => { signals.insert( target.to_owned(), (Port::Noop, vec![source.to_owned()], None), ); } // We could already preprocess a bit by calculating the result if we know the input, but that might make it overly complicated a this point // Instead, we do the actual traversal and calculations later Instruction::Not(source, target) => { signals.insert( target.to_owned(), (Port::Not, vec![source.to_owned()], None), ); } Instruction::And(source_1, source_2, target) => { signals.insert( target.to_owned(), ( Port::And, vec![source_1.to_owned(), source_2.to_owned()], None, ), ); } Instruction::OneAnd(source, target) => { signals.insert( target.to_owned(), (Port::And, vec!["one".to_string(), source.to_owned()], None), ); } Instruction::Or(source_1, source_2, target) => { signals.insert( target.to_owned(), ( Port::Or, vec![source_1.to_owned(), source_2.to_owned()], None, ), ); } Instruction::Lshift(shift, source, target) => { signals.insert( target.to_owned(), (Port::Lshift(*shift), vec![source.to_owned()], None), ); } Instruction::Rshift(shift, source, target) => { signals.insert( target.to_owned(), (Port::Rshift(*shift), vec![source.to_owned()], None), ); } }; } let mut edges_to_calc = vec![output_wire.to_string()]; while !edges_to_calc.is_empty() { let edge_to_calc = edges_to_calc.pop().unwrap(); if let Some(signal) = signals.get(&edge_to_calc) { match signal { (port, incoming_wires, None) => { let incoming_signals: Vec> = incoming_wires .iter() .map(|x| match signals.get(x).unwrap() { (_, _, None) => None, (_, _, s) => s.to_owned(), }) .collect(); if incoming_signals.iter().any(|s| s.is_none()) { edges_to_calc.push(edge_to_calc); for (s, w) in zip(incoming_signals, incoming_wires) { if s.is_none() { edges_to_calc.push(w.to_owned()); } } } else { match port { Port::Noop => { assert_eq!(incoming_signals.len(), 1); signals.insert( edge_to_calc.to_owned(), (*port, incoming_wires.to_owned(), incoming_signals[0]), ); } Port::Not => { assert_eq!(incoming_signals.len(), 1); signals.insert( edge_to_calc.to_owned(), ( *port, incoming_wires.to_owned(), Some(!incoming_signals[0].unwrap()), ), ); } Port::And => { assert_eq!(incoming_signals.len(), 2); signals.insert( edge_to_calc.to_owned(), ( *port, incoming_wires.to_owned(), Some( incoming_signals[0].unwrap() & incoming_signals[1].unwrap(), ), ), ); } Port::Or => { assert_eq!(incoming_signals.len(), 2); signals.insert( edge_to_calc.to_owned(), ( *port, incoming_wires.to_owned(), Some( incoming_signals[0].unwrap() | incoming_signals[1].unwrap(), ), ), ); } Port::Lshift(shift) => { assert_eq!(incoming_signals.len(), 1); signals.insert( edge_to_calc.to_owned(), ( *port, incoming_wires.to_owned(), Some(incoming_signals[0].unwrap() << shift), ), ); } Port::Rshift(shift) => { assert_eq!(incoming_signals.len(), 1); signals.insert( edge_to_calc.to_owned(), ( *port, incoming_wires.to_owned(), Some(incoming_signals[0].unwrap() >> shift), ), ); } } } } (_, _, Some(_)) => { /* do nothing */ } } } else { unreachable!(); } } let result = signals.get(&output_wire.to_string()).unwrap().to_owned(); match result { (_, _, None) => (0, HashMap::new()), (_, _, Some(s)) => (s, signals.to_owned()), } } fn solve_second( instructions: &[Instruction], output_wire: &str, override_wire: &str, ) -> (u16, HashMap) { // Because the graph is centered around the edges instead of nodes, we have to go through a lot of gymnastics to build it let mut signals = HashMap::::new(); // For OneAnd operator signals.insert("one".to_string(), (Port::Noop, vec![], Some(1))); for instruction in instructions { match instruction { Instruction::Assign(value, wire) => { signals.insert(wire.to_owned(), (Port::Noop, vec![], Some(*value))); } Instruction::Connect(source, target) => { signals.insert( target.to_owned(), (Port::Noop, vec![source.to_owned()], None), ); } // We could already preprocess a bit by calculating the result if we know the input, but that might make it overly complicated a this point // Instead, we do the actual traversal and calculations later Instruction::Not(source, target) => { signals.insert( target.to_owned(), (Port::Not, vec![source.to_owned()], None), ); } Instruction::And(source_1, source_2, target) => { signals.insert( target.to_owned(), ( Port::And, vec![source_1.to_owned(), source_2.to_owned()], None, ), ); } Instruction::OneAnd(source, target) => { signals.insert( target.to_owned(), (Port::And, vec!["one".to_string(), source.to_owned()], None), ); } Instruction::Or(source_1, source_2, target) => { signals.insert( target.to_owned(), ( Port::Or, vec![source_1.to_owned(), source_2.to_owned()], None, ), ); } Instruction::Lshift(shift, source, target) => { signals.insert( target.to_owned(), (Port::Lshift(*shift), vec![source.to_owned()], None), ); } Instruction::Rshift(shift, source, target) => { signals.insert( target.to_owned(), (Port::Rshift(*shift), vec![source.to_owned()], None), ); } }; } let mut edges_to_calc = vec![output_wire.to_string()]; while !edges_to_calc.is_empty() { let edge_to_calc = edges_to_calc.pop().unwrap(); if let Some(signal) = signals.get(&edge_to_calc) { match signal { (port, incoming_wires, None) => { let incoming_signals: Vec> = incoming_wires .iter() .map(|x| match signals.get(x).unwrap() { (_, _, None) => None, (_, _, s) => s.to_owned(), }) .collect(); if incoming_signals.iter().any(|s| s.is_none()) { edges_to_calc.push(edge_to_calc); for (s, w) in zip(incoming_signals, incoming_wires) { if s.is_none() { edges_to_calc.push(w.to_owned()); } } } else { match port { Port::Noop => { assert_eq!(incoming_signals.len(), 1); signals.insert( edge_to_calc.to_owned(), (*port, incoming_wires.to_owned(), incoming_signals[0]), ); } Port::Not => { assert_eq!(incoming_signals.len(), 1); signals.insert( edge_to_calc.to_owned(), ( *port, incoming_wires.to_owned(), Some(!incoming_signals[0].unwrap()), ), ); } Port::And => { assert_eq!(incoming_signals.len(), 2); signals.insert( edge_to_calc.to_owned(), ( *port, incoming_wires.to_owned(), Some( incoming_signals[0].unwrap() & incoming_signals[1].unwrap(), ), ), ); } Port::Or => { assert_eq!(incoming_signals.len(), 2); signals.insert( edge_to_calc.to_owned(), ( *port, incoming_wires.to_owned(), Some( incoming_signals[0].unwrap() | incoming_signals[1].unwrap(), ), ), ); } Port::Lshift(shift) => { assert_eq!(incoming_signals.len(), 1); signals.insert( edge_to_calc.to_owned(), ( *port, incoming_wires.to_owned(), Some(incoming_signals[0].unwrap() << shift), ), ); } Port::Rshift(shift) => { assert_eq!(incoming_signals.len(), 1); signals.insert( edge_to_calc.to_owned(), ( *port, incoming_wires.to_owned(), Some(incoming_signals[0].unwrap() >> shift), ), ); } } } } (_, _, Some(_)) => { /* do nothing */ } } } else { unreachable!(); } } let result = signals.get(&output_wire.to_string()).unwrap().to_owned(); for instruction in instructions { match instruction { Instruction::Assign(value, wire) => { signals.insert(wire.to_owned(), (Port::Noop, vec![], Some(*value))); } Instruction::Connect(source, target) => { signals.insert( target.to_owned(), (Port::Noop, vec![source.to_owned()], None), ); } // We could already preprocess a bit by calculating the result if we know the input, but that might make it overly complicated a this point // Instead, we do the actual traversal and calculations later Instruction::Not(source, target) => { signals.insert( target.to_owned(), (Port::Not, vec![source.to_owned()], None), ); } Instruction::And(source_1, source_2, target) => { signals.insert( target.to_owned(), ( Port::And, vec![source_1.to_owned(), source_2.to_owned()], None, ), ); } Instruction::OneAnd(source, target) => { signals.insert( target.to_owned(), (Port::And, vec!["one".to_string(), source.to_owned()], None), ); } Instruction::Or(source_1, source_2, target) => { signals.insert( target.to_owned(), ( Port::Or, vec![source_1.to_owned(), source_2.to_owned()], None, ), ); } Instruction::Lshift(shift, source, target) => { signals.insert( target.to_owned(), (Port::Lshift(*shift), vec![source.to_owned()], None), ); } Instruction::Rshift(shift, source, target) => { signals.insert( target.to_owned(), (Port::Rshift(*shift), vec![source.to_owned()], None), ); } }; } signals.insert( override_wire.to_string(), (Port::Noop, vec![], Some(result.2.unwrap())), ); let mut edges_to_calc = vec![output_wire.to_string()]; while !edges_to_calc.is_empty() { let edge_to_calc = edges_to_calc.pop().unwrap(); if let Some(signal) = signals.get(&edge_to_calc) { match signal { (port, incoming_wires, None) => { let incoming_signals: Vec> = incoming_wires .iter() .map(|x| match signals.get(x).unwrap() { (_, _, None) => None, (_, _, s) => s.to_owned(), }) .collect(); if incoming_signals.iter().any(|s| s.is_none()) { edges_to_calc.push(edge_to_calc); for (s, w) in zip(incoming_signals, incoming_wires) { if s.is_none() { edges_to_calc.push(w.to_owned()); } } } else { match port { Port::Noop => { assert_eq!(incoming_signals.len(), 1); signals.insert( edge_to_calc.to_owned(), (*port, incoming_wires.to_owned(), incoming_signals[0]), ); } Port::Not => { assert_eq!(incoming_signals.len(), 1); signals.insert( edge_to_calc.to_owned(), ( *port, incoming_wires.to_owned(), Some(!incoming_signals[0].unwrap()), ), ); } Port::And => { assert_eq!(incoming_signals.len(), 2); signals.insert( edge_to_calc.to_owned(), ( *port, incoming_wires.to_owned(), Some( incoming_signals[0].unwrap() & incoming_signals[1].unwrap(), ), ), ); } Port::Or => { assert_eq!(incoming_signals.len(), 2); signals.insert( edge_to_calc.to_owned(), ( *port, incoming_wires.to_owned(), Some( incoming_signals[0].unwrap() | incoming_signals[1].unwrap(), ), ), ); } Port::Lshift(shift) => { assert_eq!(incoming_signals.len(), 1); signals.insert( edge_to_calc.to_owned(), ( *port, incoming_wires.to_owned(), Some(incoming_signals[0].unwrap() << shift), ), ); } Port::Rshift(shift) => { assert_eq!(incoming_signals.len(), 1); signals.insert( edge_to_calc.to_owned(), ( *port, incoming_wires.to_owned(), Some(incoming_signals[0].unwrap() >> shift), ), ); } } } } (_, _, Some(_)) => { /* do nothing */ } } } else { unreachable!(); } } let result = signals.get(&output_wire.to_string()).unwrap().to_owned(); match result { (_, _, None) => (0, HashMap::new()), (_, _, Some(s)) => (s, signals.to_owned()), } } fn main() { println!("Hello, this is Patrick!"); let input_txt = include_str!("../input.txt"); let (_, instructions) = parse_input(input_txt).unwrap(); let (first_result, _) = solve_first(&instructions[..], "a"); println!("The provided signal at wire 'a' is {}", first_result); let (second_result, _) = solve_second(&instructions[..], "a", "b"); println!( "The signal at 'a' is {} when overriding 'b' with it and running it again", { second_result } ); }