Skip to content

Commit a0cc05f

Browse files
authored
Document alternative Dragon Curve parity calculation (#42)
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 a0cc05f

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)