Skip to content

Commit

Permalink
2016 day 15
Browse files Browse the repository at this point in the history
  • Loading branch information
ictrobot committed Sep 7, 2024
1 parent 2b13329 commit d4e5f97
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 0 deletions.
147 changes: 147 additions & 0 deletions crates/utils/src/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! Machine-dependent integer types aren't unsupported.
use std::fmt::Debug;
use std::iter::{Product, Sum};
use std::ops::{
Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div, DivAssign,
Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign,
Expand All @@ -14,6 +15,7 @@ pub trait Number:
+ Debug
+ Default
+ PartialEq
+ PartialOrd
+ Add<Output = Self>
+ AddAssign
+ Div<Output = Self>
Expand All @@ -24,12 +26,19 @@ pub trait Number:
+ RemAssign
+ Sub<Output = Self>
+ SubAssign
+ Sum<Self>
+ for<'a> Sum<&'a Self>
+ Product<Self>
+ for<'a> Product<&'a Self>
{
const ZERO: Self;
const ONE: Self;

#[must_use]
fn abs(self) -> Self;

#[must_use]
fn rem_euclid(self, rhs: Self) -> Self;
}

/// Trait implemented by the primitive signed integer and floating point types.
Expand Down Expand Up @@ -84,6 +93,11 @@ macro_rules! number_impl {
fn abs(self) -> Self {
self // no-op for unsigned integers
}

#[inline]
fn rem_euclid(self, rhs: Self) -> Self {
self.rem_euclid(rhs)
}
})+

number_impl! {@common integer => $($t),+}
Expand All @@ -99,6 +113,11 @@ macro_rules! number_impl {
fn abs(self) -> Self {
self.abs()
}

#[inline]
fn rem_euclid(self, rhs: Self) -> Self {
self.rem_euclid(rhs)
}
})+

number_impl! {@common integer => $($t),+}
Expand All @@ -115,6 +134,11 @@ macro_rules! number_impl {
fn abs(self) -> Self {
self.abs()
}

#[inline]
fn rem_euclid(self, rhs: Self) -> Self {
self.rem_euclid(rhs)
}
})+

