From 15d9bb216a9e1793569e2bb0176a19b159b596c7 Mon Sep 17 00:00:00 2001 From: Guilhem MARION Date: Mon, 8 Feb 2021 17:49:40 +1100 Subject: [PATCH] Day 13 --- 13-shuttle_search/Cargo.lock | 75 ++++++++++++++++++++++++ 13-shuttle_search/Cargo.toml | 3 + 13-shuttle_search/data/input.txt | 2 + 13-shuttle_search/src/main.rs | 98 +++++++++++++++++++++++++++++++- chinese_remainder/Cargo.lock | 5 ++ chinese_remainder/Cargo.toml | 9 +++ chinese_remainder/src/lib.rs | 64 +++++++++++++++++++++ chinese_remainder/tests/tests.rs | 36 ++++++++++++ 8 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 13-shuttle_search/Cargo.lock create mode 100644 13-shuttle_search/data/input.txt create mode 100644 chinese_remainder/Cargo.lock create mode 100644 chinese_remainder/Cargo.toml create mode 100644 chinese_remainder/src/lib.rs create mode 100644 chinese_remainder/tests/tests.rs diff --git a/13-shuttle_search/Cargo.lock b/13-shuttle_search/Cargo.lock new file mode 100644 index 0000000..60d4dd8 --- /dev/null +++ b/13-shuttle_search/Cargo.lock @@ -0,0 +1,75 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "anyhow" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" + +[[package]] +name = "chinese_remainder" +version = "0.1.0" + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "shuttle_search" +version = "0.1.0" +dependencies = [ + "anyhow", + "chinese_remainder", + "thiserror", +] + +[[package]] +name = "syn" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "thiserror" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" diff --git a/13-shuttle_search/Cargo.toml b/13-shuttle_search/Cargo.toml index 4258a1f..394ee07 100644 --- a/13-shuttle_search/Cargo.toml +++ b/13-shuttle_search/Cargo.toml @@ -7,3 +7,6 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.38" +chinese_remainder = { path = "../chinese_remainder/" } +thiserror = "1.0.23" diff --git a/13-shuttle_search/data/input.txt b/13-shuttle_search/data/input.txt new file mode 100644 index 0000000..1d09f13 --- /dev/null +++ b/13-shuttle_search/data/input.txt @@ -0,0 +1,2 @@ +1014511 +17,x,x,x,x,x,x,41,x,x,x,x,x,x,x,x,x,643,x,x,x,x,x,x,x,23,x,x,x,x,13,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,29,x,433,x,x,x,x,x,37,x,x,x,x,x,x,x,x,x,x,x,x,19 diff --git a/13-shuttle_search/src/main.rs b/13-shuttle_search/src/main.rs index e7a11a9..5f44579 100644 --- a/13-shuttle_search/src/main.rs +++ b/13-shuttle_search/src/main.rs @@ -1,3 +1,97 @@ -fn main() { - println!("Hello, world!"); +use anyhow::Result; + +#[derive(Debug, PartialEq)] +struct ProblemStatement { + departure: u64, + lines: Vec, +} +fn main() { + let input = include_str!("../data/input.txt"); + println!("Part 1: {}", step1(parse_input(input).unwrap())); + println!("Part 2: {}", step2(input)); +} + +fn parse_input(i: &str) -> Result { + let mut lines = i.lines(); + Ok(ProblemStatement { + departure: lines + .next() + .ok_or_else(|| anyhow::anyhow!("Missing departure"))? + .parse::()?, + lines: lines + .next() + .unwrap() + .split(',') + .filter(|&c| c != "x") + .map(|t| t.parse::().unwrap()) + .collect(), + }) +} + +fn step1(p: ProblemStatement) -> u64 { + let r = p + .lines + .iter() + .map(|&l| (l - p.departure % l, l)) + .min() + .unwrap(); + r.0 * r.1 +} + +fn step2(input: &str) -> i64 { + let (remainders, moduli): (Vec, Vec) = input.lines().nth(1) + .unwrap() + .split(',') + .enumerate() + .filter_map( + |(num, lineno)| { + let line = str::parse::(lineno).ok()?; + Some((line - num as i64, line)) + } + ) + .unzip(); + chinese_remainder::chinese_remainder(&remainders, &moduli).unwrap() +} + +#[cfg(test)] +mod tests { + use super::*; + + static INPUT: &str = r#"939 +7,13,x,x,59,x,31,19"#; + + #[test] + fn test_parse() { + assert_eq!( + parse_input(INPUT).unwrap(), + ProblemStatement { + departure: 939, + lines: vec![7, 13, 59, 31, 19], + } + ) + } + + #[test] + fn test_step1() { + assert_eq!(step1(parse_input(INPUT).unwrap()), 295) + } + + #[test] + fn test_step2() { + let v_rep = vec![ + (INPUT, 1068781), + ("0\n17,x,13,19", 3417), + ("0\n67,7,59,61", 754018), + ("0\n67,x,7,59,61", 779210), + ("0\n67,7,x,59,61", 1261476), + ("0\n1789,37,47,1889", 1202161486), + ]; + assert_eq!( + v_rep + .iter() + .map(|(i, _)| (*i, step2(i))) + .collect::>(), + v_rep, + ) + } } diff --git a/chinese_remainder/Cargo.lock b/chinese_remainder/Cargo.lock new file mode 100644 index 0000000..dab9e8a --- /dev/null +++ b/chinese_remainder/Cargo.lock @@ -0,0 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "chinese_remainder" +version = "0.1.0" diff --git a/chinese_remainder/Cargo.toml b/chinese_remainder/Cargo.toml new file mode 100644 index 0000000..39a7f6b --- /dev/null +++ b/chinese_remainder/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "chinese_remainder" +version = "0.1.0" +authors = ["Guilhem MARION "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/chinese_remainder/src/lib.rs b/chinese_remainder/src/lib.rs new file mode 100644 index 0000000..467e9e3 --- /dev/null +++ b/chinese_remainder/src/lib.rs @@ -0,0 +1,64 @@ +/// Represents the GCD computed as part of the Extended Euclidean Algorithm. +/// This invariant should always be valid : ax + by = gcd +#[derive(Debug,PartialEq)] +pub struct EGCD { + pub a: i64, + pub b: i64, + pub x: i64, + pub y: i64, + pub gcd: i64, +} + +/// Computes the Extended Euclidean Algorithm's solution to finding a & b's GCD +/// Additionally to the standard Euclidean Algorithm, it provides x and y so +/// that ax + by = GCD +pub fn egcd(a: i64, b: i64) -> EGCD { + // If b == 0, then a == gcd(a,b) + // Then gcd = 1 * a + 0 * b follows + if b == 0 { + EGCD { + a, + b, + x: 1, + // Anything other than 0 gives a valid solution but + // With different end x and y + y: 0, + gcd: a, + } + } else { + let rec = egcd(b, a%b); + EGCD { + a, + b, + x: rec.y, + y: rec.x - rec.y * (a / b), + gcd: rec.gcd, + } + } +} + +/// Solves the chinese remainder problem, stated as follows: +/// ```noexec +/// for i in residues.len(), +/// (chinese_remainder(residues, moduli) - residues[i]) / moduli[i] == 0 +/// ``` +pub fn chinese_remainder(residues: &[i64], moduli: &[i64]) -> Option { + // TODO: Filter out 0s in moduli (meaning big_n is 0 -> div by 0) + let big_n: i64 = moduli.iter().product(); + Some(moduli.iter() + .map(|&ni| { + egcd(ni, big_n / ni) + }) + .zip(residues) + .map(|(egcd,ai)| { + if egcd.gcd != 1 { + // Fail in case moduli are not co-prime + None + } else { + Some(ai * egcd.y * egcd.b) + } + }) + .sum::>()? + .rem_euclid(big_n)) // Sum on Option<_> returns None if the Iter + // contains a None +} diff --git a/chinese_remainder/tests/tests.rs b/chinese_remainder/tests/tests.rs new file mode 100644 index 0000000..a34f377 --- /dev/null +++ b/chinese_remainder/tests/tests.rs @@ -0,0 +1,36 @@ +#[cfg(test)] +mod tests { + use chinese_remainder::{EGCD, egcd, chinese_remainder}; + + #[test] + fn test_egcd() { + assert_eq!( + egcd(25, 7), + EGCD { + a: 25, + b: 7, + x: 2, + y: -7, + gcd: 1, + } + ) + } + #[test] + fn test_cr1() { + assert_eq!( + chinese_remainder(&[2, 3, 2], &[3, 5, 7]), Some(23)); + } + + #[test] + fn test_cr2() { + assert_eq!( + chinese_remainder(&[11,22,19], &[10,4,9]), None); + } + + #[test] + fn test_cr3() { + assert_eq!( + chinese_remainder(&[10,4,12], &[11,12,13]), Some(1000)); + } + +}