Skip to content

Commit 0b3a221

Browse files
authored
Merge branch 'master' into patch-2
2 parents 5cf726a + 68f876a commit 0b3a221

File tree

1 file changed

+103
-0
lines changed

1 file changed

+103
-0
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
"""
2+
Find all narcissistic numbers up to a given limit using dynamic programming.
3+
4+
A narcissistic number (also known as an Armstrong number or plus perfect number)
5+
is a number that is the sum of its own digits each raised to the power of the
6+
number of digits.
7+
8+
For example, 153 is a narcissistic number because 153 = 1^3 + 5^3 + 3^3.
9+
10+
This implementation uses dynamic programming with memoization to efficiently
11+
compute digit powers and find all narcissistic numbers up to a specified limit.
12+
13+
The DP optimization caches digit^power calculations. When searching through many
14+
numbers, the same digit power calculations occur repeatedly (e.g., 153, 351, 135
15+
all need 1^3, 5^3, 3^3). Memoization avoids these redundant calculations.
16+
17+
Examples of narcissistic numbers:
18+
Single digit: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
19+
Three digit: 153, 370, 371, 407
20+
Four digit: 1634, 8208, 9474
21+
Five digit: 54748, 92727, 93084
22+
23+
Reference: https://en.wikipedia.org/wiki/Narcissistic_number
24+
"""
25+
26+
27+
def find_narcissistic_numbers(limit: int) -> list[int]:
28+
"""
29+
Find all narcissistic numbers up to the given limit using dynamic programming.
30+
31+
This function uses memoization to cache digit power calculations, avoiding
32+
redundant computations across different numbers with the same digit count.
33+
34+
Args:
35+
limit: The upper bound for searching narcissistic numbers (exclusive)
36+
37+
Returns:
38+
list[int]: A sorted list of all narcissistic numbers below the limit
39+
40+
Examples:
41+
>>> find_narcissistic_numbers(10)
42+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
43+
>>> find_narcissistic_numbers(160)
44+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 153]
45+
>>> find_narcissistic_numbers(400)
46+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371]
47+
>>> find_narcissistic_numbers(1000)
48+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407]
49+
>>> find_narcissistic_numbers(10000)
50+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407, 1634, 8208, 9474]
51+
>>> find_narcissistic_numbers(1)
52+
[0]
53+
>>> find_narcissistic_numbers(0)
54+
[]
55+
"""
56+
if limit <= 0:
57+
return []
58+
59+
narcissistic_nums = []
60+
61+
# Memoization: cache[(power, digit)] = digit^power
62+
# This avoids recalculating the same power for different numbers
63+
power_cache: dict[tuple[int, int], int] = {}
64+
65+
def get_digit_power(digit: int, power: int) -> int:
66+
"""Get digit^power using memoization (DP optimization)."""
67+
if (power, digit) not in power_cache:
68+
power_cache[(power, digit)] = digit**power
69+
return power_cache[(power, digit)]
70+
71+
# Check each number up to the limit
72+
for number in range(limit):
73+
# Count digits
74+
num_digits = len(str(number))
75+
76+
# Calculate sum of powered digits using memoized powers
77+
remaining = number
78+
digit_sum = 0
79+
while remaining > 0:
80+
digit = remaining % 10
81+
digit_sum += get_digit_power(digit, num_digits)
82+
remaining //= 10
83+
84+
# Check if narcissistic
85+
if digit_sum == number:
86+
narcissistic_nums.append(number)
87+
88+
return narcissistic_nums
89+
90+
91+
if __name__ == "__main__":
92+
import doctest
93+
94+
doctest.testmod()
95+
96+
# Demonstrate the dynamic programming approach
97+
print("Finding all narcissistic numbers up to 10000:")
98+
print("(Using memoization to cache digit power calculations)")
99+
print()
100+
101+
narcissistic_numbers = find_narcissistic_numbers(10000)
102+
print(f"Found {len(narcissistic_numbers)} narcissistic numbers:")
103+
print(narcissistic_numbers)

0 commit comments

Comments
 (0)