Skip to content

Commit

Permalink
Year 2019 Day 16
Browse files Browse the repository at this point in the history
  • Loading branch information
maneatingape committed Sep 9, 2023
1 parent 056f4a9 commit 569ef15
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ pie
| 13 | [Care Package](https://adventofcode.com/2019/day/13) | [Source](src/year2019/day13.rs) | 3492 |
| 14 | [Space Stoichiometry](https://adventofcode.com/2019/day/14) | [Source](src/year2019/day14.rs) | 17 |
| 15 | [Oxygen System](https://adventofcode.com/2019/day/15) | [Source](src/year2019/day15.rs) | 442 |
| 16 | [Flawed Frequency Transmission](https://adventofcode.com/2019/day/16) | [Source](src/year2019/day16.rs) | 30000 |

## 2015

Expand Down
1 change: 1 addition & 0 deletions benches/benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ mod year2019 {
benchmark!(year2019, day13);
benchmark!(year2019, day14);
benchmark!(year2019, day15);
benchmark!(year2019, day16);
}

mod year2020 {
Expand Down
1 change: 1 addition & 0 deletions input/year2019/day16.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
59754835304279095723667830764559994207668723615273907123832849523285892960990393495763064170399328763959561728553125232713663009161639789035331160605704223863754174835946381029543455581717775283582638013183215312822018348826709095340993876483418084566769957325454646682224309983510781204738662326823284208246064957584474684120465225052336374823382738788573365821572559301715471129142028462682986045997614184200503304763967364026464055684787169501819241361777789595715281841253470186857857671012867285957360755646446993278909888646724963166642032217322712337954157163771552371824741783496515778370667935574438315692768492954716331430001072240959235708
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ pub mod year2019 {
pub mod day13;
pub mod day14;
pub mod day15;
pub mod day16;
}

/// # What could go wrong trying to enjoy a well deserved vacation?
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ fn all_solutions() -> Vec<Solution> {
solution!(year2019, day13),
solution!(year2019, day14),
solution!(year2019, day15),
solution!(year2019, day16),
// 2020
solution!(year2020, day01),
solution!(year2020, day02),
Expand Down
155 changes: 155 additions & 0 deletions src/year2019/day16.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
//! # Flawed Frequency Transmission
//!
//! ## Part One
//!
//! For each phase we split the input into two halves. The first half is processed using the
//! pattern "stretched" for the current phase. For the second half we notice that
//! the coefficients before are always zero and after always one, so the result can only depend
//! on later digits. Using the first example:
//!
//! ```none
//! 5*1 + 6*1 + 7*1 + 8*1
//! 5*0 + 6*1 + 7*1 + 8*1
//! 5*0 + 6*0 + 7*1 + 8*1
//! 5*0 + 6*0 + 7*0 + 8*1
//! ```
//!
//! This means that each digit is the sum of itself and subsequent digits and can be computed
//! using a reverse rolling [prefix sum].
//!
//! ## Part Two
//!
//! If the index from the first 7 digits lies in the second half of the input then we only need to
//! consider coefficients that form an [upper triangular matrix], for example:
//!
//! ```none
//! 1 1 1 1
//! 0 1 1 1
//! 0 0 1 1
//! 0 0 0 1
//! ```
//!
//! After the first phase:
//!
//! ```none
//! 1 2 3 4
//! 0 1 2 3
//! 0 0 1 2
//! 0 0 0 1
//! ```
//!
//! After the second phase:
//!
//! ```none
//! 1 3 6 10
//! 0 1 3 6
//! 0 0 1 3
//! 0 0 0 1
//! ```
//!
//! After the third phase:
//!
//! ```none
//! 1 4 10 20
//! 0 1 4 10
//! 0 0 1 6
//! 0 0 0 1
//! ```
//!
//! We can see that the third phase is the [triangular number] sequence and that the fourth phase
//! is the [tetrahedral number] sequence. More generally the `kth` coefficient of the `nth` phase
//! is the [binomial coefficient] `C(n, k)`.
//!
//! We compute the result for part two by finding the `C(100, k)` coefficient for each number using
//! the recursive method of summing previous coefficients from [Pascal's triangle]. This result in
//! complexity `O(100n)` where `n` is the number of digits after the starting index in the input.
//!
//! As a minor optimization we combine multiplying the digits of the input together with the
//! binomial coefficient calculation.
//!
//! [prefix sum]: https://en.wikipedia.org/wiki/Prefix_sum
//! [upper triangular matrix]: https://en.wikipedia.org/wiki/Triangular_matrix
//! [triangular number]: https://en.wikipedia.org/wiki/Triangular_number
//! [tetrahedral number]: https://en.wikipedia.org/wiki/Tetrahedral_number
//! [binomial coefficient]: https://en.wikipedia.org/wiki/Binomial_coefficient
//! [Pascal's triangle]: https://en.wikipedia.org/wiki/Pascal%27s_triangle
use crate::util::parse::*;

pub fn parse(input: &str) -> Vec<u8> {
input.trim().bytes().map(u8::to_decimal).collect()
}

pub fn part1(input: &[u8]) -> i32 {
let size = input.len();
let mid = size / 2;
let end = size - 1;
let pattern = [0, 1, 0, -1];

let mut current = &mut input.iter().copied().map(i32::from).collect::<Vec<_>>();
let mut next = &mut vec![0; size];

for _ in 0..100 {
// Brute force the first half of the input.
for i in 0..mid {
let mut total = 0;

// Skip the first `i` elements as the pattern is always zero.
for j in i..size {
total += current[j] * pattern[((j + 1) / (i + 1)) % 4];
}

next[i] = total.abs() % 10;
}

// Use a faster prefix sum approach similar to part two for the second half of the input.
next[end] = current[end];

for i in (mid..end).rev() {
next[i] = (current[i] + next[i + 1]) % 10;
}

(current, next) = (next, current);
}

current.iter().take(8).fold(0, |acc, &b| 10 * acc + b)
}

pub fn part2(input: &[u8]) -> usize {
let digits: Vec<_> = input.iter().copied().map(usize::from).collect();
let start = fold_number(&digits[..7]);

// This approach will only work if the index is in the second half of the input.
let size = digits.len();
let lower = size * 5_000;
let upper = size * 10_000;
assert!(lower <= start && start < upper);

let mut current = &mut [0; 100];
let mut next = &mut [0; 100];
let mut result = [0; 8];

for index in (start - 100..upper - 1).rev() {
let offset = index + 100 - start;
if offset < 8 {
result[offset] = current[99];
}

// It's faster to turn the loop "inside out" and keep a window of the last
// 100 coefficients pre-multiplied by the input. This means we only need a working array
// of 100 items instead of a large subset of the input.
next[0] = (digits[index % size] + current[0]) % 10;

for j in 1..100 {
next[j] = (current[j - 1] + current[j]) % 10;
}

(current, next) = (next, current);
}

fold_number(&result)
}

/// Folds a slice of digits into an integer.
fn fold_number(slice: &[usize]) -> usize {
slice.iter().fold(0, |acc, &b| 10 * acc + b)
}
1 change: 1 addition & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ mod year2019 {
mod day13_test;
mod day14_test;
mod day15_test;
mod day16_test;
}

mod year2020 {
Expand Down
16 changes: 16 additions & 0 deletions tests/year2019/day16_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use aoc::year2019::day16::*;

const FIRST_EXAMPLE: &str = "80871224585914546619083218645595";
const SECOND_EXAMPLE: &str = "03036732577212944063491565474664";

#[test]
fn part1_test() {
let input = parse(FIRST_EXAMPLE);
assert_eq!(part1(&input), 24176176);
}

#[test]
fn part2_test() {
let input = parse(SECOND_EXAMPLE);
assert_eq!(part2(&input), 84462026);
}

0 comments on commit 569ef15

Please sign in to comment.