Day 24
This commit is contained in:
parent
b83e13d0bb
commit
87883a52dc
4 changed files with 355 additions and 0 deletions
77
2024/day24/Cargo.lock
generated
Normal file
77
2024/day24/Cargo.lock
generated
Normal file
|
@ -0,0 +1,77 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "day24"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
9
2024/day24/Cargo.toml
Normal file
9
2024/day24/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "day24"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.13.0"
|
||||
lazy_static = "1.5.0"
|
||||
regex = "1.11.1"
|
25
2024/day24/README.md
Normal file
25
2024/day24/README.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Solution Day 24
|
||||
|
||||
Logic gate. What a treat. I couldn't fully solve it, though, as I got caught in thinking _way_ too complex. See below...
|
||||
|
||||
## Task 1
|
||||
|
||||
Rather straightforward. Each gate is represented by the `Gate` struct, which implements a `calculate` function, that either
|
||||
gets the input values from the known inputs or recursivly calculates other gates to then calculate its own value. This
|
||||
is slightly less efficient, than optimal, assuming we could cache intermediate values, but the calculation still is more
|
||||
than fast enough.
|
||||
|
||||
## Task 2
|
||||
|
||||
Didn't solve this one because I thought way too complex. Looking at the input size, it was clear, that a brute-force search
|
||||
for the correct swaps wouldn't be possible. I tried a few approaches to limit the search space. My main idea was, that no
|
||||
swaps should be done for outputs, that are already correct (since we might change those) and that swaps should only be performed
|
||||
between gates with different output values (otherwise we wouldn't change anything). After implementing the necessary preparations,
|
||||
I discovered, that the search space is _still_ too large (I just let the commented out loop generating all the possible swaps
|
||||
run without actually performing the swap, just to get a feel and was bored quite quickly of the runtime). After that I gave up
|
||||
and searched for inspiration on reddit.
|
||||
|
||||
Turns out: my complex approach got me down the wrong path completely. If I had remembered my days as uni and the details
|
||||
of what an adder looked like (I could still design one without looking it up. I just never thought of this circuit being
|
||||
a standard parallel adder), I probably would've been able to find the swapped wires quite quickly. Well, you can't always
|
||||
win, I guess…
|
244
2024/day24/src/main.rs
Normal file
244
2024/day24/src/main.rs
Normal file
|
@ -0,0 +1,244 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use itertools::Itertools;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
|
||||
enum Op {
|
||||
AND,
|
||||
OR,
|
||||
XOR,
|
||||
}
|
||||
|
||||
struct Gate {
|
||||
input1_name: &'static str,
|
||||
input2_name: &'static str,
|
||||
output_name: &'static str,
|
||||
op: Op,
|
||||
}
|
||||
|
||||
impl Gate {
|
||||
fn calculate(
|
||||
&self,
|
||||
inputs: &HashMap<&'static str, bool>,
|
||||
gates: &HashMap<&'static str, Gate>,
|
||||
) -> bool {
|
||||
let input1 = inputs.get(self.input1_name).cloned().unwrap_or_else(|| {
|
||||
gates
|
||||
.get(self.input1_name)
|
||||
.unwrap()
|
||||
.calculate(inputs, gates)
|
||||
});
|
||||
let input2 = inputs.get(self.input2_name).cloned().unwrap_or_else(|| {
|
||||
gates
|
||||
.get(self.input2_name)
|
||||
.unwrap()
|
||||
.calculate(inputs, gates)
|
||||
});
|
||||
match self.op {
|
||||
Op::AND => input1 && input2,
|
||||
Op::OR => input1 || input2,
|
||||
Op::XOR => input1 ^ input2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref GATE_REGEX: Regex =
|
||||
Regex::new(r"([\w\d]+) (AND|OR|XOR) ([\w\d]+) -> ([\w\d]+)").unwrap();
|
||||
}
|
||||
|
||||
impl From<&'static str> for Gate {
|
||||
fn from(value: &'static str) -> Self {
|
||||
let matches = GATE_REGEX.captures(value).unwrap();
|
||||
Gate {
|
||||
input1_name: matches.get(1).unwrap().as_str(),
|
||||
input2_name: matches.get(3).unwrap().as_str(),
|
||||
output_name: matches.get(4).unwrap().as_str(),
|
||||
op: match matches.get(2).unwrap().as_str() {
|
||||
"AND" => Op::AND,
|
||||
"OR" => Op::OR,
|
||||
"XOR" => Op::XOR,
|
||||
op => panic!("Unknown op {op}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Input {
|
||||
inputs: HashMap<&'static str, bool>,
|
||||
gates: HashMap<&'static str, Gate>,
|
||||
}
|
||||
|
||||
fn load_input() -> Input {
|
||||
let mut data = include_str!("../input.txt").lines();
|
||||
|
||||
Input {
|
||||
inputs: HashMap::from_iter(
|
||||
data.by_ref()
|
||||
.take_while(|line| !line.is_empty())
|
||||
.map(|line| {
|
||||
let mut parts = line.split(": ");
|
||||
(
|
||||
parts.next().unwrap(),
|
||||
if parts.next().unwrap() == "1" {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
},
|
||||
)
|
||||
}),
|
||||
),
|
||||
gates: HashMap::from_iter(data.map(|line| {
|
||||
let gate: Gate = line.into();
|
||||
(gate.output_name, gate)
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_decimal<Iter: Iterator<Item = bool>>(bits: Iter) -> u64 {
|
||||
bits.enumerate().fold(0_u64, |existing, (idx, value)| {
|
||||
if value {
|
||||
existing + (1_u64 << idx)
|
||||
} else {
|
||||
existing
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn task1() {
|
||||
let input = load_input();
|
||||
|
||||
let mut outputs: Vec<_> = input
|
||||
.gates
|
||||
.iter()
|
||||
.filter(|(&output_name, _)| output_name.starts_with("z"))
|
||||
.map(|(_, gate)| gate)
|
||||
.collect();
|
||||
|
||||
outputs.sort_by_key(|gate| gate.output_name);
|
||||
|
||||
let result = outputs
|
||||
.iter()
|
||||
.map(|gate| gate.calculate(&input.inputs, &input.gates));
|
||||
|
||||
let number = to_decimal(result);
|
||||
|
||||
println!("Task: result: {number}");
|
||||
}
|
||||
|
||||
fn get_affecting_outputs(
|
||||
output: &str,
|
||||
gates: &HashMap<&'static str, Gate>,
|
||||
) -> HashSet<&'static str> {
|
||||
let mut affecting_outputs = HashSet::new();
|
||||
let gate = gates.get(output).unwrap(); // we know it exists
|
||||
if !gate.input1_name.starts_with("x") && !gate.input1_name.starts_with("y") {
|
||||
affecting_outputs.insert(gate.input1_name);
|
||||
// affecting_outputs.(&mut );
|
||||
get_affecting_outputs(gate.input1_name, gates)
|
||||
.into_iter()
|
||||
.for_each(|gate| {
|
||||
affecting_outputs.insert(gate);
|
||||
});
|
||||
}
|
||||
if !gate.input2_name.starts_with("x") && !gate.input2_name.starts_with("y") {
|
||||
affecting_outputs.insert(gate.input2_name);
|
||||
get_affecting_outputs(gate.input2_name, gates)
|
||||
.into_iter()
|
||||
.for_each(|gate| {
|
||||
affecting_outputs.insert(gate);
|
||||
});
|
||||
}
|
||||
affecting_outputs
|
||||
}
|
||||
|
||||
fn task2() {
|
||||
let input = load_input();
|
||||
|
||||
let all_output_names: Vec<_> = input
|
||||
.gates
|
||||
.iter()
|
||||
.map(|(_, gate)| gate.output_name)
|
||||
.collect();
|
||||
|
||||
let mut x_inputs: Vec<_> = input
|
||||
.inputs
|
||||
.iter()
|
||||
.filter(|(&inp, _)| inp.starts_with("x"))
|
||||
.collect();
|
||||
x_inputs.sort_by_key(|(&inp, _)| inp);
|
||||
let x_value = to_decimal(x_inputs.iter().map(|(_, &value)| value));
|
||||
let mut y_inputs: Vec<_> = input
|
||||
.inputs
|
||||
.iter()
|
||||
.filter(|(&inp, _)| inp.starts_with("y"))
|
||||
.collect();
|
||||
y_inputs.sort_by_key(|(&inp, _)| inp);
|
||||
let y_value = to_decimal(x_inputs.iter().map(|(_, &value)| value));
|
||||
|
||||
let target_value = x_value + y_value;
|
||||
|
||||
let mut outputs: Vec<_> = input
|
||||
.gates
|
||||
.iter()
|
||||
.filter(|(&output_name, _)| output_name.starts_with("z"))
|
||||
.map(|(_, gate)| gate)
|
||||
.collect();
|
||||
outputs.sort_by_key(|gate| gate.output_name);
|
||||
|
||||
let result = outputs
|
||||
.iter()
|
||||
.map(|gate| gate.calculate(&input.inputs, &input.gates));
|
||||
|
||||
let number = to_decimal(result);
|
||||
|
||||
let wrong_outputs: Vec<_> = (0..outputs.len())
|
||||
.filter(|idx| number & (1 << idx) != target_value & (1 << idx))
|
||||
.map(|idx| format!("z{idx:#02}"))
|
||||
.collect();
|
||||
let correct_outputs: Vec<_> = (0..outputs.len())
|
||||
.filter(|idx| number & (1 << idx) == target_value & (1 << idx))
|
||||
.map(|idx| format!("z{idx:#02}"))
|
||||
.collect();
|
||||
|
||||
let potential_bad_gates: Vec<_> = wrong_outputs
|
||||
.iter()
|
||||
.map(|output| get_affecting_outputs(output, &input.gates))
|
||||
.collect();
|
||||
let good_gates: Vec<_> = correct_outputs
|
||||
.iter()
|
||||
.map(|output| get_affecting_outputs(output, &input.gates))
|
||||
.collect();
|
||||
// let's find all gates, that influence wrong output bits, but _NOT_ right output bits
|
||||
let only_bad_gates: HashSet<_> = potential_bad_gates
|
||||
.iter()
|
||||
.flat_map(|block| {
|
||||
block
|
||||
.iter()
|
||||
.filter(|&gate| good_gates.iter().any(|block| block.contains(gate)))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let gate_values: HashMap<&str, bool> =
|
||||
HashMap::from_iter(input.gates.iter().map(|(&output_name, gate)| {
|
||||
(output_name, gate.calculate(&input.inputs, &input.gates))
|
||||
}));
|
||||
|
||||
// separate all bad gates into true and false because it makes no sense to switch two gates with an identical value
|
||||
let (false_gates, true_gates): (Vec<_>, Vec<_>) = gate_values
|
||||
.iter()
|
||||
.filter(|(&name, _)| only_bad_gates.contains(&name))
|
||||
.partition(|(_, &value)| value);
|
||||
|
||||
// for replacement_false_block in false_gates.iter().combinations(4) {
|
||||
// for replacement_true_block in true_gates.iter().permutations(4) {
|
||||
// TODO execute the swap and test -> this is too slow. See README for explanation
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
task1();
|
||||
task2();
|
||||
}
|
Loading…
Add table
Reference in a new issue