year2016/
day16.rs

1use utils::prelude::*;
2
3/// Calculating a dragon curve checksum.
4///
5/// The checksum is based on the parity of each chunk, and each chunk is the length of the largest
6/// power of 2 that divides the checksum's length.
7///
8/// Using the dragon bit parity function from
9/// [/u/askalski's post "How to tame your dragon in under a millisecond"](https://www.reddit.com/r/adventofcode/comments/5ititq/2016_day_16_c_how_to_tame_your_dragon_in_under_a/)
10/// we can calculate the parity of any length of the sequence:
11/// - Calculate how many full repeats of the pattern
12///   \[Original data]\[Dragon bit]\[Reversed & inverted data]\[Dragon bit] there are, and how
13///   far through the pattern the final truncated repeat was.
14/// - Calculate the parity of the full repeats of the original and the reversed & inverted data.
15/// - Calculate the parity of the original data in the truncated repeat, if any.
16/// - Calculate the parity of the reversed & inverted data in the truncated repeat, if any.
17/// - Calculate the parity of all the dragon bits.
18/// - XOR the four calculated parity values together.
19///
20/// This allows computing the parity from the start of the sequence to the end of each chunk, which
21/// then can be used to find each chunk's parity by XORing each parity with the previous one.
22#[derive(Clone, Debug)]
23pub struct Day16<'a> {
24    input: &'a str,
25    input_type: InputType,
26}
27
28impl<'a> Day16<'a> {
29    pub fn new(input: &'a str, input_type: InputType) -> Result<Self, InputError> {
30        if let Some(index) = input.find(|c| c != '0' && c != '1') {
31            return Err(InputError::new(input, index, "expected 0 or 1"));
32        }
33
34        Ok(Self { input, input_type })
35    }
36
37    #[must_use]
38    pub fn part1(&self) -> String {
39        self.checksum(match self.input_type {
40            InputType::Example => 20,
41            InputType::Real => 272,
42        })
43    }
44
45    #[must_use]
46    pub fn part2(&self) -> String {
47        self.checksum(35651584)
48    }
49
50    fn checksum(&self, length: u32) -> String {
51        let chunk_length = 1 << length.trailing_zeros();
52        let input_length = self.input.len() as u32;
53
54        // Expanded data repeats the following pattern:
55        // [Original data][Dragon bit][Reversed & inverted data][Dragon bit]
56        let pattern_length = 2 * (input_length + 1);
57
58        let mut previous = false;
59        let mut output = String::with_capacity((length / chunk_length) as usize);
60        for i in (chunk_length..=length).step_by(chunk_length as usize) {
61            let mut parity = false;
62
63            // Work out how many complete pattern repeats there are up until this point, and how
64            // far through the pattern the final truncated repeat is
65            let complete_repeats = i / pattern_length;
66            let mut truncated = i % pattern_length;
67            let mut dragon_bits = 2 * complete_repeats;
68
69            // Calculate parity of all the repeated original + reversed & inverted sections
70            parity ^= (complete_repeats * input_length) % 2 == 1;
71
72            // Calculate parity of original data in the final truncated repeat
73            if truncated > 0 {
74                let original_length = truncated.min(input_length);
75                let ones = self
76                    .input
77                    .bytes()
78                    .take(original_length as usize)
79                    .filter(|&b| b == b'1')
80                    .count();
81                parity ^= ones % 2 == 1;
82                truncated -= original_length;
83            }
84
85            // Extra dragon bit in final truncated repeat
86            if truncated > 0 {
87                dragon_bits += 1;
88                truncated -= 1;
89            }
90
91            // Calculate parity of the reversed & inverted data in the final truncated repeat
92            if truncated > 0 {
93                let mirrored_inverted_length = truncated.min(input_length);
94                let ones = self
95                    .input
96                    .bytes()
97                    .rev()
98                    .take(mirrored_inverted_length as usize)
99                    .filter(|&b| b == b'0')
100                    .count();
101                parity ^= ones % 2 == 1;
102            }
103
104            // Truncated repeat can't have a final dragon bit (otherwise it wouldn't be incomplete)
105
106            // Calculate parity of all the dragon bits
107            parity ^= Self::dragon_parity(dragon_bits);
108
109            if parity ^ previous {
110                output.push('0');
111            } else {
112                output.push('1');
113            }
114            previous = parity;
115        }
116
117        output
118    }
119
120    /// See https://www.reddit.com/r/adventofcode/comments/5ititq/2016_day_16_c_how_to_tame_your_dragon_in_under_a/
121    fn dragon_parity(n: u32) -> bool {
122        let gray = n ^ (n >> 1);
123        ((n & gray).count_ones() ^ gray) & 1 != 0
124    }
125}
126
127examples!(Day16<'_> -> (&'static str, &'static str) [
128    {input: "10000", part1: "01100"},
129]);