Add composition functions to combinatoric module
Some checks failed
mint_ci / Check Python code using ruff (push) Successful in 20s
Rust-lint / Run rust tests (push) Failing after 52s
Rust-lint / Check Rust code with rustfmt and clippy (push) Failing after 32s

- Introduced new functions for generating compositions, including `composition_of_length`, `composition`, `permuted_composition`, and their variants with minimum and maximum constraints.
- Added depth-first search (DFS) logic for generating compositions and permutations.
- Included unit tests to verify the correctness of the new composition functionalities.
- Updated the combinatoric module to reflect these additions.
This commit is contained in:
2025-05-17 17:36:48 +09:00
parent 27dc9fa1e3
commit 9684cd8859
3 changed files with 775 additions and 1 deletions

View File

@@ -1,10 +1,10 @@
pub mod prob17;
pub mod prob49;
pub mod prob491;
pub mod prob497;
pub mod prob61;
pub mod prob650;
pub mod prob66;
pub mod prob719;
pub mod prob885;
pub mod prob932;
pub mod prob934;

View File

@@ -0,0 +1,579 @@
/// Number Splitting
///
/// We define an S-number to be a natural number, n, that is a perfect square and its square root can be obtained by splitting the decimal representation of n into k or more numbers then adding the numbers.
///
/// For example, 81 is an S-number because \(\sqrt{81} = 8 + 1\).
///
/// 6724 is an S-number: \(\sqrt{6724} = 6 + 72 + 4\).
///
/// 8281 is an S-number: \(\sqrt{8281} = 8 + 2 + 81\).
///
/// 9801 is an S-number: \(\sqrt{9801} = 98 + 0 + 1\).
///
/// T(N) is the sum of all S-numbers n <= N.
///
/// You are given T(10^4) = 41333.
/// Find T(10^12).
///
use crate::utils::{
combinatoric::{composition, composition_with_min_max},
integer::is_square,
};
use itertools::Itertools;
use std::collections::HashSet;
fn split_number(digits: &Vec<u32>, comp: Vec<&usize>) -> Vec<u32> {
comp.iter()
.fold((0, Vec::new()), |(cursor, mut result), &c| {
result.push(
digits[cursor..cursor + c]
.iter()
.fold(0, |num, &d| num * 10 + d),
);
(cursor + c, result)
})
.1
}
fn is_s_number(n: usize) -> bool {
if !is_square(n) {
return false;
}
let square_root = (n as f64).sqrt() as u32;
let digits = n
.to_string()
.chars()
.map(|c| c.to_digit(10).unwrap())
.collect::<Vec<_>>();
let compositions = composition(digits.len());
for comp in compositions {
for permuted_comp in comp.iter().permutations(comp.len()) {
let split_numbers = split_number(&digits, permuted_comp);
if split_numbers.iter().sum::<u32>() == square_root {
return true;
}
}
}
false
}
pub fn list_of_s_numbers<T>(iter: T) -> Vec<usize>
where
T: IntoIterator<Item = usize>,
{
iter.into_iter().filter(|n| is_s_number(*n)).collect()
}
pub fn sum_of_s_numbers<T>(iter: T) -> usize
where
T: IntoIterator<Item = usize>,
{
iter.into_iter().filter(|n| is_s_number(*n)).sum()
}
pub fn sum_of_s_numbers_up_to(n: usize) -> usize {
let sqrt_n = (n as f64).sqrt() as usize;
sum_of_s_numbers((1..=sqrt_n).map(|i| i * i))
}
pub fn list_of_s_numbers_up_to(n: usize) -> Vec<usize> {
let sqrt_n = (n as f64).sqrt() as usize;
list_of_s_numbers((1..=sqrt_n).map(|i| i * i))
}
fn can_concatenate_to(n: usize, num_vec: &Vec<usize>, digit: usize) -> bool {
for permuted_num_vec in num_vec.iter().permutations(num_vec.len()) {
let concatenated_num = permuted_num_vec
.iter()
.fold(String::new(), |acc, num| format!("{}{}", acc, num))
.parse::<usize>()
.unwrap();
let concatenated_num_digit = concatenated_num.to_string().len();
if concatenated_num == n && concatenated_num_digit == digit {
return true;
}
}
false
}
pub fn list_of_s_numbers_of_digit(digit: usize) -> HashSet<usize> {
fn number_of_digit(d: usize) -> Vec<usize> {
if d == 1 {
return vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
}
let start = 10u32.pow(d as u32 - 1);
let end = 10u32.pow(d as u32);
(start..end).map(|i| i as usize).collect()
}
let mut result = HashSet::new();
for comp in composition(digit) {
let sum_max: u64 = comp.iter().map(|d| 10u64.pow(*d as u32) - 1).sum();
if sum_max * sum_max <= 10u64.pow(digit as u32 - 1) {
continue;
}
for num_vec in comp
.iter()
.map(|c| number_of_digit(*c))
.multi_cartesian_product()
{
let sum = num_vec.iter().sum::<usize>();
if can_concatenate_to(sum * sum, &num_vec, digit) {
result.insert(sum * sum);
}
}
}
result
}
#[derive(Debug, Clone)]
pub struct SNumber {
pub composition: Vec<usize>,
pub digits: Vec<Option<u8>>,
}
pub fn digit_to_num<'a, T>(digits: T) -> usize
where
T: IntoIterator<Item = &'a u8>,
{
digits.into_iter().fold(0, |acc, d| acc * 10 + *d as usize)
}
pub fn digit_to_num_owned<T>(digits: T) -> usize
where
T: IntoIterator<Item = u8>,
{
digits.into_iter().fold(0, |acc, d| acc * 10 + d as usize)
}
impl SNumber {
fn new(composition: &Vec<usize>) -> Self {
let length = composition.iter().sum::<usize>();
Self {
composition: composition.clone(),
digits: vec![None; length],
}
}
fn check(&self) -> Option<usize> {
if self.digits.iter().any(|d| d.is_none()) {
return None;
}
let digits = self.digits.iter().map(|d| d.unwrap()).collect::<Vec<_>>();
let num = digit_to_num(&digits[..]);
let comp_sum = self
.composition
.iter()
.fold((0, 0), |(sum, cursor), d| {
(sum + digit_to_num(&digits[cursor..cursor + d]), cursor + d)
})
.0;
if comp_sum * comp_sum == num {
Some(num)
} else {
None
}
}
fn min_digit(&self) -> Vec<u8> {
self.digits
.iter()
.enumerate()
.map(|(i, d)| {
if i == 0 {
d.unwrap_or(1)
} else {
d.unwrap_or(0)
}
})
.collect()
}
fn max_digit(&self) -> Vec<u8> {
self.digits.iter().map(|d| d.unwrap_or(9)).collect()
}
fn comp_sum(&self, digits: &Vec<u8>) -> usize {
self.composition
.iter()
.fold((0, 0), |(sum, cursor), d| {
(sum + digit_to_num(&digits[cursor..cursor + d]), cursor + d)
})
.0
}
fn range_comp_sum(&self) -> (usize, usize) {
let sum_min = self.comp_sum(&self.min_digit());
let sum_max = self.comp_sum(&self.max_digit());
(sum_min, sum_max)
}
fn range_num(&self) -> (usize, usize) {
let num_min = digit_to_num_owned(self.min_digit());
let num_max = digit_to_num_owned(self.max_digit());
(num_min, num_max)
}
fn has_common_range(&self) -> bool {
let (num_min, num_max) = self.range_num();
let (sum_min, sum_max) = self.range_comp_sum();
num_min <= sum_max * sum_max && num_max >= sum_min * sum_min
}
fn dominant_digit(&self) -> usize {
let mut place_vec = vec![1; self.digits.len()];
let mut cursor = 0;
for c in self.composition.iter() {
let mut place = 10i32.pow(*c as u32 - 1);
for _ in 0..*c {
place_vec[cursor] = place;
place /= 10;
cursor += 1;
}
}
self.digits
.iter()
.zip(place_vec.iter())
.enumerate()
.max_by_key(|(i, (d, p))| match d {
Some(_) => (-1, 0),
None => (**p, -(*i as i32)),
})
.unwrap()
.0
}
fn find_solution(&mut self) -> HashSet<usize> {
if !self.has_common_range() {
return HashSet::new();
} else if let Some(num) = self.check() {
return HashSet::from([num]);
}
let dominant_digit = self.dominant_digit();
let mut result = HashSet::new();
let candidates = if dominant_digit == 0 { 1..=9 } else { 0..=9 };
for candidate in candidates {
self.digits[dominant_digit] = Some(candidate);
result.extend(self.find_solution());
self.digits[dominant_digit] = None;
}
result
}
}
pub fn list_of_s_numbers_final(digit: usize) -> HashSet<usize> {
let mut result = HashSet::new();
let mut visited = HashSet::new();
for i in 2..=digit {
let minmax = (i - 1) / 2;
for comp in composition_with_min_max(i, minmax) {
let s_number = SNumber::new(&comp);
if !s_number.has_common_range() {
continue;
}
let length = comp.len();
for perm in comp.into_iter().permutations(length) {
if visited.contains(&perm) {
continue;
}
visited.insert(perm.clone());
let mut s_number = SNumber::new(&perm);
result.extend(s_number.find_solution());
}
}
}
result
}
fn is_s_number_answer(comp_sum: i64, concatenated_num: i64) -> bool {
if concatenated_num < comp_sum || comp_sum < 0 {
return false;
} else if concatenated_num == comp_sum {
return true;
}
let mut scissor = 10;
while scissor < concatenated_num {
let remain_concatenated_num = concatenated_num / scissor;
let sliced = concatenated_num % scissor;
if is_s_number_answer(comp_sum - sliced, remain_concatenated_num) {
return true;
}
scissor *= 10;
}
false
}
pub fn sum_of_s_numbers_answer(n: i64) -> i64 {
let max_comp_sum = n.isqrt();
let mut sum = 0;
for comp_sum in 2..=max_comp_sum {
if is_s_number_answer(comp_sum, comp_sum * comp_sum) {
sum += comp_sum * comp_sum;
}
}
sum
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_split_number() {
let digits = vec![1, 3, 4, 5];
let comp = vec![&2, &2];
assert_eq!(split_number(&digits, comp), vec![13, 45]);
let comp = vec![&1, &1, &1, &1];
assert_eq!(split_number(&digits, comp), vec![1, 3, 4, 5]);
let comp = vec![&1, &2, &1];
assert_eq!(split_number(&digits, comp), vec![1, 34, 5]);
}
#[test]
fn test_is_s_number() {
assert!(is_s_number(81));
assert!(is_s_number(6724));
assert!(is_s_number(8281));
assert!(is_s_number(9801));
}
#[test]
fn test_sum_of_s_numbers() {
assert_eq!(sum_of_s_numbers(1..=10000), 41333);
}
#[test]
fn test_list_of_s_numbers() {
assert_eq!(
list_of_s_numbers(1..=100000),
vec![
81, 100, 1296, 2025, 3025, 6724, 8281, 9801, 10000, 55225, 88209
]
);
}
#[test]
fn test_s_numbers_up_to() {
assert_eq!(
list_of_s_numbers_up_to(100000),
list_of_s_numbers(1..=100000)
);
assert_eq!(sum_of_s_numbers_up_to(100000), sum_of_s_numbers(1..=100000));
}
#[test]
fn test_list_of_s_numbers_of_digit() {
assert_eq!(list_of_s_numbers_of_digit(2), HashSet::from([81]));
assert_eq!(list_of_s_numbers_of_digit(3), HashSet::from([100]));
assert_eq!(
list_of_s_numbers_of_digit(4),
HashSet::from([1296, 2025, 3025, 6724, 8281, 9801])
);
}
#[test]
#[ignore]
fn test_list_of_s_numbers_of_digit_large() {
assert_eq!(
list_of_s_numbers_of_digit(5),
HashSet::from([10000, 55225, 88209])
);
assert_eq!(
list_of_s_numbers_of_digit(6),
HashSet::from([
136161, 136900, 143641, 171396, 431649, 455625, 494209, 571536, 627264, 826281,
842724, 893025, 929296, 980100, 982081, 998001
])
);
}
#[test]
fn test_digit_to_num() {
assert_eq!(digit_to_num(&vec![1, 2, 3]), 123);
assert_eq!(digit_to_num(&vec![1, 2, 3, 4]), 1234);
}
#[test]
fn test_check_s_number() {
let mut s_number = SNumber::new(&vec![1, 1]);
s_number.digits = vec![Some(8), Some(1)];
assert_eq!(s_number.check(), Some(81));
s_number.digits = vec![Some(1), Some(8)];
assert_eq!(s_number.check(), None);
let mut s_number = SNumber::new(&vec![1, 2, 1]);
s_number.digits = vec![Some(6), Some(7), Some(2), Some(4)];
assert_eq!(s_number.check(), Some(6724));
s_number.digits = vec![Some(4), Some(7), Some(2), Some(6)];
assert_eq!(s_number.check(), None);
let mut s_number = SNumber::new(&vec![2, 1, 1]);
s_number.digits = vec![Some(9), Some(8), Some(0), Some(1)];
assert_eq!(s_number.check(), Some(9801));
s_number.digits = vec![Some(8), Some(2), Some(8), Some(1)];
assert_eq!(s_number.check(), Some(8281));
}
#[test]
fn test_min_max_digit() {
let mut s_number = SNumber::new(&vec![1, 1]);
assert_eq!(s_number.min_digit(), vec![1, 0]);
assert_eq!(s_number.max_digit(), vec![9, 9]);
s_number.digits[0] = Some(2);
assert_eq!(s_number.min_digit(), vec![2, 0]);
assert_eq!(s_number.max_digit(), vec![2, 9]);
s_number.digits[1] = Some(2);
assert_eq!(s_number.min_digit(), vec![2, 2]);
assert_eq!(s_number.max_digit(), vec![2, 2]);
let mut s_number = SNumber::new(&vec![2, 1, 1]);
assert_eq!(s_number.min_digit(), vec![1, 0, 0, 0]);
assert_eq!(s_number.max_digit(), vec![9, 9, 9, 9]);
s_number.digits[1] = Some(2);
assert_eq!(s_number.min_digit(), vec![1, 2, 0, 0]);
assert_eq!(s_number.max_digit(), vec![9, 2, 9, 9]);
}
#[test]
fn test_range_num() {
let mut s_number = SNumber::new(&vec![1, 1]);
assert_eq!(s_number.range_num(), (10, 99));
s_number.digits[0] = Some(2);
assert_eq!(s_number.range_num(), (20, 29));
s_number.digits[0] = None;
s_number.digits[1] = Some(2);
assert_eq!(s_number.range_num(), (12, 92));
}
#[test]
fn test_range_comp_sum() {
let mut s_number = SNumber::new(&vec![1, 1]);
assert_eq!(s_number.range_comp_sum(), (1, 18));
s_number.digits[0] = Some(2);
assert_eq!(s_number.range_comp_sum(), (2, 11));
s_number.digits[0] = None;
s_number.digits[1] = Some(2);
assert_eq!(s_number.range_comp_sum(), (3, 11));
let mut s_number = SNumber::new(&vec![2, 1]);
assert_eq!(s_number.range_comp_sum(), (10, 108));
s_number.digits[0] = Some(2);
assert_eq!(s_number.range_comp_sum(), (20, 38));
s_number.digits[0] = None;
s_number.digits[1] = Some(2);
assert_eq!(s_number.range_comp_sum(), (12, 101));
let mut s_number = SNumber::new(&vec![1, 2, 1]);
assert_eq!(s_number.range_comp_sum(), (1, 117));
s_number.digits[0] = Some(2);
assert_eq!(s_number.range_comp_sum(), (2, 110));
s_number.digits[0] = None;
s_number.digits[1] = Some(2);
assert_eq!(s_number.range_comp_sum(), (21, 47));
}
#[test]
fn test_dominant_digit() {
let mut s_number = SNumber::new(&vec![1, 1]);
assert_eq!(s_number.dominant_digit(), 0);
s_number.digits[0] = Some(2);
assert_eq!(s_number.dominant_digit(), 1);
s_number.digits[0] = None;
s_number.digits[1] = Some(2);
assert_eq!(s_number.dominant_digit(), 0);
let mut s_number = SNumber::new(&vec![2, 3, 1]);
assert_eq!(s_number.dominant_digit(), 2);
s_number.digits[2] = Some(2);
assert_eq!(s_number.dominant_digit(), 0);
s_number.digits[0] = Some(2);
assert_eq!(s_number.dominant_digit(), 3);
}
#[test]
fn test_has_common_range() {
let mut s_number = SNumber::new(&vec![1, 1]);
assert!(s_number.has_common_range());
for i in 1..=9 {
s_number.digits[0] = Some(i);
assert!(s_number.has_common_range());
}
let mut s_number = SNumber::new(&vec![1, 1, 1, 1]);
assert!(s_number.has_common_range());
for i in 1..=9 {
s_number.digits[0] = Some(i);
assert!(!s_number.has_common_range());
}
}
#[test]
fn test_find_solution() {
let mut s_number = SNumber::new(&vec![1, 1]);
assert_eq!(s_number.find_solution(), HashSet::from([81]));
let mut s_number = SNumber::new(&vec![2, 1]);
assert_eq!(s_number.find_solution(), HashSet::from([100]));
let mut s_number = SNumber::new(&vec![1, 2, 1]);
assert_eq!(s_number.find_solution(), HashSet::from([1296, 6724]));
let mut s_number = SNumber::new(&vec![2, 1, 1]);
assert_eq!(s_number.find_solution(), HashSet::from([8281, 9801]));
let mut s_number = SNumber::new(&vec![2, 2]);
assert_eq!(s_number.find_solution(), HashSet::from([2025, 3025, 9801]));
let mut s_number = SNumber::new(&vec![3, 2]);
assert_eq!(s_number.find_solution(), HashSet::from([10000]));
}
#[test]
fn test_list_of_s_numbers_final() {
assert_eq!(
list_of_s_numbers_final(6),
HashSet::from([
81, 100, 1296, 2025, 3025, 6724, 8281, 9801, 10000, 55225, 88209, 136161, 136900,
143641, 171396, 431649, 455625, 494209, 571536, 627264, 826281, 842724, 893025,
929296, 980100, 982081, 998001
])
);
assert_eq!(list_of_s_numbers_final(4).iter().sum::<usize>(), 31333);
// assert_eq!(list_of_s_numbers_final(12).iter().sum::<usize>(), 41333);
}
#[test]
fn test_sum_s_number_answer() {
assert_eq!(sum_of_s_numbers_answer(10000), 41333);
assert_eq!(sum_of_s_numbers_answer(1_000_000_000_000), 128088830547982);
}
}

