Add new problem modules and utility functions for combinatorics
Some checks failed
mint_ci / Check Python code using ruff (push) Successful in 20s
Rust-lint / Run rust tests (push) Failing after 56s
Rust-lint / Check Rust code with rustfmt and clippy (push) Failing after 33s

- Introduced `prob527` module for a new problem implementation in the Project Euler series.
- Enhanced the `combinatoric` module with a new `CombinationTree` struct for efficient combination calculations.
- Added utility functions for calculating expected values in binary and random search scenarios.
- Implemented unit tests for the new functionalities to ensure correctness and reliability.
- Updated the `mod.rs` files to include the new problem module and utility functions.
This commit is contained in:
2025-05-27 23:48:36 +09:00
parent b25ed7e13d
commit fb135b6ec2
5 changed files with 246 additions and 1 deletions

View File

@@ -1,13 +1,16 @@
pub mod prob17;
pub mod prob49;
pub mod prob497;
pub mod prob527;
pub mod prob61;
pub mod prob650;
pub mod prob66;
pub mod prob684;
pub mod prob700;
pub mod prob719;
pub mod prob808;
pub mod prob816;
pub mod prob845;
pub mod prob885;
pub mod prob932;
pub mod prob934;

View File

@@ -0,0 +1,116 @@
use std::collections::HashMap;
fn binary_search(n: usize, memory: &mut HashMap<usize, f64>) -> f64 {
if let Some(&value) = memory.get(&n) {
return value;
}
match n % 2 {
0 => {
let k = n / 2;
let result = ((k as f64) * binary_search(k, memory)
+ (k as f64 - 1.0) * binary_search(k - 1, memory))
/ (n as f64)
+ 1.0;
memory.insert(n, result);
result
}
1 => {
let k = n / 2;
let result = 2.0 * (k as f64) * binary_search(k, memory) / n as f64 + 1.0;
memory.insert(n, result);
result
}
_ => unreachable!(),
}
}
pub fn expected_try_binary_search(n: usize) -> f64 {
let mut memory = HashMap::from([(1, 1.0), (2, 1.5)]);
binary_search(n, &mut memory)
}
pub fn expected_try_random_search(n: usize) -> f64 {
let mut memory = vec![0.0; n + 1];
memory[1] = 1.0;
let mut sum = 1.0;
for i in 2..=n {
let temp = 1.0 + 2.0 / (i * i) as f64 * sum;
memory[i] = temp;
sum += i as f64 * temp;
}
memory[n]
}
pub fn expected_try_random_search_2(n: usize) -> f64 {
2.0 * (n as f64 + 1.0) / (n as f64) * (1..=n).map(|i| 1.0 / (i as f64)).sum::<f64>() - 3.0
}
pub fn expected_try_random_search_3(n: usize) -> f64 {
const EULER_MASCHERONI: f64 = 0.57721566490153;
2.0 * (n as f64 + 1.0) / (n as f64) * (EULER_MASCHERONI + (n as f64).ln()) - 3.0
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_expected_try_binary_search() {
assert_abs_diff_eq!(expected_try_binary_search(1), 1.0);
assert_abs_diff_eq!(expected_try_binary_search(2), 1.5);
assert_abs_diff_eq!(expected_try_binary_search(3), 1.6666, epsilon = 1e-4);
assert_abs_diff_eq!(expected_try_binary_search(6), 2.3333, epsilon = 1e-4);
for i in 4..=10 {
let n = 10i64.pow(i);
let expected = (n as f64).log2() - 1.0;
assert_abs_diff_eq!(
expected_try_binary_search(n as usize),
expected,
epsilon = 1e-1
);
}
}
#[test]
fn test_expected_try_random_search() {
assert_abs_diff_eq!(expected_try_random_search(1), 1.0);
assert_abs_diff_eq!(expected_try_random_search(2), 1.5);
assert_abs_diff_eq!(expected_try_random_search(6), 2.71666667, epsilon = 1e-4);
for i in 1..=6 {
let n = 10i64.pow(i);
assert_abs_diff_eq!(
expected_try_random_search(n as usize),
expected_try_random_search_2(n as usize),
epsilon = 1e-8
);
}
}
#[test]
fn test_error_ratio() {
let n = 10i64.pow(10);
// let expected = (n as f64).log2() - 1.0;
// assert_abs_diff_eq!(
// expected_try_binary_search(n as usize),
// expected,
// epsilon = 1e-8
// );
// assert_abs_diff_eq!(
// expected_try_random_search_2(n as usize),
// expected_try_random_search_3(n as usize),
// epsilon = 1e-8
// );
assert_abs_diff_eq!(
expected_try_random_search_3(n as usize) - expected_try_binary_search(n as usize),
11.92412011,
epsilon = 1e-8
);
}
}

View File

