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
use utils::prelude::*;

/// Counting steps through a maze of jump instructions.
#[derive(Clone, Debug)]
pub struct Day05 {
    jumps: Vec<i32>,
}

type Compressed = usize;
const BITS: usize = Compressed::BITS as usize;

impl Day05 {
    pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
        Ok(Self {
            jumps: parser::i32().parse_lines(input)?,
        })
    }

    #[must_use]
    pub fn part1(&self) -> u64 {
        let mut jumps = self.jumps.clone();
        let mut steps = 0;
        let mut pc = 0;

        while pc < jumps.len() {
            let offset = jumps[pc];
            jumps[pc] += 1;
            pc = pc.wrapping_add_signed(offset as isize);
            steps += 1;
        }

        steps
    }

    #[must_use]
    pub fn part2(&self) -> u64 {
        let mut jumps = self.jumps.clone();
        let mut steps = 0;
        let mut pc = 0;

        // After each jump instruction is run enough times the offset oscillates back and forth
        // between 2 and 3. Once this happens, represent the jump as a single bit in a compressed
        // bit mask, which allows processing multiple jumps at once without each one requiring a
        // random memory read.
        let mut threes: Vec<Compressed> = vec![0; jumps.len().next_multiple_of(BITS) / BITS];
        // boundary represents the point where all prior jumps have stabilized on oscillating
        // between 2 and 3
        let mut boundary = 0;

        while pc < jumps.len() {
            let offset = jumps[pc];
            jumps[pc] += if offset >= 3 { -1 } else { 1 };

            if pc == boundary && (jumps[pc] == 2 || jumps[pc] == 3) {
                // Next jump after the boundary stabilized on 2/3
                boundary += 1;

                let element_index = pc / BITS;
                let bit_index = pc % BITS;
                threes[element_index] |= ((jumps[pc] & 1) as Compressed) << bit_index;
            }

            pc = pc.wrapping_add_signed(offset as isize);
            steps += 1;

            while pc < boundary {
                // While inside the boundary loop over each compressed element and handle the jumps
                // in bulk
                let element_index = pc / BITS;
                let mut element = threes[element_index];

                let bit_index = pc % BITS;
                let mut bit = 1 << bit_index;

                let max = boundary.min((element_index + 1) * BITS);
                while pc < max {
                    let offset = 2 + usize::from(element & bit != 0);
                    element ^= bit;
                    bit <<= offset;
                    pc += offset;
                    steps += 1;
                }

                threes[element_index] = element;
            }
        }

        steps
    }
}

examples!(Day05 -> (u64, u64) [
    {input: "0\n3\n0\n1\n-3", part1: 5, part2: 10},
]);