703 lines
27 KiB
Rust
703 lines
27 KiB
Rust
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<Instruction>> {
|
|
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<String>, Option<u16>);
|
|
|
|
fn solve_first(instructions: &[Instruction], output_wire: &str) -> (u16, HashMap<String, Node>) {
|
|
// 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::<String, Node>::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<Option<u16>> = 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<String, Node>) {
|
|
// 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::<String, Node>::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<Option<u16>> = 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<Option<u16>> = 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 }
|
|
);
|
|
}
|