aoc/year2016/
day16.rs

1//! # Dragon Checksum
2//!
3//! We solve efficiently with a key insight that the checksum is simply the
4//! [odd parity bit](https://en.wikipedia.org/wiki/Parity_bit) for each block. If the total number
5//! of ones is even then the result is one, if the total is odd then the result is zero.
6//!
7//! This means that only the *total number of ones is important* not the pattern itself. Each
8//! checksum bit is computed over the largest power of two divisible into the output size. For part
9//! one this is 2⁴ or 16 and for part two this is 2²¹ or 2097152. If we can calculate the number of
10//! ones for any arbitrary length then we can find the number at the start and end of each block,
11//! subtract from each other to get the total in the range then find the checksum bit.
12//!
13//! We find the number of ones for a pattern of length `n` in `log(n)` complexity as follows:
14//! * Start with a known pattern `abcde` and let the reversed bit inverse of this pattern be
15//!   `EDCBA`.
16//! * Calculate the [prefix sum](https://en.wikipedia.org/wiki/Prefix_sum) of the known sequence.
17//! * If the requested length is within the known sequence (in this example from 0 to 5 inclusive)
18//!   then we're done, return the number of ones directly.
19//! * Else after one repetition this becomes `abcde0EDCBA`.
20//! * If the length is at or to the right of the middle `0`,
21//!   for example `length` is 8 then the number of ones is:
22//!    * Let `half` = 5 the length of the left hand known sequence.
23//!    * Let `full` = 11 the length of the entire sequence.
24//!    * Ones in `abcde` => x
25//!    * Ones in `EDCBA` => the number of zeroes in `abcde`
26//!      => 5 - x => half - x
27//!    * Ones in `abc` => y
28//!    * Ones in `CBA` => the number of zeroes in `abc`
29//!      => 3 - y => 11 - 8 - y => full - length - y => next - y
30//!    * The total number of ones in `abcde0ED` is
31//!      x + (half - x) - (next - y) => half - next + y
32//!
33//! Now for the really neat part. We can recursively find the number of ones in `y` by repeating
34//! the same process by setting the new `length` to `next`. We keep recursing until the length
35//! is less than the size of the initial input and we can lookup the final count from the prefix sum.
36//!
37//! Note that it is also possible to compute the parity of any prefix of the Dragon Curve in
38//! O(1) time; the formula is available on [OEIS A255070](https://oeis.org/A255070), and there
39//! are a [couple](https://www.reddit.com/r/adventofcode/comments/5ititq/2016_day_16_c_how_to_tame_your_dragon_in_under_a/)
40//! of [posts](https://www.reddit.com/r/adventofcode/comments/1r642oc/2016_day_16_in_review_dragon_checksum/)
41//! showing how to utilize that approach.  However, the logarithmic solution shown here is
42//! fast enough to not need to worry about askalski's comment "I have no idea why it works,
43//! only that it does work."
44use crate::util::parse::*;
45
46/// Build a prefix sum of the number of ones at each length in the pattern
47/// including zero at the start.
48pub fn parse(input: &str) -> Vec<usize> {
49    let mut sum = 0;
50    let mut ones = vec![0];
51
52    for b in input.trim().bytes() {
53        sum += b.to_decimal() as usize;
54        ones.push(sum);
55    }
56
57    ones
58}
59
60/// 272 is 17 * 2⁴
61pub fn part1(input: &[usize]) -> String {
62    checksum(input, 272)
63}
64
65/// 35651584 is 17 * 2²¹
66pub fn part2(input: &[usize]) -> String {
67    checksum(input, 35651584)
68}
69
70/// Collect the ones count at each `step_size` then subtract in pairs to calculate the number of
71/// ones in each interval to give the checksum.
72pub fn checksum(input: &[usize], disk_size: usize) -> String {
73    // Determine how many blocks and how big each one is, by lowest 1-bit in disk_size
74    let step_size = disk_size & (!disk_size + 1);
75    let blocks = disk_size / step_size;
76
77    let counts: Vec<_> = (0..blocks + 1).map(|i| count(input, i * step_size)).collect();
78    counts.windows(2).map(|w| if (w[1] - w[0]) % 2 == 0 { '1' } else { '0' }).collect()
79}
80
81/// Counts the number of ones from the start to the index (inclusive).
82fn count(ones: &[usize], mut length: usize) -> usize {
83    let mut half = ones.len() - 1;
84    let mut full = 2 * half + 1;
85
86    // Find the smallest pattern size such that the index is on the right hand side
87    // (greater than or to) the middle `0` character.
88    while full < length {
89        half = full;
90        full = 2 * half + 1;
91    }
92
93    let mut result = 0;
94
95    while length >= ones.len() {
96        // Shrink the pattern size until the index is on the right side once more.
97        while length <= half {
98            half /= 2;
99            full /= 2;
100        }
101
102        // "Reflect" the index then add the extra number of ones to the running total.
103        let next = full - length;
104        result += half - next;
105        length = next;
106    }
107
108    result + ones[length]
109}