fork download
  1. # Digit sum.
  2. # In mathematics, the digit sum of a natural number in a given number base
  3. # is the sum of all its digits. For example, the digit sum of the decimal
  4. # number 9045 would be 9 + 0 + 4 + 5 = 18.
  5. # @see en.wikipedia.org/wiki/Digit_sum
  6.  
  7. import sys
  8. import functools
  9. import unittest
  10. import timeit
  11.  
  12. def digit_sum_1(n: int) -> int:
  13. if n < 0:
  14. raise ValueError('n must be non-negative')
  15. a = 0
  16. while n > 0:
  17. n, a = n // 10, a + n % 10
  18. return a
  19.  
  20. def digit_sum_2(n: int) -> int:
  21. if n < 0:
  22. raise ValueError('n must be non-negative')
  23. return sum(map(int, str(n)))
  24.  
  25. def digit_sum_3(n: int) -> int:
  26. if n < 0:
  27. raise ValueError('n must be non-negative')
  28. return functools.reduce(lambda a, x: a + x, map(int, str(n)))
  29.  
  30. def digit_sum_4(n: int) -> int:
  31. if n < 0:
  32. raise ValueError('n must be non-negative')
  33. def loop(x):
  34. if x == 0:
  35. return 0
  36. else:
  37. return x % 10 + loop(x // 10)
  38. return loop(n)
  39.  
  40. def digit_sum_5(n: int) -> int:
  41. if n < 0:
  42. raise ValueError('n must be non-negative')
  43. def loop(x, a):
  44. if x == 0:
  45. return a
  46. else:
  47. return loop(x // 10, a + x % 10)
  48. return loop(n, 0)
  49.  
  50. # Test.
  51.  
  52. class TestDigitSum(unittest.TestCase):
  53. def _test_digit_sum(self, f):
  54. with self.assertRaisesRegex(ValueError, 'negative'):
  55. f(-1)
  56. self.assertEqual(f(0), 0)
  57. self.assertEqual(f(123456789), 45)
  58. for i in range(10):
  59. self.assertEqual(f(10**i*2-1), 1+9*i) # 1, 19, 199, ...
  60.  
  61. def test_digit_sum_1(self):
  62. self._test_digit_sum(digit_sum_1)
  63.  
  64. def test_digit_sum_2(self):
  65. self._test_digit_sum(digit_sum_2)
  66.  
  67. def test_digit_sum_3(self):
  68. self._test_digit_sum(digit_sum_3)
  69.  
  70. def test_digit_sum_4(self):
  71. self._test_digit_sum(digit_sum_4)
  72.  
  73. def test_digit_sum_5(self):
  74. self._test_digit_sum(digit_sum_5)
  75.  
  76. def benchmark_digit_sum(n):
  77. def benchmark(f):
  78. def bench():
  79. for i in range(n):
  80. f(10**(i%10)*2-1) # 1, 19, 199, ...
  81. return f.__name__, timeit.timeit(bench, number=1)
  82.  
  83. fs = [
  84. digit_sum_1,
  85. digit_sum_2,
  86. digit_sum_3,
  87. digit_sum_4,
  88. digit_sum_5
  89. ]
  90.  
  91. for name, t in map(benchmark, fs):
  92. print(f'{name}: {t:.6f}s')
  93.  
  94. if __name__ == '__main__':
  95. print('Running unit tests ...', file=sys.stderr)
  96. unittest.main(verbosity=2, exit=False)
  97. print('\nBenchmarking ...')
  98. benchmark_digit_sum(10000)
Success #stdin #stdout #stderr 0.47s 21908KB
stdin
Standard input is empty
stdout
Benchmarking ...
digit_sum_1: 0.009938s
digit_sum_2: 0.021925s
digit_sum_3: 0.034071s
digit_sum_4: 0.017713s
digit_sum_5: 0.010308s
stderr
Running unit tests ...
test_digit_sum_1 (__main__.TestDigitSum.test_digit_sum_1) ... ok
test_digit_sum_2 (__main__.TestDigitSum.test_digit_sum_2) ... ok
test_digit_sum_3 (__main__.TestDigitSum.test_digit_sum_3) ... ok
test_digit_sum_4 (__main__.TestDigitSum.test_digit_sum_4) ... ok
test_digit_sum_5 (__main__.TestDigitSum.test_digit_sum_5) ... ok

----------------------------------------------------------------------
Ran 5 tests in 0.001s

OK