advent_of_code_2024/src/day24.rs

230 lines
7.7 KiB
Rust

use itertools::Itertools;
use std::collections::HashMap;
pub fn day24() {
let (_values, mut connections) = input(&crate::input(24));
fn swap(connections: &mut Vec<Connection>, a_name: &str, b_name: &str) {
let a = connections
.iter()
.position(|connection| &connection.output == a_name)
.unwrap();
let b = connections
.iter()
.position(|connection| &connection.output == b_name)
.unwrap();
connections[a].output = b_name.to_string();
connections[b].output = a_name.to_string();
}
swap(&mut connections, "z06", "vwr");
swap(&mut connections, "z11", "tqm");
swap(&mut connections, "z16", "kfs");
swap(&mut connections, "hcm", "gfv");
fn wire_number(w: &str) -> u64 {
w.get(1..).unwrap().parse().unwrap()
}
fn is_start_wire(w: &str) -> bool {
w.starts_with("x") || w.starts_with("y")
}
fn are_start_wires(a: &str, b: &str) -> Option<u64> {
if !is_start_wire(a) || !is_start_wire(b) {
return None;
}
let a_n = wire_number(a);
let b_n = wire_number(b);
if a_n == b_n {
Some(a_n)
} else {
None
}
}
let mut carry_out_wires: HashMap<u64, String> = HashMap::new();
let mut second_carries: HashMap<u64, String> = HashMap::new();
for i in 0..45 {
// The first sum wire goes xi XOR yi
let first_sum = connections.iter().find(|connection| {
are_start_wires(&connection.a, &connection.b) == Some(i) && connection.f == xor
});
let first_sum = match first_sum {
Some(x) => &x.output,
None => panic!("First sum for {i} does not exist."),
};
// The first carry wire goes xi AND yi
let first_carry = connections.iter().find(|connection| {
are_start_wires(&connection.a, &connection.b) == Some(i) && connection.f == and
});
let first_carry = match first_carry {
Some(x) => &x.output,
None => panic!("First carry for {i} does not exist."),
};
// If this is 0, the first carry is the carry_out and the first sum is the output (and skip the rest)
if i == 0 {
if wire_number(&first_sum) != i || !first_sum.starts_with("z") {
panic!("The first output wire is wrong.");
}
carry_out_wires.insert(0, first_carry.clone());
continue;
}
// The output wire goes [first_sum] XOR [prev carry_out] -> zi
let prev_carry_out = carry_out_wires.get(&(i - 1)).unwrap();
let output = &connections
.iter()
.find(|connection| {
let p1 = &connection.a == first_sum
&& &connection.b == prev_carry_out
&& connection.f == xor;
let p2 = &connection.b == first_sum
&& &connection.a == prev_carry_out
&& connection.f == xor;
p1 || p2
})
.expect(&format!("Couldn't find an output for {i}. Previous carry-out was {prev_carry_out} and first_sum was {first_sum}."))
.output;
if !output.starts_with("z") || wire_number(&output) != i {
println!(
"The output for {i} should be z{i} but is instead {}",
output
);
}
// The second carry wire goes [first_sum] AND [prev carry_out]
let second_carry = &connections
.iter()
.find(|connection| {
let p1 = &connection.a == first_sum
&& &connection.b == prev_carry_out
&& connection.f == and;
let p2 = &connection.b == first_sum
&& &connection.a == prev_carry_out
&& connection.f == and;
p1 || p2
})
.expect(&format!("Couldn't find a second carry for {i}."))
.output;
second_carries.insert(i, second_carry.clone());
// The carry_out goes [first_carry OR second_carry]
let carry_out = &connections
.iter()
.find(|connection| {
let p1 = &connection.a == first_carry
&& &connection.b == second_carry
&& connection.f == or;
let p2 = &connection.b == first_carry
&& &connection.a == second_carry
&& connection.f == or;
p1 || p2
})
.expect(&format!("Couldn't find a carry out for {i}."))
.output;
carry_out_wires.insert(i, carry_out.clone());
}
// Finally, make sure z45 is carry_out of 44
if carry_out_wires.get(&44).unwrap() != "z45" {
println!(
"The final carry out should be z45 but is instead {}",
carry_out_wires.get(&44).unwrap()
);
}
}
fn run_system_with_inputs(connections: &Vec<Connection>, x: u64, y: u64) -> u64 {
let mut values: HashMap<String, bool> = HashMap::new();
for b in 0..45 {
values.insert(format!("x{:02}", b), ((1 << b) & x) != 0);
values.insert(format!("y{:02}", b), ((1 << b) & y) != 0);
}
run_system(&values, connections)
}
fn run_system(values: &HashMap<String, bool>, connections: &Vec<Connection>) -> u64 {
let mut values = values.clone();
let mut connections = connections.clone();
let mut remaining_connections = vec![];
while !connections.is_empty() {
for connection in connections {
if values.contains_key(&connection.a) && values.contains_key(&connection.b) {
values.insert(
connection.output.to_string(),
(connection.f)(values[&connection.a], values[&connection.b]),
);
} else {
remaining_connections.push(connection);
}
}
connections = remaining_connections;
remaining_connections = vec![];
}
let mut number: u64 = 0;
for (wire, value) in values.iter() {
if wire.starts_with("z") {
let place: u64 = wire.get(1..).unwrap().parse().unwrap();
number = number | ((*value as u64) << place);
}
}
number
}
type Gate = fn(bool, bool) -> bool;
#[derive(Clone)]
struct Connection {
a: String,
b: String,
f: Gate,
output: String,
}
impl Connection {
fn new(a: String, b: String, f: Gate, output: String) -> Self {
Self { a, b, f, output }
}
}
fn input(s: &str) -> (HashMap<String, bool>, Vec<Connection>) {
let (values_input, gates_input) = s.split("\n\n").collect_tuple().unwrap();
let mut values: HashMap<String, bool> = values_input
.lines()
.map(|line| {
let (name, value) = line.split(": ").collect_tuple().unwrap();
let value = match value {
"0" => false,
"1" => true,
_ => panic!(),
};
(name.to_string(), value)
})
.collect();
let mut connections: Vec<Connection> = gates_input
.lines()
.map(|line| {
let parts: Vec<&str> = line.split(" ").collect();
let (a, gate, b, c) = (parts[0], parts[1], parts[2], parts[4]);
let gate = match gate {
"AND" => and,
"OR" => or,
"XOR" => xor,
_ => panic!(),
};
Connection::new(a.to_string(), b.to_string(), gate, c.to_string())
})
.collect();
(values, connections)
}
fn and(a: bool, b: bool) -> bool {
a && b
}
fn or(a: bool, b: bool) -> bool {
a || b
}
fn xor(a: bool, b: bool) -> bool {
a ^ b
}