use crate::alphabet::{decode, is_vowel, Accent};
use crate::error::NumerologyError;
use crate::System;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LetterValue {
pub original: char,
pub base: char,
pub accent: Option<Accent>,
pub is_vowel: bool,
pub value: u8,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NameToken {
pub letters: Vec<LetterValue>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NameAnalysis {
pub system: System,
pub tokens: Vec<NameToken>,
}
impl NameAnalysis {
pub fn new(name: &str, system: System) -> Result<Self, NumerologyError> {
let mut tokens = Vec::new();
for word in name.split(|c: char| c.is_whitespace() || c == '-') {
let mut letters = Vec::new();
for ch in word.chars() {
if let Some(d) = decode(ch) {
letters.push(LetterValue {
original: ch,
base: d.base,
accent: d.accent,
is_vowel: is_vowel(d.base) && !d.cedilla,
value: system.letter_value(&d),
});
}
}
if !letters.is_empty() {
tokens.push(NameToken { letters });
}
}
if tokens.is_empty() {
return Err(NumerologyError::EmptyName);
}
Ok(Self { system, tokens })
}
pub fn letters(&self) -> impl Iterator<Item = &LetterValue> {
self.tokens.iter().flat_map(|t| t.letters.iter())
}
#[must_use]
pub fn total_sum(&self) -> u32 {
self.letters().map(|l| u32::from(l.value)).sum()
}
#[must_use]
pub fn vowel_sum(&self) -> u32 {
self.letters().filter(|l| l.is_vowel).map(|l| u32::from(l.value)).sum()
}
#[must_use]
pub fn consonant_sum(&self) -> u32 {
self.letters().filter(|l| !l.is_vowel).map(|l| u32::from(l.value)).sum()
}
#[must_use]
pub fn initials_sum(&self) -> u32 {
self
.tokens
.iter()
.filter_map(|t| t.letters.first())
.map(|l| u32::from(l.value))
.sum()
}
#[must_use]
pub fn digit_frequency(&self) -> [u32; 10] {
let mut freq = [0u32; 10];
for l in self.letters() {
let v = usize::from(l.value);
if v <= 9 {
freq[v] += 1;
}
}
freq
}
#[must_use]
pub fn base_values(&self) -> Vec<u8> {
self.letters().map(|l| l.value).collect()
}
}
#[must_use]
pub fn missing_digits(freq: &[u32; 10], max: u8) -> Vec<u8> {
(1u8..=max).filter(|&d| freq[usize::from(d)] == 0).collect()
}
#[must_use]
pub fn most_frequent(freq: &[u32; 10]) -> Vec<u8> {
let max = (1u8..=9).map(|d| freq[usize::from(d)]).max().unwrap_or(0);
if max == 0 {
return Vec::new();
}
(1u8..=9).filter(|&d| freq[usize::from(d)] == max).collect()
}
#[must_use]
pub fn distinct_present(freq: &[u32; 10]) -> u32 {
let count = (1u8..=9).filter(|&d| freq[usize::from(d)] > 0).count();
u32::try_from(count).unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn splits_tokens_and_skips_punctuation() {
let a = NameAnalysis::new("Ada Lovelace-King!", System::Cabalistic).unwrap();
assert_eq!(a.tokens.len(), 3);
let b = NameAnalysis::new("Karen Spärck Jones", System::Cabalistic).unwrap();
assert_eq!(b.tokens.len(), 3);
}
#[test]
fn empty_name_errors() {
assert_eq!(
NameAnalysis::new(" 123 !!", System::Pythagorean),
Err(NumerologyError::EmptyName)
);
assert_eq!(
NameAnalysis::new("", System::Cabalistic),
Err(NumerologyError::EmptyName)
);
}
#[test]
fn sums_split_into_vowels_and_consonants() {
let a = NameAnalysis::new("Ada Lovelace", System::Cabalistic).unwrap();
assert_eq!(a.total_sum(), 39);
assert_eq!(a.vowel_sum(), 20);
assert_eq!(a.consonant_sum(), 19);
assert_eq!(a.total_sum(), a.vowel_sum() + a.consonant_sum());
}
#[test]
fn initials_sum_uses_first_letter_of_each_token() {
let a = NameAnalysis::new("Ada Lovelace", System::Cabalistic).unwrap();
assert_eq!(a.initials_sum(), 4);
}
#[test]
fn base_values_preserve_letter_order() {
let a = NameAnalysis::new("Ada Lovelace", System::Cabalistic).unwrap();
assert_eq!(a.base_values(), vec![1, 4, 1, 3, 7, 6, 5, 3, 1, 3, 5]);
}
#[test]
fn frequency_helpers() {
let a = NameAnalysis::new("Ada Lovelace", System::Cabalistic).unwrap();
let freq = a.digit_frequency();
assert_eq!(freq[1], 3);
assert_eq!(freq[3], 3);
assert_eq!(missing_digits(&freq, 9), vec![2, 8, 9]);
assert_eq!(most_frequent(&freq), vec![1, 3]);
assert_eq!(distinct_present(&freq), 6);
}
#[test]
fn most_frequent_can_return_a_tie() {
let a = NameAnalysis::new("Ada Lovelace", System::Cabalistic).unwrap();
assert_eq!(most_frequent(&a.digit_frequency()), vec![1, 3]);
}
}