Solve 24
This commit is contained in:
parent
9e8d3fb4bb
commit
c37ca571a5
1 changed files with 293 additions and 0 deletions
293
src/bin/24.rs
Normal file
293
src/bin/24.rs
Normal file
|
@ -0,0 +1,293 @@
|
|||
use aoc::*;
|
||||
use itertools::Itertools;
|
||||
use std::cmp::{Ordering, Reverse};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
|
||||
const INPUT: &str = include_str!("../../input/24");
|
||||
|
||||
fn main() {
|
||||
assert_example!(part1, "24-test", 2024);
|
||||
println!("Part 1: {}", part1(INPUT));
|
||||
part2(INPUT);
|
||||
}
|
||||
|
||||
fn part1(input: &str) -> usize {
|
||||
let mut device = Device::parse(input);
|
||||
device.propagate();
|
||||
device.integer('z')
|
||||
}
|
||||
|
||||
fn part2(input: &str) {
|
||||
let device = Device::parse(input);
|
||||
|
||||
let mistakes = device
|
||||
.gates
|
||||
.iter()
|
||||
.filter(|&&gate| !is_gate_ok(&device, gate))
|
||||
.map(|gate| gate.out)
|
||||
.unique()
|
||||
.collect_vec();
|
||||
|
||||
println!("digraph {{");
|
||||
for gate in &device.gates {
|
||||
let shape = match gate.op {
|
||||
Op::And => "box",
|
||||
Op::Or => "circle",
|
||||
Op::Xor => "diamond",
|
||||
};
|
||||
let color = if mistakes.contains(&gate.out) {
|
||||
",fillcolor=red,style=filled"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
println!(
|
||||
r#"{} [label={:?}{color},shape={shape}];"#,
|
||||
gate.out, gate.op
|
||||
);
|
||||
println!("{} -> {} [label={}];", gate.a, gate.out, gate.a);
|
||||
println!("{} -> {} [label={}];", gate.b, gate.out, gate.b);
|
||||
}
|
||||
for gate in device.gates.iter().filter(|gate| gate.out.is_output()) {
|
||||
println!("{}_out [label={},shape=doublecircle];", gate.out, gate.out);
|
||||
println!("{} -> {}_out [label={}];", gate.out, gate.out, gate.out);
|
||||
}
|
||||
println!("}}");
|
||||
|
||||
eprintln!("\nYou should pipe this diagram into graphviz");
|
||||
eprintln!("and look for mistakes in the colored nodes.");
|
||||
eprintln!(" just day=24 | dot -T svg -o 24.svg");
|
||||
}
|
||||
|
||||
/// Return true if the gate seems to be connected correctly.
|
||||
/// Rules were derived by staring at circuit diagrams:
|
||||
/// https://en.wikipedia.org/wiki/Adder_(electronics)
|
||||
fn is_gate_ok(device: &Device, gate: Gate) -> bool {
|
||||
match gate.op {
|
||||
Op::And | Op::Xor => {
|
||||
if gate.out.is_output() && gate.out.n() <= 1 {
|
||||
return true;
|
||||
}
|
||||
if gate.a.is_input() && gate.b.is_input() {
|
||||
return true;
|
||||
}
|
||||
let a = device.gate_with_output(gate.a).unwrap();
|
||||
let b = device.gate_with_output(gate.b).unwrap();
|
||||
if a.op == Op::Or && b.op == Op::Xor || a.op == Op::Xor && b.op == Op::Or {
|
||||
return true;
|
||||
}
|
||||
eprintln!(
|
||||
"[{:?}] needs to be connected to input or to XOR and OR {gate:?}",
|
||||
gate.op
|
||||
);
|
||||
false
|
||||
}
|
||||
Op::Or => {
|
||||
let a = device.gate_with_output(gate.a);
|
||||
let b = device.gate_with_output(gate.b);
|
||||
let (a, b) = match (a, b) {
|
||||
(Some(a), Some(b)) => (a, b),
|
||||
_ => {
|
||||
eprintln!("[Or] gate is missing an input: {gate:?}");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if a.op != Op::And || b.op != Op::And {
|
||||
eprintln!("[Or] gate must only be connected to AND gates: {gate:?}");
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Device {
|
||||
values: HashMap<Wire, bool>,
|
||||
gates: Vec<Gate>,
|
||||
swap: Vec<(Wire, Wire)>,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
fn propagate(&mut self) {
|
||||
// Limit the number of iterations to prevent loops
|
||||
for _ in 0..50 {
|
||||
let mut changed = false;
|
||||
|
||||
for gate in &self.gates {
|
||||
let Some(a) = self.get_value(gate.a) else {
|
||||
continue;
|
||||
};
|
||||
let Some(b) = self.get_value(gate.b) else {
|
||||
continue;
|
||||
};
|
||||
let value = gate.op.eval(a, b);
|
||||
|
||||
let prev = self.values.insert(gate.out, value);
|
||||
if prev != Some(value) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !changed {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_value(&self, wire: Wire) -> Option<bool> {
|
||||
// Swap wire, if applicable
|
||||
let wire = self
|
||||
.swap
|
||||
.iter()
|
||||
.copied()
|
||||
.find_map(|(a, b)| {
|
||||
if a == wire {
|
||||
Some(b)
|
||||
} else if b == wire {
|
||||
Some(a)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(wire);
|
||||
self.values.get(&wire).copied()
|
||||
}
|
||||
|
||||
fn gate_with_output(&self, wire: Wire) -> Option<Gate> {
|
||||
self.gates
|
||||
.iter()
|
||||
.filter(move |g| g.out == wire)
|
||||
.exactly_one()
|
||||
.copied()
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn integer(&self, c: char) -> usize {
|
||||
let wires = self
|
||||
.values
|
||||
.keys()
|
||||
.copied()
|
||||
.filter(|w| w.0 == c)
|
||||
.sorted_by_key(|&w| Reverse(w));
|
||||
let values = wires
|
||||
.map(|w| self.get_value(w).unwrap())
|
||||
.map(|v| v as usize);
|
||||
values.fold(0, |acc, v| (acc << 1) | v)
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> Self {
|
||||
let (values, gates) = input.split_once("\n\n").unwrap();
|
||||
let values = values.lines().map(parse_wire_value).collect();
|
||||
let gates = gates.lines().map(Gate::parse).collect();
|
||||
Self {
|
||||
values,
|
||||
gates,
|
||||
swap: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct Gate {
|
||||
a: Wire,
|
||||
b: Wire,
|
||||
op: Op,
|
||||
out: Wire,
|
||||
}
|
||||
|
||||
impl Gate {
|
||||
fn parse(line: &str) -> Self {
|
||||
let (input, out) = line.split_once(" -> ").unwrap();
|
||||
let [a, op, b] = input.split_whitespace().collect_vec().try_into().unwrap();
|
||||
let a = Wire::parse(a);
|
||||
let b = Wire::parse(b);
|
||||
let op = Op::parse(op);
|
||||
let out = Wire::parse(out);
|
||||
Self { a, b, op, out }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
struct Wire(char, char, char);
|
||||
|
||||
impl Wire {
|
||||
fn parse(s: &str) -> Self {
|
||||
if s.len() != 3 {
|
||||
panic!("wire length must be three, got {} ({s})", s.len());
|
||||
}
|
||||
|
||||
let (a, b, c) = s.chars().tuple_windows().exactly_one().unwrap();
|
||||
Self(a, b, c)
|
||||
}
|
||||
|
||||
fn is_input(self) -> bool {
|
||||
self.0 == 'x' || self.0 == 'y'
|
||||
}
|
||||
|
||||
fn is_output(self) -> bool {
|
||||
self.0 == 'z'
|
||||
}
|
||||
|
||||
fn n(self) -> u8 {
|
||||
format!("{}{}", self.1, self.2).parse().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Wire {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
(self.0, self.1, self.2).cmp(&(other.0, other.1, other.2))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Wire {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Wire {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let s = format!("{}{}{}", self.0, self.1, self.2);
|
||||
f.debug_tuple("Wire").field(&s).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Wire {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}{}{}", self.0, self.1, self.2)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_wire_value(s: &str) -> (Wire, bool) {
|
||||
let (wire, value) = s.split_once(": ").unwrap();
|
||||
let wire = Wire::parse(wire);
|
||||
let value: u8 = value.parse().unwrap();
|
||||
(wire, value > 0)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
enum Op {
|
||||
And,
|
||||
Or,
|
||||
Xor,
|
||||
}
|
||||
|
||||
impl Op {
|
||||
fn parse(s: &str) -> Self {
|
||||
match s {
|
||||
"AND" => Self::And,
|
||||
"OR" => Self::Or,
|
||||
"XOR" => Self::Xor,
|
||||
other => panic!("unknown gate '{other}'"),
|
||||
}
|
||||
}
|
||||
|
||||
fn eval(self, a: bool, b: bool) -> bool {
|
||||
match self {
|
||||
Self::And => a && b,
|
||||
Self::Or => a || b,
|
||||
Self::Xor => a ^ b,
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue