1use utils::prelude::*;
2
3#[derive(Clone, Debug)]
5pub struct Day08 {
6 part1: i32,
7 part2: i32,
8}
9
10#[derive(Copy, Clone, Debug)]
11enum Instruction {
12 Acc(i32),
13 Jmp(i32),
14 Nop(i32),
15}
16
17#[derive(Copy, Clone, Debug, Eq, PartialEq)]
18enum State {
19 Unvisited,
20 Loops,
21 Terminates,
22}
23
24impl Day08 {
25 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
26 if input.is_empty() {
27 return Err(InputError::new(input, 0, "expected instruction"));
28 }
29
30 let instructions = parser::parse_tree!(
31 ("acc ", v @ parser::i32()) => Instruction::Acc(v),
32 ("jmp ", v @ parser::i32()) => Instruction::Jmp(v),
33 ("nop ", v @ parser::i32()) => Instruction::Nop(v),
34 )
35 .parse_lines(input)?;
36
37 let len = instructions.len();
38 let mut outcomes = vec![State::Unvisited; len + 1];
39 let mut alternatives = Vec::with_capacity(len);
40 outcomes[len] = State::Terminates;
41
42 let (mut pc, mut part1) = (0, 0);
43 while outcomes.get(pc) == Some(&State::Unvisited) {
44 outcomes[pc] = State::Loops;
45
46 match instructions[pc] {
47 Instruction::Acc(value) => {
48 part1 += value;
49 pc += 1;
50 }
51 Instruction::Jmp(offset) => {
52 alternatives.push((pc + 1, part1));
53 pc = pc.wrapping_add_signed(offset as isize);
54 }
55 Instruction::Nop(offset) => {
56 alternatives.push((pc.wrapping_add_signed(offset as isize), part1));
57 pc += 1;
58 }
59 };
60 }
61
62 if pc >= len {
63 return Err(InputError::new(input, 0, "expected program to loop"));
64 }
65
66 let mut path = Vec::with_capacity(len);
67 let mut suffix_sums = vec![0; len + 1];
68 for &(alternate_pc, alternative_acc) in alternatives.iter().rev() {
69 if alternate_pc > len {
70 continue;
71 }
72
73 if outcomes[alternate_pc] == State::Unvisited {
74 path.clear();
75 pc = alternate_pc;
76 while pc < len && outcomes[pc] == State::Unvisited {
77 outcomes[pc] = State::Loops;
78 path.push(pc);
79 pc = match instructions[pc] {
80 Instruction::Acc(_) | Instruction::Nop(_) => pc + 1,
81 Instruction::Jmp(offset) => {
82 pc.checked_add_signed(offset as isize).unwrap_or(usize::MAX)
83 }
84 };
85 }
86
87 if pc <= len && outcomes[pc] == State::Terminates {
88 let mut acc = suffix_sums[pc];
89 for &pc in path.iter().rev() {
90 if let Instruction::Acc(value) = instructions[pc] {
91 acc += value;
92 }
93 suffix_sums[pc] = acc;
94 outcomes[pc] = State::Terminates;
95 }
96 }
97 }
98
99 if outcomes[alternate_pc] == State::Terminates {
100 return Ok(Self {
101 part1,
102 part2: alternative_acc + suffix_sums[alternate_pc],
103 });
104 }
105 }
106
107 Err(InputError::new(
108 input,
109 0,
110 "expected one corrupted instruction",
111 ))
112 }
113
114 #[must_use]
115 pub fn part1(&self) -> i32 {
116 self.part1
117 }
118
119 #[must_use]
120 pub fn part2(&self) -> i32 {
121 self.part2
122 }
123}
124
125examples!(Day08 -> (i32, i32) [
126 {file: "day08_example0.txt", part1: 5, part2: 8},
127]);