pub const PYTHAGOREAN_MASTERS: &[u32] = &[11, 22, 33];
pub const CABALISTIC_MASTERS: &[u32] = &[11, 22];
pub const KARMIC_DEBTS: &[u32] = &[13, 14, 16, 19];
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CalculatedNumber {
pub raw: u32,
pub value: u32,
pub is_master: bool,
pub steps: Vec<u32>,
}
impl CalculatedNumber {
#[must_use]
pub fn passes_through(&self, n: u32) -> bool {
self.steps.contains(&n)
}
}
#[must_use]
pub fn digit_sum(mut n: u32) -> u32 {
let mut sum = 0;
while n > 0 {
sum += n % 10;
n /= 10;
}
sum
}
#[must_use]
pub fn reduce(raw: u32, masters: &[u32]) -> CalculatedNumber {
let mut steps = vec![raw];
let mut cur = raw;
while cur > 9 && !masters.contains(&cur) {
cur = digit_sum(cur);
steps.push(cur);
}
let is_master = cur > 9 && masters.contains(&cur);
CalculatedNumber {
raw,
value: cur,
is_master,
steps,
}
}
#[must_use]
pub fn reduce_single(raw: u32) -> CalculatedNumber {
reduce(raw, &[])
}
#[must_use]
pub fn debt_of(cn: &CalculatedNumber) -> Option<u32> {
cn.steps.iter().copied().find(|s| KARMIC_DEBTS.contains(s))
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KarmicDebt {
pub position: &'static str,
pub debt: u32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn digit_sum_adds_digits() {
assert_eq!(digit_sum(0), 0);
assert_eq!(digit_sum(28), 10);
assert_eq!(digit_sum(499), 22);
assert_eq!(digit_sum(999_999), 54);
}
#[test]
fn reduce_to_single_digit() {
let cn = reduce_single(28);
assert_eq!(cn.value, 1);
assert_eq!(cn.steps, vec![28, 10, 1]);
assert!(!cn.is_master);
}
#[test]
fn single_digit_input_is_left_untouched() {
let cn = reduce_single(7);
assert_eq!(cn.value, 7);
assert_eq!(cn.steps, vec![7]);
assert!(!cn.is_master);
}
#[test]
fn reduce_preserves_master_numbers() {
let cn = reduce(499, CABALISTIC_MASTERS);
assert_eq!(cn.value, 22);
assert!(cn.is_master);
let cn = reduce(38, PYTHAGOREAN_MASTERS);
assert_eq!(cn.value, 11);
assert!(cn.is_master);
assert_eq!(reduce(33, PYTHAGOREAN_MASTERS).value, 33);
assert_eq!(reduce(33, CABALISTIC_MASTERS).value, 6);
}
#[test]
fn reduce_single_never_keeps_masters() {
let cn = reduce_single(38);
assert_eq!(cn.value, 2);
assert!(!cn.is_master);
}
#[test]
fn detects_karmic_debt_in_trail() {
for debt in [13u32, 14, 16, 19] {
let cn = reduce(debt, CABALISTIC_MASTERS);
assert_eq!(debt_of(&cn), Some(debt), "debt {debt} should be detected");
assert!(cn.passes_through(debt));
}
let clean = reduce(25, PYTHAGOREAN_MASTERS);
assert_eq!(debt_of(&clean), None);
assert!(!clean.passes_through(16));
}
}