Python/project_euler/problem_145/sol1.py
Manpreet Singh 1969259868
Performance: 80% faster Project Euler 145 (#10445)
* Performance: 80% faster Project Euler145

* Added timeit benchmark

* >>> slow_solution() doctest
2023-10-14 19:35:01 +02:00

153 lines
4.1 KiB
Python

"""
Project Euler problem 145: https://projecteuler.net/problem=145
Author: Vineet Rao, Maxim Smolskiy
Problem statement:
Some positive integers n have the property that the sum [ n + reverse(n) ]
consists entirely of odd (decimal) digits.
For instance, 36 + 63 = 99 and 409 + 904 = 1313.
We will call such numbers reversible; so 36, 63, 409, and 904 are reversible.
Leading zeroes are not allowed in either n or reverse(n).
There are 120 reversible numbers below one-thousand.
How many reversible numbers are there below one-billion (10^9)?
"""
EVEN_DIGITS = [0, 2, 4, 6, 8]
ODD_DIGITS = [1, 3, 5, 7, 9]
def slow_reversible_numbers(
remaining_length: int, remainder: int, digits: list[int], length: int
) -> int:
"""
Count the number of reversible numbers of given length.
Iterate over possible digits considering parity of current sum remainder.
>>> slow_reversible_numbers(1, 0, [0], 1)
0
>>> slow_reversible_numbers(2, 0, [0] * 2, 2)
20
>>> slow_reversible_numbers(3, 0, [0] * 3, 3)
100
"""
if remaining_length == 0:
if digits[0] == 0 or digits[-1] == 0:
return 0
for i in range(length // 2 - 1, -1, -1):
remainder += digits[i] + digits[length - i - 1]
if remainder % 2 == 0:
return 0
remainder //= 10
return 1
if remaining_length == 1:
if remainder % 2 == 0:
return 0
result = 0
for digit in range(10):
digits[length // 2] = digit
result += slow_reversible_numbers(
0, (remainder + 2 * digit) // 10, digits, length
)
return result
result = 0
for digit1 in range(10):
digits[(length + remaining_length) // 2 - 1] = digit1
if (remainder + digit1) % 2 == 0:
other_parity_digits = ODD_DIGITS
else:
other_parity_digits = EVEN_DIGITS
for digit2 in other_parity_digits:
digits[(length - remaining_length) // 2] = digit2
result += slow_reversible_numbers(
remaining_length - 2,
(remainder + digit1 + digit2) // 10,
digits,
length,
)
return result
def slow_solution(max_power: int = 9) -> int:
"""
To evaluate the solution, use solution()
>>> slow_solution(3)
120
>>> slow_solution(6)
18720
>>> slow_solution(7)
68720
"""
result = 0
for length in range(1, max_power + 1):
result += slow_reversible_numbers(length, 0, [0] * length, length)
return result
def reversible_numbers(
remaining_length: int, remainder: int, digits: list[int], length: int
) -> int:
"""
Count the number of reversible numbers of given length.
Iterate over possible digits considering parity of current sum remainder.
>>> reversible_numbers(1, 0, [0], 1)
0
>>> reversible_numbers(2, 0, [0] * 2, 2)
20
>>> reversible_numbers(3, 0, [0] * 3, 3)
100
"""
# There exist no reversible 1, 5, 9, 13 (ie. 4k+1) digit numbers
if (length - 1) % 4 == 0:
return 0
return slow_reversible_numbers(length, 0, [0] * length, length)
def solution(max_power: int = 9) -> int:
"""
To evaluate the solution, use solution()
>>> solution(3)
120
>>> solution(6)
18720
>>> solution(7)
68720
"""
result = 0
for length in range(1, max_power + 1):
result += reversible_numbers(length, 0, [0] * length, length)
return result
def benchmark() -> None:
"""
Benchmarks
"""
# Running performance benchmarks...
# slow_solution : 292.9300301000003
# solution : 54.90970860000016
from timeit import timeit
print("Running performance benchmarks...")
print(f"slow_solution : {timeit('slow_solution()', globals=globals(), number=10)}")
print(f"solution : {timeit('solution()', globals=globals(), number=10)}")
if __name__ == "__main__":
print(f"Solution : {solution()}")
benchmark()
# for i in range(1, 15):
# print(f"{i}. {reversible_numbers(i, 0, [0]*i, i)}")