From 0aa3491d082731a516b9a63c7922b282910172a8 Mon Sep 17 00:00:00 2001 From: Ali Alimohammadi Date: Wed, 26 Nov 2025 16:49:42 -0800 Subject: [PATCH 1/6] Add narcissistic number finder with dynamic programming --- dynamic_programming/narcissistic_number.py | 110 +++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 dynamic_programming/narcissistic_number.py diff --git a/dynamic_programming/narcissistic_number.py b/dynamic_programming/narcissistic_number.py new file mode 100644 index 000000000000..a4ec892c1417 --- /dev/null +++ b/dynamic_programming/narcissistic_number.py @@ -0,0 +1,110 @@ +""" +Find all narcissistic numbers up to a given limit using dynamic programming. + +A narcissistic number (also known as an Armstrong number or plus perfect number) +is a number that is the sum of its own digits each raised to the power of the +number of digits. + +For example, 153 is a narcissistic number because 153 = 1^3 + 5^3 + 3^3. + +This implementation uses dynamic programming with memoization to efficiently +compute digit powers and find all narcissistic numbers up to a specified limit. + +The DP optimization caches digit^power calculations. When searching through many +numbers, the same digit power calculations occur repeatedly (e.g., 153, 351, 135 +all need 1^3, 5^3, 3^3). Memoization avoids these redundant calculations. + +Examples of narcissistic numbers: + Single digit: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + Three digit: 153, 370, 371, 407 + Four digit: 1634, 8208, 9474 + Five digit: 54748, 92727, 93084 + +Reference: https://en.wikipedia.org/wiki/Narcissistic_number +""" + + +def find_narcissistic_numbers(limit: int) -> list[int]: + """ + Find all narcissistic numbers up to the given limit using dynamic programming. + + This function uses memoization to cache digit power calculations, avoiding + redundant computations across different numbers with the same digit count. + + Args: + limit: The upper bound for searching narcissistic numbers (exclusive) + + Returns: + list[int]: A sorted list of all narcissistic numbers below the limit + + Examples: + >>> find_narcissistic_numbers(10) + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> find_narcissistic_numbers(160) + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 153] + >>> find_narcissistic_numbers(400) + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371] + >>> find_narcissistic_numbers(1000) + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407] + >>> find_narcissistic_numbers(10000) + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407, 1634, 8208, 9474] + >>> find_narcissistic_numbers(1) + [0] + >>> find_narcissistic_numbers(0) + [] + """ + if limit <= 0: + return [] + + narcissistic_nums = [] + + # Memoization: cache[num_digits][digit] = digit^num_digits + # This avoids recalculating the same power for different numbers + power_cache: dict[tuple[int, int], int] = {} + + def get_digit_power(digit: int, power: int) -> int: + """Get digit^power using memoization (DP optimization).""" + if (power, digit) not in power_cache: + power_cache[(power, digit)] = digit**power + return power_cache[(power, digit)] + + # Check each number up to the limit + for number in range(limit): + if number == 0: + narcissistic_nums.append(0) + continue + + # Count digits + num_digits = len(str(number)) + + # Calculate sum of powered digits using memoized powers + temp = number + digit_sum = 0 + while temp > 0: + digit = temp % 10 + digit_sum += get_digit_power(digit, num_digits) + temp //= 10 + + # Check if narcissistic + if digit_sum == number: + narcissistic_nums.append(number) + + return narcissistic_nums + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + # Demonstrate the dynamic programming approach + print("Finding all narcissistic numbers up to 10000:") + print("(Using memoization to cache digit power calculations)") + print() + + narcissistic_numbers = find_narcissistic_numbers(10000) + print(f"Found {len(narcissistic_numbers)} narcissistic numbers:") + print(narcissistic_numbers) + + + From 440b37ee973942471455866cece79217a87baaa6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 00:56:05 +0000 Subject: [PATCH 2/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- dynamic_programming/narcissistic_number.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/dynamic_programming/narcissistic_number.py b/dynamic_programming/narcissistic_number.py index a4ec892c1417..8eb971213a21 100644 --- a/dynamic_programming/narcissistic_number.py +++ b/dynamic_programming/narcissistic_number.py @@ -105,6 +105,3 @@ def get_digit_power(digit: int, power: int) -> int: narcissistic_numbers = find_narcissistic_numbers(10000) print(f"Found {len(narcissistic_numbers)} narcissistic numbers:") print(narcissistic_numbers) - - - From 5b1fd5ca0e9b7f4984197c23716517f018f73661 Mon Sep 17 00:00:00 2001 From: Ali Alimohammadi <41567902+AliAlimohammadi@users.noreply.github.com> Date: Mon, 15 Dec 2025 12:42:17 -0800 Subject: [PATCH 3/6] Update dynamic_programming/narcissistic_number.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- dynamic_programming/narcissistic_number.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynamic_programming/narcissistic_number.py b/dynamic_programming/narcissistic_number.py index 8eb971213a21..9ad678fd2b6f 100644 --- a/dynamic_programming/narcissistic_number.py +++ b/dynamic_programming/narcissistic_number.py @@ -58,7 +58,7 @@ def find_narcissistic_numbers(limit: int) -> list[int]: narcissistic_nums = [] - # Memoization: cache[num_digits][digit] = digit^num_digits + # Memoization: cache[(power, digit)] = digit^power # This avoids recalculating the same power for different numbers power_cache: dict[tuple[int, int], int] = {} From 4e277e02bc6d76881daaffddbbaaa5fca15cdadb Mon Sep 17 00:00:00 2001 From: Ali Alimohammadi <41567902+AliAlimohammadi@users.noreply.github.com> Date: Mon, 15 Dec 2025 12:42:44 -0800 Subject: [PATCH 4/6] Update dynamic_programming/narcissistic_number.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- dynamic_programming/narcissistic_number.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dynamic_programming/narcissistic_number.py b/dynamic_programming/narcissistic_number.py index 9ad678fd2b6f..d9db6e80a179 100644 --- a/dynamic_programming/narcissistic_number.py +++ b/dynamic_programming/narcissistic_number.py @@ -78,12 +78,12 @@ def get_digit_power(digit: int, power: int) -> int: num_digits = len(str(number)) # Calculate sum of powered digits using memoized powers - temp = number + remaining = number digit_sum = 0 - while temp > 0: - digit = temp % 10 + while remaining > 0: + digit = remaining % 10 digit_sum += get_digit_power(digit, num_digits) - temp //= 10 + remaining //= 10 # Check if narcissistic if digit_sum == number: From 5a748de24b0f8b2e32a62811306299d6bdf9e9c3 Mon Sep 17 00:00:00 2001 From: Ali Alimohammadi <41567902+AliAlimohammadi@users.noreply.github.com> Date: Mon, 15 Dec 2025 12:43:29 -0800 Subject: [PATCH 5/6] Update dynamic_programming/narcissistic_number.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- dynamic_programming/narcissistic_number.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/dynamic_programming/narcissistic_number.py b/dynamic_programming/narcissistic_number.py index d9db6e80a179..3c83ce0c8103 100644 --- a/dynamic_programming/narcissistic_number.py +++ b/dynamic_programming/narcissistic_number.py @@ -70,9 +70,6 @@ def get_digit_power(digit: int, power: int) -> int: # Check each number up to the limit for number in range(limit): - if number == 0: - narcissistic_nums.append(0) - continue # Count digits num_digits = len(str(number)) From 0dec31a31d8899166e8509217b4dfcd67c9dd514 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 20:43:49 +0000 Subject: [PATCH 6/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- dynamic_programming/narcissistic_number.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dynamic_programming/narcissistic_number.py b/dynamic_programming/narcissistic_number.py index 3c83ce0c8103..dc1c6f5a5660 100644 --- a/dynamic_programming/narcissistic_number.py +++ b/dynamic_programming/narcissistic_number.py @@ -70,7 +70,6 @@ def get_digit_power(digit: int, power: int) -> int: # Check each number up to the limit for number in range(limit): - # Count digits num_digits = len(str(number))