1#[must_use]
11pub fn digital_root(n: u64) -> u64 {
12 if n == 0 {
13 0
14 } else {
15 1 + (n - 1) % 9
16 }
17}
18
19#[must_use]
21pub fn sum_digits(mut n: u64) -> u64 {
22 let mut s = 0;
23 while n > 0 {
24 s += n % 10;
25 n /= 10;
26 }
27 s
28}
29
30#[must_use]
32pub fn string_sum(s: &str, value: impl Fn(char) -> Option<u64>) -> u64 {
33 s.chars().filter_map(value).sum()
34}
35
36#[cfg(test)]
37mod tests {
38 use super::*;
39
40 #[test]
41 fn digital_roots() {
42 assert_eq!(digital_root(0), 0);
43 assert_eq!(digital_root(9), 9);
44 assert_eq!(digital_root(18), 9);
45 assert_eq!(digital_root(31), 4); }
47
48 #[test]
49 fn string_sum_skips_unmapped_and_is_additive() {
50 let v = |c: char| c.to_digit(10).map(u64::from);
52 assert_eq!(string_sum("a1b2c3", v), 6);
53 assert_eq!(string_sum("", v), 0);
54 assert_eq!(string_sum("12", v) + string_sum("34", v), string_sum("1234", v));
55 }
56
57 #[test]
58 fn sum_digits_and_zero() {
59 assert_eq!(sum_digits(0), 0);
60 assert_eq!(sum_digits(999), 27);
61 }
62
63 use proptest::prelude::*;
64 proptest! {
65 #[test]
66 fn prop_digital_root_is_mod9_variant(n in any::<u64>()) {
67 let dr = digital_root(n);
68 if n == 0 {
69 prop_assert_eq!(dr, 0);
70 } else {
71 prop_assert!((1..=9).contains(&dr));
72 prop_assert_eq!(dr, 1 + (n - 1) % 9);
73 }
74 }
75 #[test]
76 fn prop_sum_digits_never_exceeds_input(n in 1..u64::MAX) {
77 prop_assert!(sum_digits(n) <= n);
79 prop_assert_eq!(sum_digits(n) % 9, n % 9);
80 }
81 }
82}