# Digital root. (2.00)
# @see j9kUG0
import sys
import unittest
import timeit
import random
# Formal definition.
# A natural number n is a digital root if it is a fixed point for digit_sum
# (which occurs if n equals digit_sum(n)).
# @see en.wikipedia.org/wiki/Digital_root
def digital_root_1(n: int) -> int:
if n < 0:
raise ValueError('n must be non-negative')
return fixed_point(digit_sum, n, lambda x, y: x == y)
def fixed_point(f, guess, equal):
while not equal(guess, result := f(guess)):
guess = result
return guess
def digit_sum(n: int) -> int:
a = 0
while n > 0:
n, a = n // 10, a + n % 10
return a
# Direct formulas.
def digital_root_2(n: int) -> int:
if n < 0:
raise ValueError('n must be non-negative')
if n == 0:
return 0
n %= 9
return n if n != 0 else 9
def digital_root_3(n: int) -> int:
if n < 0:
raise ValueError('n must be non-negative')
if n == 0:
return 0
return 1 + (n - 1) % 9
def digital_root_4(n: int) -> int:
if n < 0:
raise ValueError('n must be non-negative')
if n == 0:
return 0
return n - (n - 1) // 9 * 9 # flooring division
# Test.
class TestDigitalRoot(unittest.TestCase):
def _test_digital_root(self, f):
with self.assertRaisesRegex(ValueError, 'negative'):
f(-1)
self.assertEqual(f(0), 0)
for i in range(1, 10):
for j in range(3):
self.assertEqual(f(i+9*j), i)
def test_digital_root_1(self):
self._test_digital_root(digital_root_1)
def test_digital_root_2(self):
self._test_digital_root(digital_root_2)
def test_digital_root_3(self):
self._test_digital_root(digital_root_3)
def test_digital_root_4(self):
self._test_digital_root(digital_root_4)
def benchmark_digital_root(n):
def benchmark(f):
def bench():
for i in range(n):
f(10**(i%10)*2-1) # 1, 19, 199, ...
return f.__name__, timeit.timeit(bench, number=1)
fs = [
digital_root_1,
digital_root_2,
digital_root_3,
digital_root_4
]
for name, t in map(benchmark, fs):
print(f'{name}: {t:.6f}s')
if __name__ == '__main__':
print('Running unit tests ...', file=sys.stderr)
unittest.main(verbosity=2, exit=False)
print('\nBenchmarking ...')
benchmark_digital_root(10000)