number_impl! {@common signed => $($t),+}
Expand Down Expand Up @@ -152,3 +176,126 @@ macro_rules! number_impl {
number_impl! {uint => u8, u16, u32, u64, u128}
number_impl! {int => i8, i16, i32, i64, i128}
number_impl! {float => f32, f64}

/// Checks if the provided unsigned integer `n` is a prime number.
///
/// # Examples
/// ```
/// # use utils::number::is_prime;
/// assert_eq!(is_prime(7901u32), true);
/// assert_eq!(is_prime(2147483647u32), true);
/// assert_eq!(is_prime(4294967291u32), true);
/// assert_eq!(is_prime(6u32), false);
/// assert_eq!(is_prime(123u32), false);
/// ```
#[inline]
pub fn is_prime<T: UnsignedInteger>(n: T) -> bool {
if n <= T::ONE {
return false;
}
if n == T::from(2) || n == T::from(3) {
return true;
}
if n % T::from(2) == T::ZERO || n % T::from(3) == T::ZERO {
return false;
}

let mut i = T::from(5);
while let Some(square) = i.checked_mul(i) {
if square > n {
break;
}

if n % i == T::ZERO || n % (i + T::from(2)) == T::ZERO {
return false;
}

if let Some(next) = i.checked_add(T::from(6)) {
i = next;
} else {
break;
}
}

true
}

/// Computes the greatest common divisor (GCD) using the extended Euclidean algorithm.
///
/// Returns a tuple `(gcd, x, y)` where `x`, `y` are the coefficients of Bézout's identity:
/// ```text
/// ax + by = gcd(a, b)
/// ```
///
/// # Examples
/// ```
/// # use utils::number::egcd;
/// assert_eq!(egcd(252, 105), (21, -2, 5));
/// assert_eq!((252 * -2) + (105 * 5), 21);
/// ```
#[inline]
pub fn egcd<T: SignedInteger>(mut a: T, mut b: T) -> (T, T, T) {
let (mut x0, mut x1, mut y0, mut y1) = (T::ONE, T::ZERO, T::ZERO, T::ONE);

while b != T::ZERO {
let q = a / b;
(a, b) = (b, a % b);
(x0, x1) = (x1, x0 - q * x1);
(y0, y1) = (y1, y0 - q * y1);
}

(a, x0, y0)
}

/// Computes the modular inverse of `a` modulo `b` if it exists.
///
/// # Examples
/// ```
/// # use utils::number::mod_inverse;
/// assert_eq!(mod_inverse(3, 5), Some(2));
/// assert_eq!((3 * 2) % 5, 1);
///
/// assert_eq!(mod_inverse(10, 23), Some(7));
/// assert_eq!((10 * 7) % 23, 1);
///
/// assert_eq!(mod_inverse(2, 8), None);
/// ```
#[inline]
pub fn mod_inverse<T: SignedInteger>(a: T, b: T) -> Option<T> {
let (gcd, x, _) = egcd(a, b);
if gcd == T::ONE {
Some(x.rem_euclid(b))
} else {
None
}
}

/// Solves a system of simultaneous congruences using the Chinese Remainder Theorem.
///
/// This function finds the smallest non-negative integer `x` where `x % modulus = residue` for each
/// provided (residue, modulus) pair.
///
/// # Examples
/// ```
/// # use utils::number::chinese_remainder;
/// assert_eq!(chinese_remainder([1, 2, 3], [5, 7, 11]), Some(366));
/// assert_eq!(366 % 5, 1);
/// assert_eq!(366 % 7, 2);
/// assert_eq!(366 % 11, 3);
/// ```
#[inline]
pub fn chinese_remainder<T: SignedInteger>(
residues: impl IntoIterator<Item = T>,
moduli: impl IntoIterator<Item = T, IntoIter: Clone>,
) -> Option<T> {
let moduli = moduli.into_iter();
let product = moduli.clone().product();

let mut sum = T::ZERO;
for (residue, modulus) in residues.into_iter().zip(moduli) {
let p = product / modulus;
sum += residue * mod_inverse(p, modulus)? * p;
}

Some(sum.rem_euclid(product))
}
76 changes: 76 additions & 0 deletions crates/year2016/src/day15.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use std::cell::RefCell;
use std::collections::HashSet;
use std::iter;
use utils::number::{chinese_remainder, is_prime};
use utils::prelude::*;

/// Finding when discs align.
///
/// This puzzle is a system of linear simultaneous congruences which can be solved using
/// <https://en.wikipedia.org/wiki/Chinese_remainder_theorem>.
#[derive(Clone, Debug)]
pub struct Day15 {
discs: Vec<Disc>,
}

#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct Disc {
size: u32,
position: u32,
}

impl Day15 {
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
let seen_positions: RefCell<HashSet<u32>> = Default::default();
Ok(Self {
discs: parser::u32()
.with_prefix(" has ".with_prefix(parser::u32()).with_prefix("Disc #"))
.then(parser::u32().with_prefix(" positions; at time=0, it is at position "))
.with_suffix(".")
.map_res(|(size, position)| {
if position >= size {
Err("current position should be less than number of positions")
} else if !is_prime(size) {
Err("number of positions should be prime")
} else if !seen_positions.borrow_mut().insert(size) {
Err("number of positions should be unique")
} else {
Ok(Disc { size, position })
}
})
.parse_lines(input)?,
})
}

#[must_use]
pub fn part1(&self) -> i64 {
Self::earliest_alignment(self.discs.iter())
}

#[must_use]
pub fn part2(&self) -> i64 {
Self::earliest_alignment(self.discs.iter().chain(iter::once(&Disc {
size: 11,
position: 0,
})))
}

#[inline]
fn earliest_alignment<'a>(discs: impl Iterator<Item = &'a Disc> + Clone) -> i64 {
let residues = discs
.clone()
.enumerate()
.map(|(i, disc)| -(disc.position as i64) - (i as i64 + 1));
let moduli = discs.map(|disc| disc.size as i64);

chinese_remainder(residues, moduli).expect("sizes are all prime")
}
}

examples!(Day15 -> (i64, i64) [
{
input: "Disc #1 has 5 positions; at time=0, it is at position 4.\n\
Disc #2 has 2 positions; at time=0, it is at position 1.",
part1: 5
},
]);
1 change: 1 addition & 0 deletions crates/year2016/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ utils::year!(2016 => year2016, ${
12 => day12::Day12,
13 => day13::Day13,
14 => day14::Day14<'_>,
15 => day15::Day15,
});

0 comments on commit d4e5f97

Please sign in to comment.