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
//! # Handheld Halting
//!
//! A brute force implementation that changes every `Jmp` or `Nop` in the input one at at time then
//! tests the result would have `O(n²)` complexity for part two.
//!
//! We can solve part two in `O(n)` complexity, executing each instruction at most twice. We start
//! the same as the brute force solution by stepping through the input speculatively changing each
//! `Nop` to a `Jmp` or vice-versa, then executing the remaining program from that point and
//! checking if it finishes.
//!
//! The trick is to re-use the `visited` vec that stores if we have executed an instruction before.
//! As each previous failed code path will have executed some instructions, trying to execute an
//! instruction twice means that we know immediately we are on a bad path and can stop.
use crate::util::iter::*;
use crate::util::parse::*;

pub enum Instruction {
    Acc(i16),
    Jmp(i16),
    Nop(i16),
}

impl Instruction {
    fn from([a, b]: [&str; 2]) -> Instruction {
        let amount = b.signed();
        match a {
            "acc" => Instruction::Acc(amount),
            "jmp" => Instruction::Jmp(amount),
            "nop" => Instruction::Nop(amount),
            _ => unreachable!(),
        }
    }
}

enum State {
    Infinite(i32),
    Halted(i32),
}

pub fn parse(input: &str) -> Vec<Instruction> {
    input.split_ascii_whitespace().chunk::<2>().map(Instruction::from).collect()
}

pub fn part1(input: &[Instruction]) -> i32 {
    let mut visited = vec![false; input.len()];

    match execute(input, 0, 0, &mut visited) {
        State::Infinite(acc) => acc,
        State::Halted(_) => unreachable!(),
    }
}

pub fn part2(input: &[Instruction]) -> i32 {
    let mut pc = 0;
    let mut acc = 0;
    let visited = &mut vec![false; input.len()];

    loop {
        match input[pc] {
            Instruction::Acc(arg) => {
                pc += 1;
                acc += arg as i32;
            }
            Instruction::Jmp(arg) => {
                let speculative = pc + 1;
                match execute(input, speculative, acc, visited) {
                    State::Infinite(_) => pc = pc.wrapping_add(arg as usize),
                    State::Halted(acc) => break acc,
                }
            }
            Instruction::Nop(arg) => {
                let speculative = pc.wrapping_add(arg as usize);
                match execute(input, speculative, acc, visited) {
                    State::Infinite(_) => pc += 1,
                    State::Halted(acc) => break acc,
                }
            }
        }
    }
}

fn execute(input: &[Instruction], mut pc: usize, mut acc: i32, visited: &mut [bool]) -> State {
    loop {
        if pc >= input.len() {
            break State::Halted(acc);
        } else if visited[pc] {
            break State::Infinite(acc);
        }

        visited[pc] = true;

        match input[pc] {
            Instruction::Acc(arg) => {
                pc += 1;
                acc += arg as i32;
            }
            Instruction::Jmp(arg) => {
                pc = pc.wrapping_add(arg as usize);
            }
            Instruction::Nop(_) => {
                pc += 1;
            }
        }
    }
}