From 71a46593ba7f5aff55f6e4d06acf7e62961f8b3e Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Tue, 15 Dec 2020 13:58:14 -0800 Subject: [PATCH] day 13 solution **after hints!** Attempted a brute force solution. After letting my i9 churn on it across all cores and hyperthreads for ~4 days, looked online and learned about Chinese Remainder Theorem. Applied code from Rosetta Code and success. Signed-off-by: Stephen Waits --- data/input-13.txt | 2 + data/output-13.txt | 2 + src/day13.rs | 121 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 2 + 4 files changed, 127 insertions(+) create mode 100644 data/input-13.txt create mode 100644 data/output-13.txt create mode 100644 src/day13.rs diff --git a/data/input-13.txt b/data/input-13.txt new file mode 100644 index 0000000..8d2559b --- /dev/null +++ b/data/input-13.txt @@ -0,0 +1,2 @@ +1002392 +23,x,x,x,x,x,x,x,x,x,x,x,x,41,x,x,x,37,x,x,x,x,x,421,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,17,x,19,x,x,x,x,x,x,x,x,x,29,x,487,x,x,x,x,x,x,x,x,x,x,x,x,13 diff --git a/data/output-13.txt b/data/output-13.txt new file mode 100644 index 0000000..7b5f092 --- /dev/null +++ b/data/output-13.txt @@ -0,0 +1,2 @@ +3789 +667437230788118 diff --git a/src/day13.rs b/src/day13.rs new file mode 100644 index 0000000..e13bbba --- /dev/null +++ b/src/day13.rs @@ -0,0 +1,121 @@ +// ==== day 13 + +use crate::utils; +use anyhow::Result; + +#[derive(Debug)] +struct BusRoute { + index: usize, + time: usize, +} + +fn parse_bus_routes(input: &str) -> Vec { + input + .trim() + .split(',') + .enumerate() + .filter_map(|(i, s)| { + if let Ok(t) = s.parse::() { + Some(BusRoute { index: i, time: t }) + } else { + None + } + }) + .collect() +} + +fn mins_to_next_departure(dep_time: usize, bus: &BusRoute) -> usize { + let scheduled = bus.time * (dep_time / bus.time); + if scheduled == dep_time { + 0 + } else { + scheduled + bus.time - dep_time + } +} + +// after failing at brute force, looked online for hints and learned about +// the Chinese Remainder Theorem. +// +// code from https://rosettacode.org/wiki/Chinese_remainder_theorem#Rust +fn egcd(a: i64, b: i64) -> (i64, i64, i64) { + if a == 0 { + (b, 0, 1) + } else { + let (g, x, y) = egcd(b % a, a); + (g, y - (b / a) * x, x) + } +} + +// after failing at brute force, looked online for hints and learned about +// the Chinese Remainder Theorem. +// +// code from https://rosettacode.org/wiki/Chinese_remainder_theorem#Rust +fn mod_inv(x: i64, n: i64) -> Option { + let (g, x, _) = egcd(x, n); + if g == 1 { + Some((x % n + n) % n) + } else { + None + } +} + +// after failing at brute force, looked online for hints and learned about +// the Chinese Remainder Theorem. +// +// code from https://rosettacode.org/wiki/Chinese_remainder_theorem#Rust +fn chinese_remainder(residues: &[i64], modulii: &[i64]) -> Option { + let prod = modulii.iter().product::(); + + let mut sum = 0; + + for (&residue, &modulus) in residues.iter().zip(modulii) { + let p = prod / modulus; + sum += residue * mod_inv(p, modulus)? * p + } + + Some(sum % prod) +} + +// main +pub fn run() -> Result<(usize, i64)> { + let data = utils::read_file("data/input-13.txt")?; + let answers = utils::read_i64s("data/output-13.txt")?; + + // parse initial start time and bus routes + let parsed: Vec<&str> = data.splitn(2, '\n').collect(); + assert_eq!(parsed.len(), 2); + let departure_time = parsed[0].parse::().unwrap(); + let routes: Vec = parse_bus_routes(parsed[1]); + + // find which of the bus routes departs most soon after departure_time + let mut next_departures: Vec<(usize, usize)> = routes + .iter() + .map(|bus| (mins_to_next_departure(departure_time, bus), bus.time)) + .collect(); + next_departures.sort_unstable(); + let best = next_departures.first().unwrap(); + let p1 = best.0 * best.1; + + // use CRT to compute the congruence across all these modulii + // + // after failing at brute force, looked online for hints and learned about + // the Chinese Remainder Theorem. + // + // code from https://rosettacode.org/wiki/Chinese_remainder_theorem#Rust + let modulii: Vec = routes.iter().map(|bus| bus.time as i64).collect(); + let residues: Vec = routes + .iter() + .map(|bus| { + if bus.index == 0 { + 0 + } else { + ((100 * bus.time - bus.index) % bus.time) as i64 + } + }) + .collect(); + let p2 = chinese_remainder(&residues, &modulii).unwrap(); + + assert_eq!(p1, answers[0] as usize); + assert_eq!(p2, answers[1]); + Ok((p1, p2)) +} diff --git a/src/main.rs b/src/main.rs index 9c26cf3..010de21 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ mod day09; mod day10; mod day11; mod day12; +mod day13; mod day14; mod day15; mod utils; @@ -39,6 +40,7 @@ fn main() -> Result<()> { println!("Day 10: {:?}", day10::run()?); println!("Day 11: {:?}", day11::run()?); println!("Day 12: {:?}", day12::run()?); + println!("Day 13: {:?}", day13::run()?); println!("Day 14: {:?}", day14::run()?); println!("Day 15: {:?}", day15::run()?); Ok(())