-
Notifications
You must be signed in to change notification settings - Fork 15
/
day16.rs
114 lines (100 loc) · 4.02 KB
/
day16.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
//! # Chronal Classification
//!
//! There are only 16 opcodes so we can use bitwise logic to efficiently perform the set operations
//! that uniquely determine the mapping of each opcode to instruction.
//!
//! First we create a bitmask for each instruction block in the first half of the input
//! with a `1` for each potential instruction. For example:
//!
//! ```none
//! Before: [3, 2, 1, 1]
//! 9 2 1 2
//! After: [3, 2, 2, 1]
//!
//! Possible instructions: mulr, addi, seti
//! Binary Mask: 0000001000000110
//! ```
//!
//! For part one the [`count_ones`] intrinsic computes the size of each set.
//!
//! For part two we need to determine the mapping of the unknown codes. First we reduce each
//! unknown to a single set by taking the intersection of all examples. Then similar to
//! solving simultaneous equation, we eliminate one unknown at a time, removing it from the other
//! possibilities. This causes a domino effect, continuing until all unknowns are resolved.
//!
//! [`count_ones`]: u32::count_ones
use crate::util::iter::*;
use crate::util::parse::*;
pub struct Input {
samples: Vec<(usize, u32)>,
program: Vec<[usize; 4]>,
}
pub fn parse(input: &str) -> Input {
let (first, second) = input.rsplit_once("\n\n").unwrap();
let samples = first
.iter_unsigned()
.chunk::<4>()
.chunk::<3>()
.map(|[before, instruction, after]| {
let [unknown, a, b, c] = instruction;
let mut mask = 0;
// Build set of possible opcodes
for opcode in 0..16 {
if cpu(opcode, a, b, &before) == after[c] {
mask |= 1 << opcode;
}
}
(unknown, mask)
})
.collect();
let program = second.iter_unsigned().chunk::<4>().collect();
Input { samples, program }
}
pub fn part1(input: &Input) -> usize {
input.samples.iter().filter(|(_, mask)| mask.count_ones() >= 3).count()
}
pub fn part2(input: &Input) -> usize {
// Take intersection of samples, reducing each unknown opcode to a single set of possibilities.
let mut masks = [0xffff; 16];
for &(unknown, mask) in &input.samples {
masks[unknown] &= mask;
}
// To uniquely determine the mapping, there must be at least 1 opcode during each iteration
// that only has one possibility.
let mut convert = [0; 16];
while let Some(index) = masks.iter().position(|m| m.count_ones() == 1) {
let mask = masks[index];
// This opcode has only 1 possible mapping, so remove possbility from other opcodes.
masks.iter_mut().for_each(|m| *m &= !mask);
// Add mapping.
convert[index] = mask.trailing_zeros() as usize;
}
// Run the program now that we know the mapping.
let mut register = [0; 4];
for &[unknown, a, b, c] in &input.program {
let opcode = convert[unknown];
register[c] = cpu(opcode, a, b, ®ister);
}
register[0]
}
fn cpu(opcode: usize, a: usize, b: usize, register: &[usize; 4]) -> usize {
match opcode {
0 => register[a] + register[b], // addr
1 => register[a] + b, // addi
2 => register[a] * register[b], // mulr
3 => register[a] * b, // muli
4 => register[a] & register[b], // banr
5 => register[a] & b, // bani
6 => register[a] | register[b], // borr
7 => register[a] | b, // bori
8 => register[a], // setr
9 => a, // seti
10 => (a > register[b]) as usize, // gtir
11 => (register[a] > b) as usize, // gtri
12 => (register[a] > register[b]) as usize, // gtrr
13 => (a == register[b]) as usize, // eqir
14 => (register[a] == b) as usize, // eqri
15 => (register[a] == register[b]) as usize, // eqrr
_ => unreachable!(),
}
}