Skip to content

Commit 6812cd7

Browse files
committed
Year 2016 Day 16
1 parent 7e9139d commit 6812cd7

File tree

8 files changed

+116
-0
lines changed

8 files changed

+116
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ pie
258258
| 13 | [A Maze of Twisty Little Cubicles](https://adventofcode.com/2016/day/13) | [Source](src/year2016/day13.rs) | 4 |
259259
| 14 | [One-Time Pad](https://adventofcode.com/2016/day/14) | [Source](src/year2016/day14.rs) | 439000 |
260260
| 15 | [Timing is Everything](https://adventofcode.com/2016/day/15) | [Source](src/year2016/day15.rs) | 1 |
261+
| 16 | [Dragon Checksum](https://adventofcode.com/2016/day/16) | [Source](src/year2016/day16.rs) | 1 |
261262

262263
## 2015
263264

benches/benchmark.rs

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ mod year2016 {
7979
benchmark!(year2016, day13);
8080
benchmark!(year2016, day14);
8181
benchmark!(year2016, day15);
82+
benchmark!(year2016, day16);
8283
}
8384

8485
mod year2019 {

input/year2016/day16.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
01111001100111011

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ pub mod year2016 {
206206
pub mod day13;
207207
pub mod day14;
208208
pub mod day15;
209+
pub mod day16;
209210
}
210211

211212
/// # Rescue Santa from deep space with a solar system adventure.

src/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ fn all_solutions() -> Vec<Solution> {
117117
solution!(year2016, day13),
118118
solution!(year2016, day14),
119119
solution!(year2016, day15),
120+
solution!(year2016, day16),
120121
// 2019
121122
solution!(year2019, day01),
122123
solution!(year2019, day02),

src/year2016/day16.rs

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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 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+
//! * Caculate 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 the size of the inital input and we can lookup the final count from the prefix sum.
36+
use crate::util::parse::*;
37+
38+
/// Build a prefix sum of the number of ones at each length in the pattern
39+
/// including zero at the start.
40+
pub fn parse(input: &str) -> Vec<usize> {
41+
let mut sum = 0;
42+
let mut ones = vec![0];
43+
44+
for b in input.trim().bytes() {
45+
sum += b.to_decimal() as usize;
46+
ones.push(sum);
47+
}
48+
49+
ones
50+
}
51+
52+
/// 272 is 17 * 2⁴
53+
pub fn part1(input: &[usize]) -> String {
54+
checksum(input, 1 << 4)
55+
}
56+
57+
/// 35651584 is 17 * 2²¹
58+
pub fn part2(input: &[usize]) -> String {
59+
checksum(input, 1 << 21)
60+
}
61+
62+
/// Collect the ones count at each `step_size` then subtract in pairs to calculate the number of
63+
/// ones in each interval to give the checksum.
64+
fn checksum(input: &[usize], step_size: usize) -> String {
65+
(0..18)
66+
.map(|i| count(input, i * step_size))
67+
.collect::<Vec<_>>()
68+
.windows(2)
69+
.map(|w| if (w[1] - w[0]) % 2 == 0 { '1' } else { '0' })
70+
.collect()
71+
}
72+
73+
/// Counts the number of ones from the start to the index (inclusive).
74+
fn count(ones: &[usize], mut length: usize) -> usize {
75+
let mut half = ones.len() - 1;
76+
let mut full = 2 * half + 1;
77+
78+
// Find the smallest pattern size such that the index is on the right hand side
79+
// (greater than or to) the middle `0` character.
80+
while full < length {
81+
half = full;
82+
full = 2 * half + 1;
83+
}
84+
85+
let mut result = 0;
86+
87+
while length >= ones.len() {
88+
// Shrink the pattern size until the index is on the right side once more.
89+
while length <= half {
90+
half /= 2;
91+
full /= 2;
92+
}
93+
94+
// "Reflect" the index then add the extra number of ones to the running total.
95+
let next = full - length;
96+
result += half - next;
97+
length = next;
98+
}
99+
100+
result + ones[length]
101+
}

tests/test.rs

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ mod year2016 {
7272
mod day13_test;
7373
mod day14_test;
7474
mod day15_test;
75+
mod day16_test;
7576
}
7677

7778
mod year2019 {

tests/year2016/day16_test.rs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#[test]
2+
fn part1_test() {
3+
// No example data
4+
}
5+
6+
#[test]
7+
fn part2_test() {
8+
// No example data
9+
}

0 commit comments

Comments
 (0)