Skip to content

Commit e06b843

Browse files
committed
Document alternative Dragon Curve parity calculation
The existing O(log n) solution solves in under 1µs, so there is not really much to be shaved by swapping to an O(1) computation per bit. But it is still a fun read, so worth documenting for anyone else using this repository as a reference point. It is also worth adding a unit test of the example given in part 1.
1 parent 6fd8ddf commit e06b843

File tree

2 files changed

+24
-5
lines changed

2 files changed

+24
-5
lines changed

src/year2016/day16.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@
3333
//! Now for the really neat part. We can recursively find the number of ones in `y` by repeating
3434
//! the same process by setting the new `length` to `next`. We keep recursing until the length
3535
//! is less 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."
3644
use crate::util::parse::*;
3745

3846
/// Build a prefix sum of the number of ones at each length in the pattern
@@ -51,18 +59,22 @@ pub fn parse(input: &str) -> Vec<usize> {
5159

5260
/// 272 is 17 * 2⁴
5361
pub fn part1(input: &[usize]) -> String {
54-
checksum(input, 1 << 4)
62+
checksum(input, 272)
5563
}
5664

5765
/// 35651584 is 17 * 2²¹
5866
pub fn part2(input: &[usize]) -> String {
59-
checksum(input, 1 << 21)
67+
checksum(input, 35651584)
6068
}
6169

6270
/// Collect the ones count at each `step_size` then subtract in pairs to calculate the number of
6371
/// ones in each interval to give the checksum.
64-
fn checksum(input: &[usize], step_size: usize) -> String {
65-
let counts: Vec<_> = (0..18).map(|i| count(input, i * step_size)).collect();
72+
pub 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();
6678
counts.windows(2).map(|w| if (w[1] - w[0]) % 2 == 0 { '1' } else { '0' }).collect()
6779
}
6880

tests/year2016/day16.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1+
use aoc::year2016::day16::*;
2+
3+
// From the puzzle, valid for part 1.
4+
const EXAMPLE: &str = "10000";
5+
16
#[test]
27
fn part1_test() {
3-
// No example data
8+
// 20 is 5 * 2²
9+
let input = parse(EXAMPLE);
10+
assert_eq!(checksum(&input, 20), "01100");
411
}
512

613
#[test]

0 commit comments

Comments
 (0)