use english_numbers::{convert, Formatting};
use std::collections::HashMap;
fn binomial_coefficient(n: usize, k: usize) -> usize {
if k > n {
return 0;
}
let k = k.min(n - k);
let mut result = 1;
for i in 0..k {
result = result * (n - i) / (i + 1);
}
result
}
fn find_combination<const N: usize>(total: usize, index: usize) -> Option<[usize; N]> {
if N == 1 {
return Some([total; N]);
}
let mut current_index = index;
let mut result = [0; N];
let mut remaining_total = total;
for i in 0..N {
let remaining_terms = N - i;
if remaining_terms == 1 {
result[i] = remaining_total;
return Some(result);
}
let mut binomial_sum = 0;
for x in 0..=remaining_total {
let count = binomial_coefficient(
remaining_total - x + remaining_terms - 2,
remaining_terms - 2,
);
binomial_sum += count;
if current_index < binomial_sum {
result[i] = x;
remaining_total -= x;
current_index -= binomial_sum - count;
break;
}
}
}
Some(result)
}
fn smallest_vector_index<const N: usize>(index: usize) -> [usize; N] {
if index == 0 {
return [0; N];
}
let mut index = index - 1;
let mut total_index = 1;
let mut total = binomial_coefficient(total_index + N - 1, N - 1);
while index >= total {
index -= total;
total_index += 1;
total = binomial_coefficient(total_index + N - 1, N - 1);
}
find_combination::<N>(total_index, index).unwrap()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OptimizationError {
NoSolution,
}
pub fn optimize<const N: usize>(
string_generator: impl Fn([i64; N]) -> String,
number_generators: [impl Fn(&str) -> i64; N],
condition: impl Fn(&str) -> bool,
check_n: usize,
) -> Result<[i64; N], OptimizationError> {
let mut checked: HashMap<[i64; N], String> = HashMap::new();
let mut current_start_index = 0;
let mut current: [i64; N] = [1; N];
loop {
let string = string_generator(current);
let mut numbers = [0; N];
for i in 0..N {
numbers[i] = number_generators[i](&string);
}
checked.insert(current, string.clone());
if numbers == current && condition(&string) {
return Ok(numbers);
}
if checked.contains_key(&numbers) {
if current_start_index >= check_n {
return Err(OptimizationError::NoSolution);
}
current = smallest_vector_index::<N>(current_start_index).map(|x| x as i64 + 1);
current_start_index += 1;
} else {
current = numbers;
}
}
}
pub fn count_letters(string: &str) -> i64 {
string.chars().filter(|c| c.is_alphabetic()).count() as i64
}
pub fn count_words(string: &str) -> i64 {
string
.chars()
.map(|c| if c.is_alphabetic() { c } else { ' ' })
.collect::<String>()
.split_whitespace()
.count() as i64
}
pub fn count_vowels(string: &str) -> i64 {
string
.to_lowercase()
.chars()
.filter(|c| "aeiouy".contains(*c))
.count() as i64
}
pub fn count_consonants(string: &str) -> i64 {
string
.to_lowercase()
.chars()
.filter(|c| "bcdfghjklmnpqrstvwxz".contains(*c))
.count() as i64
}
pub fn count_vowels_no_y(string: &str) -> i64 {
string
.to_lowercase()
.chars()
.filter(|c| "aeiou".contains(*c))
.count() as i64
}
pub fn count_consonants_with_y(string: &str) -> i64 {
string
.to_lowercase()
.chars()
.filter(|c| "bcdfghjklmnpqrstvwxyz".contains(*c))
.count() as i64
}
pub fn count_words_of_length_n(n: i64) -> impl Fn(&str) -> i64 {
move |string: &str| {
string
.chars()
.map(|c| if c.is_alphabetic() { c } else { ' ' })
.collect::<String>()
.split_whitespace()
.filter(|word| word.chars().count() as i64 == n)
.count() as i64
}
}
pub fn no_condition(_: &str) -> bool {
true
}
pub fn has_n_letters(n: i64) -> impl Fn(&str) -> bool {
move |string: &str| count_letters(string) == n
}
#[macro_export]
macro_rules! carykh_optimize {
($fmt:expr, $($fn:expr),+ $(,)?; { formatting = $formatting:expr, condition = $condition:expr }) => {{
let string_generator = move |numbers: [i64; carykh_optimize!(@count $($fn),*)]| {
let mut iter = numbers.iter();
format!(
$fmt,
$(
{
let _ = $fn;
convert(*iter.next().expect("Insufficient number of arguments"), $formatting)
},
)*
)
};
let number_generators = [$($fn),+];
let result = optimize(string_generator, number_generators, $condition, 100000);
result.map(|x| string_generator(x))
}};
($fmt:expr, $($fn:expr),+ $(,)?; { formatting = $formatting:expr }) => {{
carykh_optimize!($fmt, $($fn),+; { formatting = $formatting, condition = no_condition })
}};
($fmt:expr, $($fn:expr),+ $(,)?; { condition = $condition:expr }) => {{
let default_formatting = Formatting {
title_case: false,
spaces: true,
conjunctions: true,
commas: true,
dashes: true,
};
carykh_optimize!($fmt, $($fn),+; { formatting = default_formatting, condition = $condition })
}};
($fmt:expr, $($fn:expr),+ $(,)?) => {{
let default_formatting = Formatting {
title_case: false,
spaces: true,
conjunctions: true,
commas: true,
dashes: true,
};
carykh_optimize!($fmt, $($fn),+; { formatting = default_formatting, condition = no_condition })
}};
(@count $($rest:expr),*) => {
<[()]>::len(&[$(carykh_optimize!(@substitute $rest)),*])
};
(@substitute $_:expr) => { () };
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_carykh_optimize() {
assert_eq!(
carykh_optimize!("This sentence has {} letters.", count_letters),
Ok("This sentence has thirty-one letters.".to_string())
);
assert_eq!(
carykh_optimize!(
"This sentence has {} vowels and {} consonants. Hooray!",
count_vowels,
count_consonants
),
Ok(
"This sentence has twenty-two vowels and thirty-eight consonants. Hooray!"
.to_string()
)
);
assert_eq!(
carykh_optimize!(
"Number of vowels and consonants in this mesmerizing pie chart:\nVowels: {} percent\nConsonants: {} percent",
count_vowels,
count_consonants;
{
formatting = Formatting::all(),
condition = has_n_letters(100)
}
),
Ok("Number of vowels and consonants in this mesmerizing pie chart:\nVowels: Thirty-Four percent\nConsonants: Sixty-Six percent".to_string())
);
assert_eq!(
carykh_optimize!(
"Number of words of each length in this bar graph: ({}) two-letter-words, ({}) three-letter-words, ({}) four-letter-words, ({}) five-letter-words, ({}) six-letter-words",
count_words_of_length_n(2),
count_words_of_length_n(3),
count_words_of_length_n(4),
count_words_of_length_n(5),
count_words_of_length_n(6)
),
Ok("Number of words of each length in this bar graph: (three) two-letter-words, (three) three-letter-words, (five) four-letter-words, (eleven) five-letter-words, (eight) six-letter-words".to_string())
);
println!(
"{}",
carykh_optimize!(
"A rust macro for finding strings that contain self-referential numbers. Inspired by carykh. This description contains {} words, {} vowels, and {} consonants.",
count_words,
count_vowels,
count_consonants
)
.unwrap()
);
}
}