View File

@@ -1,3 +1,6 @@
use itertools::Itertools;
use std::collections::HashSet;
pub fn factorial(n: usize) -> usize {
assert!(
n <= 20,
@@ -16,3 +19,195 @@ pub fn combination(n: usize, k: usize) -> usize {
pub fn combination_with_repetition(n: usize, k: usize) -> usize {
combination(n + k - 1, k)
}
pub fn composition_of_length(n: usize, length: usize) -> Vec<Vec<usize>> {
fn dfs(
n: usize,
length: usize,
idx: usize,
current: &mut Vec<usize>,
result: &mut Vec<Vec<usize>>,
) {
let remain = n - current.iter().sum::<usize>();
let value_limit = if idx == 0 { n - 1 } else { current[idx - 1] };
if remain == 0 && idx == length {
result.push(current.clone());
return;
} else if remain > (length - idx) * value_limit || idx == length {
return;
}
let max_value = remain.min(value_limit);
for i in (1..=max_value).rev() {
current[idx] = i;
dfs(n, length, idx + 1, current, result);
}
current[idx] = 0;
}
let mut result = Vec::new();
let mut current = vec![0; length];
dfs(n, length, 0, &mut current, &mut result);
result
}
pub fn composition(n: usize) -> Vec<Vec<usize>> {
let mut result = Vec::new();
for length in 2..=n {
result.extend(composition_of_length(n, length));
}
result
}
pub fn permuted_composition(n: usize) -> HashSet<Vec<usize>> {
let mut visited = HashSet::new();
for length in 2..=n {
for comp in composition_of_length(n, length) {
for perm in comp.iter().permutations(length) {
visited.insert(perm.iter().map(|d| **d).collect::<Vec<_>>());
}
}
}
visited
}
pub fn composition_of_length_with_min_max(
n: usize,
length: usize,
minmax: usize,
) -> Vec<Vec<usize>> {
fn dfs(
n: usize,
length: usize,
idx: usize,
current: &mut Vec<usize>,
result: &mut Vec<Vec<usize>>,
) {
let remain = n - current.iter().sum::<usize>();
let value_limit = if idx == 0 { n - 1 } else { current[idx - 1] };
if remain == 0 && idx == length {
result.push(current.clone());
return;
} else if remain > (length - idx) * value_limit || idx == length {
return;
}
let max_value = remain.min(value_limit);
for i in (1..=max_value).rev() {
current[idx] = i;
dfs(n, length, idx + 1, current, result);
}
current[idx] = 0;
}
let mut result = Vec::new();
let mut current = vec![0; length];
for i in (minmax..n).rev() {
current[0] = i;
dfs(n, length, 1, &mut current, &mut result);
}
result
}
pub fn composition_with_min_max(n: usize, minmax: usize) -> Vec<Vec<usize>> {
let mut result = Vec::new();
for length in 2..=(n - minmax + 1) {
result.extend(composition_of_length_with_min_max(n, length, minmax));
}
result
}
pub fn permuted_composition_with_min_max(n: usize, minmax: usize) -> HashSet<Vec<usize>> {
let mut visited = HashSet::new();
for length in 2..=(n - minmax + 1) {
for comp in composition_of_length_with_min_max(n, length, minmax) {
for perm in comp.iter().permutations(length) {
visited.insert(perm.iter().map(|d| **d).collect::<Vec<_>>());
}
}
}
visited
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_composition_of_length() {
let result = composition_of_length(10, 2);
assert_eq!(
result,
vec![vec![9, 1], vec![8, 2], vec![7, 3], vec![6, 4], vec![5, 5]]
);
let result = composition_of_length(8, 3);
assert_eq!(
result,
vec![
vec![6, 1, 1],
vec![5, 2, 1],
vec![4, 3, 1],
vec![4, 2, 2],
vec![3, 3, 2]
]
);
}
#[test]
fn test_composition() {
let result = composition(5);
assert_eq!(
result,
vec![
vec![4, 1],
vec![3, 2],
vec![3, 1, 1],
vec![2, 2, 1],
vec![2, 1, 1, 1],
vec![1, 1, 1, 1, 1]
]
);
}
#[test]
fn test_permuted_composition() {
let result = permuted_composition(3);
assert_eq!(
result,
HashSet::from([vec![1, 2], vec![2, 1], vec![1, 1, 1]])
);
let result = permuted_composition(4);
assert_eq!(
result,
HashSet::from([
vec![1, 3],
vec![3, 1],
vec![2, 2],
vec![1, 1, 2],
vec![1, 2, 1],
vec![2, 1, 1],
vec![1, 1, 1, 1]
])
)
}
#[test]
fn test_composition_of_length_with_min_max() {
let result = composition_of_length_with_min_max(2, 2, 1);
assert_eq!(result, vec![vec![1, 1]]);
let result = composition_of_length_with_min_max(8, 2, 6);
assert_eq!(result, vec![vec![7, 1], vec![6, 2]]);
let result = composition_of_length_with_min_max(8, 3, 6);
assert_eq!(result, vec![vec![6, 1, 1]]);
}
#[test]
fn test_composition_with_min_max() {
let result = composition_with_min_max(8, 6);
assert_eq!(result, vec![vec![7, 1], vec![6, 2], vec![6, 1, 1]]);
}
}