diff --git a/crates/utils/src/number.rs b/crates/utils/src/number.rs index 16f13f7..886e0c3 100644 --- a/crates/utils/src/number.rs +++ b/crates/utils/src/number.rs @@ -255,6 +255,23 @@ pub fn egcd(mut a: T, mut b: T) -> (T, T, T) { (a, x0, y0) } +/// Computes the lowest common multiple (LCM). +/// +/// # Examples +/// ``` +/// # use utils::number::lcm; +/// assert_eq!(lcm(6, 4), 12); +/// assert_eq!(lcm(21, 6), 42); +/// ``` +pub fn lcm(a: T, b: T) -> T { + if a == T::ZERO || b == T::ZERO { + return T::ZERO; + } + + let (gcd, ..) = egcd(a, b); + (a / gcd).abs() * b.abs() +} + /// Computes the modular inverse of `a` modulo `b` if it exists. /// /// # Examples diff --git a/crates/year2017/src/day13.rs b/crates/year2017/src/day13.rs new file mode 100644 index 0000000..7a30c3e --- /dev/null +++ b/crates/year2017/src/day13.rs @@ -0,0 +1,76 @@ +use std::collections::BTreeMap; +use utils::number; +use utils::prelude::*; + +/// Finding the gap in the firewall. +/// +/// Similar to [2016 day 15](../year2016/struct.Day15.html), but instead of a system of linear +/// simultaneous congruences, it is a system of simultaneous modular inequalities. +#[derive(Clone, Debug)] +pub struct Day13 { + layers: Vec<(u32, u32)>, +} + +impl Day13 { + pub fn new(input: &str, _: InputType) -> Result { + Ok(Self { + layers: parser::u32() + .with_suffix(": ") + .then(parser::u32()) + .parse_lines(input)?, + }) + } + + #[must_use] + pub fn part1(&self) -> u32 { + self.layers + .iter() + .map(|&(depth, range)| { + let period = (range - 1) * 2; + if depth % period == 0 { + depth * range + } else { + 0 + } + }) + .sum() + } + + #[must_use] + pub fn part2(&self) -> u32 { + // Each key represents a modulus and maps to a list of values where + // delay % modulus can't equal the value + let mut constraints = BTreeMap::new(); + for &(depth, range) in &self.layers { + let modulus = (range as i32 - 1) * 2; + let disallowed_value = (-(depth as i32)).rem_euclid(modulus); + + constraints + .entry(modulus) + .or_insert_with(Vec::new) + .push(disallowed_value); + } + + // Find all the possible delays % lcm which meet the above constraints + let mut lcm = 1; + let mut possible_delays = vec![0]; + for (modulus, disallowed_values) in constraints { + let new_lcm = number::lcm(modulus, lcm); + possible_delays = possible_delays + .into_iter() + .flat_map(|delay| { + (delay..new_lcm) + .step_by(lcm as usize) + .filter(|&i| !disallowed_values.contains(&(i % modulus))) + }) + .collect(); + lcm = new_lcm; + } + + *possible_delays.iter().min().unwrap() as u32 + } +} + +examples!(Day13 -> (u32, u32) [ + {input: "0: 3\n1: 2\n4: 4\n6: 4", part1: 24, part2: 10}, +]); diff --git a/crates/year2017/src/lib.rs b/crates/year2017/src/lib.rs index f6edb6f..b593cfe 100644 --- a/crates/year2017/src/lib.rs +++ b/crates/year2017/src/lib.rs @@ -14,4 +14,5 @@ utils::year!(2017 => year2017, ${ 10 => day10::Day10<'_>, 11 => day11::Day11, 12 => day12::Day12, + 13 => day13::Day13, });