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