aoc/year2017/
day13.rs

1//! # Packet Scanners
2//!
3//! This problem is similar to [`Year 2016 Day 15`]. However the key difference is that we need
4//! to *avoid* the scanners, so the [Chinese Remainder Theorem](https://en.wikipedia.org/wiki/Chinese_remainder_theorem)
5//! is not applicable.
6//!
7//! Part one checks that we can calculate the period for each scanner which is `2 * (range - 1)`.
8//! For example a scanner with range 3 will be at the top position at time 0, 4, 8 and so on.
9//!
10//! To avoid a brute force approach for part two we sieve possible values for each scanner
11//! sorted in ascending order of period. To combine the previous sieved values with the next
12//! scanner, we find the [least common multiple](https://en.wikipedia.org/wiki/Least_common_multiple)
13//! of their ranges. Then we "stretch" the sieved values to cover the new range, by adding
14//! multiples of the factor between the previous range and the new lcm. Finally any values
15//! that would collide with the new scanner are filtered out.
16//!
17//! Using the sample data:
18//!
19//! ```none
20//!    0: 3       1  2
21//!    1: 2  =>   0  4
22//!    4: 4       4  6
23//!    6: 4       6  6
24//! ```
25//!
26//! Starting value is `[1]`. First scanner:
27//!
28//! * Lcm of 1 and 2 => 2
29//! * Stretch `[1] => [1+0 1+1] => [1 2]`
30//! * Filter `[1 2] => [2]`
31//!
32//! Second scanner:
33//!
34//! * Lcm of 2 and 4 => 4
35//! * Stretch `[2] => [2+0 2+2] => [2 4]`
36//! * Filter `[2 4] => [2]`
37//!
38//! Third scanner:
39//!
40//! * Lcm of 4 and 6 => 12
41//! * Stretch `[2] => [2+0 2+4 2+8] => [2 6 10]`
42//! * Filter `[2 6 10] => [6 10]`
43//!
44//! Fourth scanner:
45//!
46//! * Lcm of 12 and 6 => 12
47//! * Stretch `[6 10] => [6+0 10+0] => [6 10]`
48//! * Filter `[6 10] => [10]`
49//!
50//! The lowest remaining value is our answer `10`.
51//!
52//! [`Year 2016 Day 15`]: crate::year2016::day15
53use crate::util::iter::*;
54use crate::util::math::*;
55use crate::util::parse::*;
56
57type Input = Vec<[u32; 2]>;
58
59/// Sorts scanners in ascending order of range.
60pub fn parse(input: &str) -> Input {
61    let mut scanners: Vec<_> = input.iter_unsigned().chunk::<2>().collect();
62    scanners.sort_unstable_by_key(|s| s[1]);
63    scanners
64}
65
66/// Leaving at time zero the packet will encounter each scanner at time `depth`.
67pub fn part1(input: &Input) -> u32 {
68    let mut result = 0;
69
70    for &[depth, range] in input {
71        let period = 2 * (range - 1);
72        if depth % period == 0 {
73            result += depth * range;
74        }
75    }
76
77    result
78}
79
80/// Sieves possible values at each scanner stage to reduce the number of possible values.
81pub fn part2(input: &Input) -> u32 {
82    let mut lcm = 1;
83    let mut current = &mut Vec::new();
84    let mut next = &mut Vec::new();
85
86    current.push(1);
87
88    for &[depth, range] in input {
89        // Find the least common multiple of the existing lcm and the new scanner period.
90        let period = 2 * (range - 1);
91        let next_lcm = lcm.lcm(period);
92
93        // Check each multiple of the current `end` against the new scanner.
94        for extra in (0..next_lcm).step_by(lcm as usize) {
95            for &delay in current.iter() {
96                if (delay + extra + depth) % period != 0 {
97                    next.push(delay + extra);
98                }
99            }
100        }
101
102        lcm = next_lcm;
103        (current, next) = (next, current);
104        next.clear();
105    }
106
107    *current.first().unwrap()
108}