Skip to content

Commit 7a8885e

Browse files
committed
2017 day 18
1 parent d32c7eb commit 7a8885e

File tree

2 files changed

+248
-0
lines changed

2 files changed

+248
-0
lines changed

crates/year2017/src/day18.rs

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
use std::collections::VecDeque;
2+
use utils::prelude::*;
3+
4+
/// Interpreting assembly, with message passing between instances.
5+
#[derive(Clone, Debug)]
6+
pub struct Day18 {
7+
instructions: Vec<Instruction>,
8+
}
9+
10+
// Use an enum for registers to avoid bounds checks when accessing the register array, slightly
11+
// improving performance
12+
#[derive(Copy, Clone, Debug, PartialEq)]
13+
enum Register {
14+
A,
15+
B,
16+
C,
17+
D,
18+
F,
19+
I,
20+
P,
21+
}
22+
23+
// Use separate enum variants for register and constant operands to minimize branching in the
24+
// interpreter loop, again slightly improving performance
25+
#[derive(Copy, Clone, Debug, PartialEq)]
26+
enum Instruction {
27+
Snd(Register),
28+
SndN(i32), // Used in second example
29+
Set(Register, Register),
30+
SetN(Register, i32),
31+
Add(Register, Register),
32+
AddN(Register, i32),
33+
Mul(Register, Register), // Used in first example
34+
MulN(Register, i32),
35+
Mod(Register, Register),
36+
ModN(Register, i32),
37+
Rcv(Register),
38+
Jgz(Register, Register),
39+
JgzN(Register, i32),
40+
Jmp(i32), // Used for jgz $n where $n > 0
41+
Noop(), // Used for jgz $n where $n <= 0
42+
}
43+
44+
#[derive(Debug)]
45+
struct Program<'a> {
46+
instructions: &'a [Instruction],
47+
pc: usize,
48+
reg: [i64; 7],
49+
}
50+
51+
impl Day18 {
52+
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
53+
let register = parser::literal_map!(
54+
"a" => Register::A,
55+
"b" => Register::B,
56+
"c" => Register::C,
57+
"d" => Register::D,
58+
"f" => Register::F,
59+
"i" => Register::I,
60+
"p" => Register::P,
61+
);
62+
63+
let instruction = parser::parse_tree!(
64+
("snd ") =>> {
65+
(r @ register) => Instruction::Snd(r),
66+
(v @ parser::i32()) => Instruction::SndN(v),
67+
},
68+
("set ", r @ register, b' ') =>> {
69+
(r2 @ register) => Instruction::Set(r, r2),
70+
(v @ parser::i32()) => Instruction::SetN(r, v),
71+
},
72+
("add ", r @ register, b' ') =>> {
73+
(r2 @ register) => Instruction::Add(r, r2),
74+
(v @ parser::i32()) => Instruction::AddN(r, v),
75+
},
76+
("mul ", r @ register, b' ') =>> {
77+
(r2 @ register) => Instruction::Mul(r, r2),
78+
(v @ parser::i32()) => Instruction::MulN(r, v),
79+
},
80+
("mod ", r @ register, b' ') =>> {
81+
(r2 @ register) => Instruction::Mod(r, r2),
82+
(v @ parser::i32()) => Instruction::ModN(r, v),
83+
},
84+
("rcv ", r @ register) => Instruction::Rcv(r),
85+
("jgz ") =>> {
86+
(r @ register, b' ') =>> {
87+
(r2 @ register) => Instruction::Jgz(r, r2),
88+
(v @ parser::i32()) => Instruction::JgzN(r, v),
89+
},
90+
(x @ parser::i32(), b' ', v @ parser::i32()) => {
91+
if x > 0 {
92+
Instruction::Jmp(v)
93+
} else {
94+
Instruction::Noop()
95+
}
96+
},
97+
},
98+
);
99+
100+
Ok(Self {
101+
instructions: instruction.parse_lines(input)?,
102+
})
103+
}
104+
105+
#[must_use]
106+
pub fn part1(&self) -> i64 {
107+
let mut program = Program::from_instructions(&self.instructions);
108+
let mut last = None;
109+
110+
program.run(
111+
|v| last = Some(v),
112+
|v| {
113+
if v == 0 {
114+
// If the register is zero, do nothing/set the register back to zero
115+
Some(0)
116+
} else {
117+
// If the register is not zero, this is the first recovered frequency, so return
118+
// None to halt program execution
119+
None
120+
}
121+
},
122+
);
123+
124+
last.unwrap()
125+
}
126+
127+
#[must_use]
128+
pub fn part2(&self) -> u32 {
129+
let mut program0 = Program::from_instructions(&self.instructions);
130+
let mut inbound0 = VecDeque::new();
131+
132+
let mut program1 = Program::from_instructions(&self.instructions);
133+
let mut inbound1 = VecDeque::new();
134+
let mut sent = 0;
135+
program1.reg[Register::P as usize] = 1;
136+
137+
// Run both programs until they both fail to make progress. Use | instead of || so each
138+
// iteration always attempts to run both
139+
while program0.run(
140+
|v| {
141+
// Send to program 1
142+
inbound1.push_back(v)
143+
},
144+
|_| {
145+
// Receive from program 1
146+
inbound0.pop_front()
147+
},
148+
) | program1.run(
149+
|v| {
150+
// Send to program 0, keeping track of how many values are sent
151+
sent += 1;
152+
inbound0.push_back(v)
153+
},
154+
|_| {
155+
// Receive from program 0
156+
inbound1.pop_front()
157+
},
158+
) {}
159+
160+
sent
161+
}
162+
}
163+
164+
impl<'a> Program<'a> {
165+
fn from_instructions(instructions: &'a [Instruction]) -> Self {
166+
Program {
167+
instructions,
168+
pc: 0,
169+
reg: [0; 7],
170+
}
171+
}
172+
173+
fn run(&mut self, mut snd: impl FnMut(i64), mut rcv: impl FnMut(i64) -> Option<i64>) -> bool {
174+
let mut progress = false;
175+
176+
while let Some(&instruction) = self.instructions.get(self.pc) {
177+
match instruction {
178+
Instruction::Snd(r) => snd(self.reg[r as usize]),
179+
Instruction::SndN(v) => snd(v as i64),
180+
Instruction::Set(r, r2) => self.reg[r as usize] = self.reg[r2 as usize],
181+
Instruction::SetN(r, v) => self.reg[r as usize] = v as i64,
182+
Instruction::Add(r, r2) => self.reg[r as usize] += self.reg[r2 as usize],
183+
Instruction::AddN(r, v) => self.reg[r as usize] += v as i64,
184+
Instruction::Mul(r, r2) => self.reg[r as usize] *= self.reg[r2 as usize],
185+
Instruction::MulN(r, v) => self.reg[r as usize] *= v as i64,
186+
Instruction::Mod(r, r2) => {
187+
self.reg[r as usize] = self.reg[r as usize].rem_euclid(self.reg[r2 as usize])
188+
}
189+
Instruction::ModN(r, v) => {
190+
self.reg[r as usize] = self.reg[r as usize].rem_euclid(v as i64)
191+
}
192+
Instruction::Rcv(r) => {
193+
if let Some(value) = rcv(self.reg[r as usize]) {
194+
self.reg[r as usize] = value;
195+
} else {
196+
break;
197+
}
198+
}
199+
Instruction::Jgz(r, ro) => {
200+
if self.reg[r as usize] > 0 {
201+
self.pc = self.pc.wrapping_add_signed(self.reg[ro as usize] as isize) - 1;
202+
}
203+
}
204+
Instruction::JgzN(r, v) => {
205+
if self.reg[r as usize] > 0 {
206+
self.pc = self.pc.wrapping_add_signed(v as isize) - 1;
207+
}
208+
}
209+
Instruction::Jmp(v) => {
210+
self.pc = self.pc.wrapping_add(v as usize) - 1;
211+
}
212+
Instruction::Noop() => {}
213+
}
214+
215+
self.pc += 1;
216+
progress = true;
217+
}
218+
219+
progress
220+
}
221+
}
222+
223+
examples!(Day18 -> (i64, u32) [
224+
{
225+
input: "set a 1\n\
226+
add a 2\n\
227+
mul a a\n\
228+
mod a 5\n\
229+
snd a\n\
230+
set a 0\n\
231+
rcv a\n\
232+
jgz a -1\n\
233+
set a 1\n\
234+
jgz a -2",
235+
part1: 4,
236+
},
237+
{
238+
input: "snd 1\n\
239+
snd 2\n\
240+
snd p\n\
241+
rcv a\n\
242+
rcv b\n\
243+
rcv c\n\
244+
rcv d",
245+
part2: 3,
246+
},
247+
]);

crates/year2017/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ utils::year!(2017 => year2017, ${
2121
15 => day15::Day15,
2222
16 => day16::Day16,
2323
17 => day17::Day17,
24+
18 => day18::Day18,
2425
});

0 commit comments

Comments
 (0)