@@ -16,6 +16,33 @@ pub fn combination(n: usize, k: usize) -> usize {
factorial(n) / (factorial(k) * factorial(n - k))
}
pub struct CombinationTree {
max: usize,
tree: Vec<Vec<usize>>,
}
impl CombinationTree {
pub fn new(max: usize) -> Self {
let mut tree = vec![vec![1]];
for i in 1..=max {
let mut row = vec![1; i + 1];
for j in 1..i {
row[j] = tree[i - 1][j - 1] + tree[i - 1][j];
}
tree.push(row);
}
Self { max, tree }
}
pub fn get(&self, n: usize, k: usize) -> Option<usize> {
if n > self.max || k > n {
None
} else {
Some(self.tree[n][k])
}
}
}
pub fn combination_with_repetition(n: usize, k: usize) -> usize {
combination(n + k - 1, k)
}
@@ -133,6 +160,70 @@ pub fn permuted_composition_with_min_max(n: usize, minmax: usize) -> HashSet<Vec
mod tests {
use super::*;
#[test]
fn test_combination_tree() {
let result = CombinationTree::new(5);
assert_eq!(
result.tree,
vec![
vec![1],
vec![1, 1],
vec![1, 2, 1],
vec![1, 3, 3, 1],
vec![1, 4, 6, 4, 1],
vec![1, 5, 10, 10, 5, 1]
]
);
// Additional test case for n = 20, test full record
let result_20 = CombinationTree::new(20);
let expected_20 = vec![
vec![1],
vec![1, 1],
vec![1, 2, 1],
vec![1, 3, 3, 1],
vec![1, 4, 6, 4, 1],
vec![1, 5, 10, 10, 5, 1],
vec![1, 6, 15, 20, 15, 6, 1],
vec![1, 7, 21, 35, 35, 21, 7, 1],
vec![1, 8, 28, 56, 70, 56, 28, 8, 1],
vec![1, 9, 36, 84, 126, 126, 84, 36, 9, 1],
vec![1, 10, 45, 120, 210, 252, 210, 120, 45, 10, 1],
vec![1, 11, 55, 165, 330, 462, 462, 330, 165, 55, 11, 1],
vec![1, 12, 66, 220, 495, 792, 924, 792, 495, 220, 66, 12, 1],
vec![
1, 13, 78, 286, 715, 1287, 1716, 1716, 1287, 715, 286, 78, 13, 1,
],
vec![
1, 14, 91, 364, 1001, 2002, 3003, 3432, 3003, 2002, 1001, 364, 91, 14, 1,
],
vec![
1, 15, 105, 455, 1365, 3003, 5005, 6435, 6435, 5005, 3003, 1365, 455, 105, 15, 1,
],
vec![
1, 16, 120, 560, 1820, 4368, 8008, 11440, 12870, 11440, 8008, 4368, 1820, 560, 120,
16, 1,
],
vec![
1, 17, 136, 680, 2380, 6188, 12376, 19448, 24310, 24310, 19448, 12376, 6188, 2380,
680, 136, 17, 1,
],
vec![
1, 18, 153, 816, 3060, 8568, 18564, 31824, 43758, 48620, 43758, 31824, 18564, 8568,
3060, 816, 153, 18, 1,
],
vec![
1, 19, 171, 969, 3876, 11628, 27132, 50388, 75582, 92378, 92378, 75582, 50388,
27132, 11628, 3876, 969, 171, 19, 1,
],
vec![
1, 20, 190, 1140, 4845, 15504, 38760, 77520, 125970, 167960, 184756, 167960,
125970, 77520, 38760, 15504, 4845, 1140, 190, 20, 1,
],
];
assert_eq!(result_20.tree, expected_20);
}
#[test]
fn test_composition_of_length() {
let result = composition_of_length(10, 2);

View File

@@ -1,6 +1,22 @@
use num::integer::{Integer, Roots, gcd};
use num::{
FromPrimitive, PrimInt,
integer::{Integer, Roots, gcd},
};
use std::cmp::Ordering;
pub fn digit_sum<T>(n: T) -> T
where
T: PrimInt + FromPrimitive,
{
let mut sum = T::zero();
let mut n = n;
while n > T::zero() {
sum = sum + n % T::from_usize(10).unwrap();
n = n / T::from_usize(10).unwrap();
}
sum
}
pub fn is_square<T>(n: T) -> bool
where
T: Roots + Copy,

View File

@@ -37,6 +37,17 @@ where
}
}
pub fn modulo_pow<T>(base: T, exp: T, modulo: T) -> T
where
T: PrimInt + Euclid + ToBigInt,
{
let base_big = base.to_bigint().unwrap();
let exp_big = exp.to_bigint().unwrap();
let modulo_big = modulo.to_bigint().unwrap();
let result_big = base_big.modpow(&exp_big, &modulo_big);
T::from(result_big.to_u32_digits().1[0]).unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
@@ -72,4 +83,12 @@ mod tests {
1_110_380_180
);
}
#[test]
fn test_modulo_pow() {
assert_eq!(modulo_pow::<u32>(2, 10, 1000), 24);
assert_eq!(modulo_pow::<u32>(3, 4, 17), 13);
assert_eq!(modulo_pow::<u32>(123456, 789, 1000000007), 182677862);
assert_eq!(modulo_pow::<u32>(2, 31, 4294967291), 2147483648);
}
}