From b25ed7e13de6286986eb2bf06ef89f4cdc428e23 Mon Sep 17 00:00:00 2001 From: Myeongseon Choi Date: Fri, 23 May 2025 11:12:37 +0900 Subject: [PATCH] Add new problem modules and utility functions for Euler and Rosalind challenges - Introduced `prob700`, `prob808`, and `prob816` modules for new problem implementations in the Project Euler series. - Added utility functions for calculating Euler coins and their sums, as well as methods for finding reversible primes and calculating shortest distances in a modular context. - Updated the `mod.rs` files to include the new problem modules in both the Project Euler and Rosalind sections. - Enhanced the `integer` and `modulo` utility modules with new functions for coprimality checks and modular multiplication. - Included unit tests for the new functionalities to ensure correctness and reliability. - Refactored existing tests for consistency in the `finding_protein_motif` module. --- src/project_euler/mod.rs | 3 + src/project_euler/prob700.rs | 198 ++++++++++++++++++++++++ src/project_euler/prob808.rs | 149 ++++++++++++++++++ src/project_euler/prob816.rs | 135 ++++++++++++++++ src/rosalind/finding_protein_motif.rs | 4 +- src/rosalind/mod.rs | 1 + src/rosalind/rna_secondary_structure.rs | 0 src/utils/integer.rs | 9 +- src/utils/modulo.rs | 49 +++++- src/utils/prime.rs | 70 ++++++++- 10 files changed, 608 insertions(+), 10 deletions(-) create mode 100644 src/project_euler/prob700.rs create mode 100644 src/project_euler/prob808.rs create mode 100644 src/project_euler/prob816.rs create mode 100644 src/rosalind/rna_secondary_structure.rs diff --git a/src/project_euler/mod.rs b/src/project_euler/mod.rs index 464110d..2f4da85 100644 --- a/src/project_euler/mod.rs +++ b/src/project_euler/mod.rs @@ -4,7 +4,10 @@ pub mod prob497; pub mod prob61; pub mod prob650; pub mod prob66; +pub mod prob700; pub mod prob719; +pub mod prob808; +pub mod prob816; pub mod prob885; pub mod prob932; pub mod prob934; diff --git a/src/project_euler/prob700.rs b/src/project_euler/prob700.rs new file mode 100644 index 0000000..e670f96 --- /dev/null +++ b/src/project_euler/prob700.rs @@ -0,0 +1,198 @@ +use num_integer::{Integer, gcd}; +use std::collections::HashSet; + +fn euler_inverse(p: i64, q: i64) -> i64 { + let g = gcd(p, q) as i64; + if g != 1 { + panic!("p and q are not coprime"); + } + + let (mut rn, mut rn1) = (p, q); + let (mut an, mut an1) = (1, 0); + while rn1 != 0 { + let (quot, rem) = rn.div_rem(&rn1); + let temp = an1; + an1 = an - quot * an1; + an = temp; + rn = rn1; + rn1 = rem; + } + an +} + +fn naive_eulercoins(p: i64, q: i64) -> Vec { + let mut coins = vec![p]; + let mut current = p; + while current != 1 { + current = (current + p) % q; + if ¤t < coins.last().unwrap() { + coins.push(current); + } + } + coins +} + +pub fn naive_eulercoins_sum(p: i64, q: i64) -> i64 { + let coins = naive_eulercoins(p, q); + coins.iter().sum() +} + +pub fn eulercoins(p: i64, q: i64) -> HashSet { + let inv = euler_inverse(p, q); + + // for left index, large number + let mut left_index = 0; + let mut left_coin = i64::MAX; + let mut test_number = 0; + let left_step = p; + + // for right index, small number + let mut right_index = i64::MAX; + let mut right_coin = 0; + let mut test_index = 0; + let right_step = inv; + + let mut coins = HashSet::new(); + let mut bigstep; + while left_index < right_index { + left_index += 1; + test_number = (test_number + left_step).rem_euclid(q); + if test_number < left_coin { + left_coin = test_number; + + if coins.contains(&left_coin) { + break; + } + coins.insert(left_coin); + + bigstep = (q - test_number) / left_step; + test_number += bigstep * left_step; + left_index += bigstep; + } + + right_coin += 1; + test_index = (test_index + right_step).rem_euclid(q); + if test_index < right_index { + right_index = test_index; + + if coins.contains(&right_coin) { + break; + } + coins.insert(right_coin); + + bigstep = (q - test_index) / right_step; + test_index += bigstep * right_step; + right_coin += bigstep; + } + } + coins +} + +pub fn eulercoins_sum(p: i64, q: i64) -> i64 { + let coins = eulercoins(p, q); + coins.iter().sum() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::utils::prime::is_prime; + + #[test] + #[ignore] + fn test_naive_eulercoins() { + assert_eq!( + naive_eulercoins_sum(1_983_365_009, 8_977_183_777), + 6696721770 + ); + } + + #[test] + fn test_euler_coefficient() { + assert_eq!(euler_inverse(3, 2), 1); + assert_eq!(euler_inverse(2, 3), -1); + + assert_eq!(euler_inverse(5, 3), -1); + assert_eq!(euler_inverse(3, 5), 2); + + assert_eq!(euler_inverse(17, 13), -3); + assert_eq!(euler_inverse(13, 17), 4); + } + + #[test] + fn test_eulercoins() { + assert_eq!(eulercoins(2, 3), HashSet::from([2, 1])); + assert_eq!(eulercoins(3, 5), HashSet::from([1, 3])); + assert_eq!(eulercoins(13, 17), HashSet::from([1, 5, 9, 13])); + assert_eq!( + eulercoins(1_983_365_009, 8_977_183_777), + HashSet::from([ + 1_983_365_009, + 939641268, + 835558795, + 731476322, + 627393849, + 523311376, + 419228903, + 315146430, + 211063957, + 106981484, + 2899011, + 281923, + 202142, + 122361, + 42580, + 5379, + 452, + 45, + 43, + 41, + 39, + 37, + 35, + 33, + 31, + 29, + 27, + 25, + 23, + 21, + 19, + 17, + 15, + 13, + 11, + 9, + 7, + 5, + 3, + 1, + ]) + ); + } + + #[test] + fn test_eulercoins_many_case() { + for p in 5..=100 { + for q in p + 1..=200 { + if !is_prime(p as usize) || !is_prime(q as usize) { + continue; + } + + let coins = eulercoins(p, q); + let naive_coins = naive_eulercoins(p, q); + assert_eq!(coins, HashSet::from_iter(naive_coins.into_iter())); + } + } + } + + #[test] + fn test_eulercoins_sum() { + assert_eq!(eulercoins_sum(1_983_365_009, 8_977_183_777), 6696721770); + + assert_eq!( + eulercoins_sum(1_504_170_715_041_707, 4_503_599_627_370_517), + 1517926517777556 + ); + } +} diff --git a/src/project_euler/prob808.rs b/src/project_euler/prob808.rs new file mode 100644 index 0000000..ec35bc5 --- /dev/null +++ b/src/project_euler/prob808.rs @@ -0,0 +1,149 @@ +use crate::utils::integer::is_square; +use crate::utils::prime::{is_prime, prime_iter}; +use std::collections::HashMap; + +pub fn is_revserible(prime: u64) -> bool { + let rev_square = (prime * prime) + .to_string() + .chars() + .rev() + .collect::() + .parse::() + .unwrap(); + + is_square(rev_square) && rev_square != prime * prime && is_prime(rev_square.isqrt()) +} + +pub fn reverse(n: u64) -> u64 { + n.to_string() + .chars() + .rev() + .collect::() + .parse::() + .unwrap() +} + +pub fn length(n: u64) -> usize { + n.to_string().len() +} + +pub fn find_reversible_primes_in_range(n: usize) -> Vec { + let mut cache_rev_squares: HashMap = HashMap::new(); + let mut reversible_prime_squares = Vec::new(); + + let mut square; + for prime in prime_iter::(n) { + square = prime * prime; + if let Some(rev_square) = cache_rev_squares.remove(&square) { + reversible_prime_squares.push(rev_square); + reversible_prime_squares.push(square); + } else if length(square) % 2 == 1 { + let rev_square = reverse(square); + if rev_square != square && (rev_square % 10 == 1 || rev_square % 10 == 9) { + cache_rev_squares.insert(rev_square, square); + } + } + } + + reversible_prime_squares.sort(); + reversible_prime_squares +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_revserible() { + assert_eq!(is_revserible(3), false); + assert_eq!(is_revserible(5), false); + assert_eq!(is_revserible(7), false); + assert_eq!(is_revserible(11), false); + assert_eq!(is_revserible(13), true); + } + + #[test] + fn test_find_reversible_primes_in_range() { + assert_eq!( + find_reversible_primes_in_range(500_000_000), + vec![ + 169, + 961, + 12769, + 96721, + 1042441, + 1062961, + 1216609, + 1442401, + 1692601, + 9066121, + 121066009, + 900660121, + 12148668841, + 12367886521, + 12568876321, + 14886684121, + 1000422044521, + 1002007006009, + 1020506060401, + 1040606050201, + 1210684296721, + 1212427816609, + 1212665666521, + 1214648656321, + 1234367662441, + 1236568464121, + 1254402240001, + 1256665662121, + 1276924860121, + 1442667634321, + 9006007002001, + 9066187242121, + 100042424498641, + 100222143232201, + 100240164024001, + 100402824854641, + 100420461042001, + 102012282612769, + 102014060240401, + 102232341222001, + 104042060410201, + 121002486012769, + 121264386264121, + 121462683462121, + 123212686214641, + 146412686212321, + 146458428204001, + 146894424240001, + 967210684200121, + 967216282210201, + 10020032222212321, + 10022014702860169, + 10201204627026169, + 10203042928272769, + 10424432666212321, + 10444842248400121, + 12100484224844401, + 12100662429066121, + 12102442783276609, + 12104622861462121, + 12120294922868521, + 12122012862600169, + 12122034882612769, + 12126416822640121, + 12144284865634321, + 12166092426600121, + 12321222223002001, + 12321266623442401, + 12343656848244121, + 12586822949202121, + 90667238724420121, + 96100626821022121, + 96106820741022001, + 96162072640210201, + 96721628843022121, + 96727282924030201 + ] + ); + } +} diff --git a/src/project_euler/prob816.rs b/src/project_euler/prob816.rs new file mode 100644 index 0000000..65a210c --- /dev/null +++ b/src/project_euler/prob816.rs @@ -0,0 +1,135 @@ +use crate::utils::modulo::modulo_mul; + +fn make_point_array(s0: u64, n: usize, modulus: u64) -> Vec<(u64, u64)> { + let mut s2n = s0; + let mut s2n1 = modulo_mul(s0, s0, modulus); + let mut points = vec![(s2n, s2n1)]; + for _ in 1..n { + s2n = modulo_mul(s2n1, s2n1, modulus); + s2n1 = modulo_mul(s2n, s2n, modulus); + points.push((s2n, s2n1)); + } + points +} + +pub fn naive_shortest_distance(s0: u64, n: usize, modulus: u64) -> String { + let points = make_point_array(s0, n, modulus); + let mut min_distance_square = i64::MAX; + for i in 0..n { + for j in i + 1..n { + let dx1 = (points[i].0 as i64 - points[j].0 as i64).abs(); + let dx2 = (points[i].1 as i64 - points[j].1 as i64).abs(); + let distance_square = dx1 * dx1 + dx2 * dx2; + if distance_square < min_distance_square { + min_distance_square = distance_square; + } + } + } + format!("{:.9}", (min_distance_square as f64).sqrt()) +} + +pub fn dc_shortest_distance(points: &[(u64, u64)]) -> i64 { + let n = points.len(); + if n <= 3 { + let mut min_distance_square = i64::MAX; + for i in 0..n { + for j in i + 1..n { + let dx1 = points[i].0 as i64 - points[j].0 as i64; + let dx2 = points[i].1 as i64 - points[j].1 as i64; + let distance_square = dx1 * dx1 + dx2 * dx2; + if distance_square < min_distance_square { + min_distance_square = distance_square; + } + } + } + return min_distance_square; + } + + let mid = n / 2; + let min_from_left = dc_shortest_distance(&points[..mid]); + let min_from_right = dc_shortest_distance(&points[mid..]); + let mut min_distance_square = min_from_left.min(min_from_right); + let min_distance = min_distance_square.isqrt() as u64 + 1; + + let mid_x = points[mid].0; + let mut strip = vec![points[mid]]; + let mut cursor = mid - 1; + while mid_x < points[cursor].0 + min_distance { + strip.push(points[cursor]); + cursor = match cursor.checked_sub(1) { + Some(c) => c, + None => break, + }; + } + cursor = mid + 1; + while cursor < n && points[cursor].0 < mid_x + min_distance { + strip.push(points[cursor]); + cursor += 1; + } + + strip.sort_by_key(|p| p.1); + let length = strip.len(); + for i in 0..length { + cursor = i + 1; + while cursor < length && strip[cursor].1 < min_distance + strip[i].1 { + let dx1 = strip[i].0 as i64 - strip[cursor].0 as i64; + let dx2 = strip[i].1 as i64 - strip[cursor].1 as i64; + let distance_square = dx1 * dx1 + dx2 * dx2; + if distance_square < min_distance_square { + min_distance_square = distance_square; + } + cursor += 1; + } + } + min_distance_square +} + +pub fn shortest_distance(s0: u64, n: usize, modulus: u64) -> String { + let mut points = make_point_array(s0, n, modulus); + points.sort_by_key(|p| p.0); + let min_distance_square = dc_shortest_distance(&points); + format!("{:.9}", (min_distance_square as f64).sqrt()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_make_point_array() { + let points = make_point_array(2, 10, 1000); + assert_eq!(points.len(), 10); + assert_eq!( + points, + vec![ + (2, 4), + (16, 256), + (536, 296), + (616, 456), + (936, 96), + (216, 656), + (336, 896), + (816, 856), + (736, 696), + (416, 56) + ] + ) + } + + #[test] + fn test_naive_shortest_distance() { + assert_eq!( + naive_shortest_distance(290797, 14, 50515093), + "546446.466846479" + ); + } + + #[test] + fn test_shortest_distance() { + assert_eq!(shortest_distance(290797, 14, 50515093), "546446.466846479"); + assert_eq!( + shortest_distance(290797, 2_000_000, 50515093), + "20.880613018" + ); + } +} diff --git a/src/rosalind/finding_protein_motif.rs b/src/rosalind/finding_protein_motif.rs index c6ddf8f..8c72163 100644 --- a/src/rosalind/finding_protein_motif.rs +++ b/src/rosalind/finding_protein_motif.rs @@ -47,7 +47,7 @@ mod tests { let protein = "MRASRPVVHPVEAPPPAALAVAAAAVAVEAGVGAGGGAAAHGGENAQPRGVRMKDPPGAPGTPGGLGLRLVQAFFAAAALAVMASTDDFPSVSAFCYLVAAAILQCLWSLSLAVVDIYALLVKRSLRNPQAVCIFTIGDGITGTLTLGAACASAGITVLIGNDLNICANNHCASFETATAMAFISWFALAPSCVLNFWSMASR"; let motif = "N{P}[ST]{P}"; let positions = find_protein_motif(protein, motif); - assert_eq!(positions, vec![]); + assert_eq!(positions, Vec::::new()); let protein = "GCATGATACATG"; let motif = "CAT"; @@ -66,7 +66,7 @@ mod tests { let uniprot_id = "A2Z669"; let positions = find_protein_motif_in_uniprot(uniprot_id, motif); - assert_eq!(positions, vec![]); + assert_eq!(positions, Vec::::new()); sleep(Duration::from_millis(10)); diff --git a/src/rosalind/mod.rs b/src/rosalind/mod.rs index 353c00e..acdb2bb 100644 --- a/src/rosalind/mod.rs +++ b/src/rosalind/mod.rs @@ -3,4 +3,5 @@ pub mod enumerating_gene_orders; pub mod finding_protein_motif; pub mod inferring_mrna_from_protein; pub mod open_reading_frames; +pub mod rna_secondary_structure; pub mod rna_splicing; diff --git a/src/rosalind/rna_secondary_structure.rs b/src/rosalind/rna_secondary_structure.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/integer.rs b/src/utils/integer.rs index 722a371..ca9ef4b 100644 --- a/src/utils/integer.rs +++ b/src/utils/integer.rs @@ -1,4 +1,4 @@ -use num::integer::{Integer, Roots}; +use num::integer::{Integer, Roots, gcd}; use std::cmp::Ordering; pub fn is_square(n: T) -> bool @@ -9,6 +9,13 @@ where s * s == n } +pub fn is_coprime(a: T, b: T) -> bool +where + T: Integer + Copy, +{ + gcd(a, b) == T::one() +} + pub fn has_split_of_sum(n: usize, sum: usize) -> Option> { match n.cmp(&sum) { Ordering::Less => None, diff --git a/src/utils/modulo.rs b/src/utils/modulo.rs index 753ee3c..68f65f1 100644 --- a/src/utils/modulo.rs +++ b/src/utils/modulo.rs @@ -1,6 +1,9 @@ -use num::traits::{Euclid, PrimInt}; +use num::{ + bigint::{BigInt, ToBigInt}, + traits::{Euclid, PrimInt}, +}; -pub fn checked_modulo_add(a: T, b: T, modulo: T) -> T { +pub fn modulo_add(a: T, b: T, modulo: T) -> T { match a.checked_add(&b) { Some(c) => c % modulo, None => { @@ -16,23 +19,57 @@ pub fn checked_modulo_add(a: T, b: T, modulo: T) -> T { } } +pub fn modulo_mul(a: T, b: T, modulo: T) -> T +where + T: PrimInt + Euclid + ToBigInt, + BigInt: std::ops::Rem, +{ + match a.checked_mul(&b) { + Some(c) => c.rem_euclid(&modulo), + None => { + let a_big = a.to_bigint().unwrap(); + let b_big = b.to_bigint().unwrap(); + let modulo_big = modulo.to_bigint().unwrap(); + let c_big = a_big * b_big; + let c = c_big.rem_euclid(&modulo_big); + T::from(c.to_u32_digits().1[0]).unwrap() + } + } +} + #[cfg(test)] mod tests { use super::*; #[test] - fn test_checked_modulo_add() { + fn test_modulo_add() { assert_eq!( - checked_modulo_add::(1_000_000_000, 4_000_000_000, 1_543_545_676), + modulo_add::(1_000_000_000, 4_000_000_000, 1_543_545_676), 369_362_972 ); assert_eq!( - checked_modulo_add::(1_000_000_000, 2_000_000_000, 1_543_545_676), + modulo_add::(1_000_000_000, 2_000_000_000, 1_543_545_676), 1_456_454_324 ); assert_eq!( - checked_modulo_add::(-1_000_000_000, -2_000_000_000, 1_543_545_676), + modulo_add::(-1_000_000_000, -2_000_000_000, 1_543_545_676), 87_091_352 ); } + + #[test] + fn test_modulo_mul() { + assert_eq!( + modulo_mul::(1_000_000_000, 4_000_000_000, 1_543_545_676), + 866_330_992 + ); + assert_eq!( + modulo_mul::(1_000_000_000, 2_000_000_000, 1_543_545_676), + 433_165_496 + ); + assert_eq!( + modulo_mul::(-1_000_000_000, 2_000_000_000, 1_543_545_676), + 1_110_380_180 + ); + } } diff --git a/src/utils/prime.rs b/src/utils/prime.rs index 46e82e2..373db14 100644 --- a/src/utils/prime.rs +++ b/src/utils/prime.rs @@ -1,4 +1,4 @@ -use num::{FromPrimitive, ToPrimitive, Unsigned, integer::Roots, iter::range_inclusive}; +use num::{FromPrimitive, PrimInt, ToPrimitive, Unsigned, integer::Roots, iter::range_inclusive}; pub fn is_prime(n: T) -> bool where @@ -18,6 +18,60 @@ where } } +struct PrimeSieve { + _marker: std::marker::PhantomData, + sieve: Vec, + cursor: usize, + max: usize, +} + +impl PrimeSieve +where + T: PrimInt + FromPrimitive, +{ + fn new(n: usize) -> Self { + Self { + _marker: std::marker::PhantomData, + sieve: vec![true; n + 1], + cursor: 2, + max: n, + } + } +} + +impl Iterator for PrimeSieve +where + T: PrimInt + ToPrimitive + FromPrimitive, +{ + type Item = T; + + fn next(&mut self) -> Option { + while self.cursor <= self.max && !self.sieve[self.cursor] { + self.cursor += 1; + } + + if self.cursor > self.max { + return None; + } + + let prime = T::from_usize(self.cursor).unwrap(); + let end = self.max / self.cursor; + (2..=end).for_each(|i| { + self.sieve[i * self.cursor] = false; + }); + + self.cursor += 1; + Some(prime) + } +} + +pub fn prime_iter(n: usize) -> impl Iterator +where + T: PrimInt + ToPrimitive + FromPrimitive, +{ + PrimeSieve::::new(n).into_iter() +} + #[cfg(test)] mod tests { use super::*; @@ -44,4 +98,18 @@ mod tests { assert_eq!(is_prime::(10000006), false); assert_eq!(is_prime::(10000019), true); } + + #[test] + fn test_prime_sieve() { + let mut sieve = PrimeSieve::::new(20); + assert_eq!(sieve.next(), Some(2)); + assert_eq!(sieve.next(), Some(3)); + assert_eq!(sieve.next(), Some(5)); + assert_eq!(sieve.next(), Some(7)); + assert_eq!(sieve.next(), Some(11)); + assert_eq!(sieve.next(), Some(13)); + assert_eq!(sieve.next(), Some(17)); + assert_eq!(sieve.next(), Some(19)); + assert_eq!(sieve.next(), None); + } }