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
//! # Chronal Calibration
//!
//! The simplest approach to part two is to store previously seen numbers in a `HashSet` then
//! stop once a duplicate is found. However this approach requires scanning the input of ~1,000
//! numbers multiple times, around 150 times for my input.
//!
//! A much faster `O(nlogn)` approach relies on the fact that each frequency increases by the same
//! amount (the sum of all deltas) each time the list of numbers is processed. For example:
//!
//! ```none
//! Deltas: +1, -2, +3, +1 =>
//! 0 1 -1 2
//! 3 4 2 5
//! ```
//!
//! Two frequencies that are a multiple of the sum will eventually repeat. First we group each
//! frequencies by its remainder modulo the sum, using `rem_euclid` to handle negative frequencies
//! correctly, Then we sort, first by the remainder to group frequencies that can repeat together,
//! then by the frequency increasing in order to help find the smallest gap between similar
//! frequencies, then lastly by index as this is needed in the next step.
//!
//! For the example this produces `[(0, 0, 0), (1, 1, 1), (2, -1, 2), (2, 2, 3)]`. Then we use
//! a sliding windows of size two to compare each pair of adjacent canditates, considering only
//! candidates with the same remainder. For each valid pair we then produce a tuple of
//! `(frequency gap, index, frequency)`.
//!
//! Finally we sort the tuples in ascending order, first by smallest frequency gap, breaking any
//! ties using the index to find frequencies that appear earlier in the list. The first tuple
//! in the list gives the result, in the example this is `[(3, 2, 2)]`.
use crate::util::parse::*;
pub fn parse(input: &str) -> Vec<i32> {
input.iter_signed().collect()
}
pub fn part1(input: &[i32]) -> i32 {
input.iter().sum()
}
pub fn part2(input: &[i32]) -> i32 {
// The frequencies increase by this amount each pass through the list of deltas.
let total: i32 = input.iter().sum();
// Calculate tuples of `(frequency gap, index, frequency)` then sort to group frequencies that
// can collide together.
let mut frequency: i32 = 0;
let mut seen = Vec::with_capacity(input.len());
for n in input {
seen.push((frequency.rem_euclid(total), frequency, seen.len()));
frequency += n;
}
seen.sort_unstable();
// Compare each adjacent pair of tuples to find candidates, then sort by smallest gap first,
// tie breaking with index if needed.
let mut pairs = Vec::new();
for window in seen.windows(2) {
let (remainder0, freq0, index0) = window[0];
let (remainder1, freq1, _) = window[1];
if remainder0 == remainder1 {
pairs.push((freq1 - freq0, index0, freq1));
}
}
pairs.sort_unstable();
// Result is the frequency of the first tuple.
let (_, _, freq) = pairs[0];
freq
}