-
Notifications
You must be signed in to change notification settings - Fork 15
/
day24.rs
166 lines (151 loc) · 6.21 KB
/
day24.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//! # Crossed Wires
//!
//! Part one is a straightforward simulation of the gates. Part two asks us to fix a broken
//! [ripple carry adder](https://en.wikipedia.org/wiki/Adder_(electronics)).
//!
//! The structure of the adder is:
//!
//! * Half adder for bits `x00` and `y00`. Outputs sum to `z00` and carry to `z01`.
//! * Full adder for bits `x01..x44` and `y01..y44`. Outputs carry to next bit in the chain
//! "rippling" up to final bit.
//! * `z45` is the carry output from `x44` and `y44`.
//!
//! Implemented in logic gates this looks like:
//!
//! ```none
//! Half Adder Full Adder
//! ┌───┐ ┌───┐ ┌───┐ ┌───┐
//! |x00| |y00| |x01| |y01|
//! └───┘ └───┘ └───┘ └───┘
//! | | ┌─┘ | | | ┌─┘ |
//! | └───┐ | | └───┐ |
//! | ┌-┘ | | | ┌-┘ | |
//! ┌───┐ ┌───┐ ┌───┐ ┌───┐
//! |XOR| |AND| |XOR| |AND|
//! └───┘ └───┘ └───┘ └───┘
//! | | ┌───┴┐ |
//! | └──┬────┐ | |
//! | Carry| | ┌───┐ |
//! | out | | |AND| |
//! | | | └───┘ |
//! | | | └────┐ |
//! | | └────┐ | |
//! | └────┐ | | |
//! | ┌───┐ ┌───┐
//! | |XOR| |OR | Carry
//! | └───┘ └───┘ out
//! | | | |
//! ┌───┐ ┌───┐ | ┌───┐
//! |z00| |z01| Carry ...repeat for z01 to z44... |z45|
//! └───┘ └───┘ out └───┘
//! ```
//!
//! Then we can deduce some rules for the output of each gate type:
//!
//! 1. **XOR** If inputs are `x` and `y` then output must be another XOR gate
//! (except for inputs `x00` and `y00`) otherwise output must be `z`.
//! 2. **AND** Output must be an OR gate (except for inputs `x00` and `y00`).
//! 3. **OR** Output must be both AND and XOR gate, except for final carry
//! which must output to `z45`.
//!
//! We only need to find swapped outputs (not fix them) so the result is the labels of gates
//! that breaks the rules in alphabetical order.
use crate::util::hash::*;
use crate::util::iter::*;
use crate::util::parse::*;
use std::collections::VecDeque;
type Input<'a> = (&'a str, Vec<[&'a str; 5]>);
pub fn parse(input: &str) -> Input<'_> {
let (prefix, suffix) = input.split_once("\n\n").unwrap();
let gates = suffix.split_ascii_whitespace().chunk::<5>().collect();
(prefix, gates)
}
pub fn part1(input: &Input<'_>) -> u64 {
let (prefix, gates) = input;
// Using an array to store already computed values is much faster than a `HashMap`.
let mut todo: VecDeque<_> = gates.iter().copied().collect();
let mut cache = vec![u8::MAX; 1 << 15];
let mut result = 0;
// Convert each character to a 5 bit number from 0..31
// then each group of 3 to a 15 bit index from 0..32768.
let to_index = |s: &str| {
let b = s.as_bytes();
((b[0] as usize & 31) << 10) + ((b[1] as usize & 31) << 5) + (b[2] as usize & 31)
};
// Add input signals to cache.
for line in prefix.lines() {
let prefix = &line[..3];
let suffix = &line[5..];
cache[to_index(prefix)] = suffix.unsigned();
}
// If both inputs are available then add gate output to cache
// otherwise push back to end of queue for reprocessing later.
while let Some(gate @ [left, kind, right, _, to]) = todo.pop_front() {
let left = cache[to_index(left)];
let right = cache[to_index(right)];
if left == u8::MAX || right == u8::MAX {
todo.push_back(gate);
} else {
cache[to_index(to)] = match kind {
"AND" => left & right,
"OR" => left | right,
"XOR" => left ^ right,
_ => unreachable!(),
}
}
}
// Output 46 bit result.
for i in (to_index("z00")..to_index("z46")).rev() {
if cache[i] != u8::MAX {
result = (result << 1) | (cache[i] as u64);
}
}
result
}
pub fn part2(input: &Input<'_>) -> String {
let (_, gates) = input;
let mut output = FastSet::new();
let mut swapped = FastSet::new();
// Track the kind of gate that each wire label outputs to.
for &[left, kind, right, _, _] in gates {
output.insert((left, kind));
output.insert((right, kind));
}
for &[left, kind, right, _, to] in gates {
match kind {
"AND" => {
// Check that all AND gates point to an OR, except for first AND.
if left != "x00" && right != "x00" && !output.contains(&(to, "OR")) {
swapped.insert(to);
}
}
"OR" => {
// Check that only XOR gates point to output, except for last carry which is OR.
if to.starts_with('z') && to != "z45" {
swapped.insert(to);
}
// OR can never point to OR.
if output.contains(&(to, "OR")) {
swapped.insert(to);
}
}
"XOR" => {
if left.starts_with('x') || right.starts_with('x') {
// Check that first level XOR points to second level XOR, except for first XOR.
if left != "x00" && right != "x00" && !output.contains(&(to, "XOR")) {
swapped.insert(to);
}
} else {
// Second level XOR must point to output.
if !to.starts_with('z') {
swapped.insert(to);
}
}
}
_ => unreachable!(),
}
}
let mut result: Vec<_> = swapped.into_iter().collect();
result.sort_unstable();
result.join(",")
}