Skip to content

Commit c5d0461

Browse files
authored
Merge branch 'master' into greedy-set-cover
2 parents 65524ad + 678dedb commit c5d0461

File tree

13 files changed

+256
-16
lines changed

13 files changed

+256
-16
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
runs-on: ubuntu-latest
1111
steps:
1212
- run: sudo apt-get update && sudo apt-get install -y libhdf5-dev
13-
- uses: actions/checkout@v5
13+
- uses: actions/checkout@v6
1414
- uses: astral-sh/setup-uv@v7
1515
with:
1616
enable-cache: true

.github/workflows/devcontainer_ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
build:
1313
runs-on: ubuntu-latest
1414
steps:
15-
- uses: actions/checkout@v5
15+
- uses: actions/checkout@v6
1616
- uses: devcontainers/ci@v0.3
1717
with:
1818
push: never

.github/workflows/directory_writer.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ jobs:
66
directory_writer:
77
runs-on: ubuntu-latest
88
steps:
9-
- uses: actions/checkout@v5
9+
- uses: actions/checkout@v6
1010
with:
1111
fetch-depth: 0
1212
- uses: actions/setup-python@v6

.github/workflows/project_euler.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
libxml2-dev libxslt-dev
2222
libhdf5-dev
2323
libopenblas-dev
24-
- uses: actions/checkout@v5
24+
- uses: actions/checkout@v6
2525
- uses: astral-sh/setup-uv@v7
2626
- uses: actions/setup-python@v6
2727
with:
@@ -39,7 +39,7 @@ jobs:
3939
libxml2-dev libxslt-dev
4040
libhdf5-dev
4141
libopenblas-dev
42-
- uses: actions/checkout@v5
42+
- uses: actions/checkout@v6
4343
- uses: astral-sh/setup-uv@v7
4444
- uses: actions/setup-python@v6
4545
with:

.github/workflows/ruff.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ jobs:
1111
ruff:
1212
runs-on: ubuntu-latest
1313
steps:
14-
- uses: actions/checkout@v5
14+
- uses: actions/checkout@v6
1515
- uses: astral-sh/setup-uv@v7
1616
- run: uvx ruff check --output-format=github .

.github/workflows/sphinx.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
libxml2-dev libxslt-dev
3333
libhdf5-dev
3434
libopenblas-dev
35-
- uses: actions/checkout@v5
35+
- uses: actions/checkout@v6
3636
- uses: astral-sh/setup-uv@v7
3737
- uses: actions/setup-python@v6
3838
with:

.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ repos:
1414
- id: requirements-txt-fixer
1515

1616
- repo: https://github.com/MarcoGorelli/auto-walrus
17-
rev: 0.3.4
17+
rev: 0.4.1
1818
hooks:
1919
- id: auto-walrus
2020

2121
- repo: https://github.com/astral-sh/ruff-pre-commit
22-
rev: v0.14.3
22+
rev: v0.14.14
2323
hooks:
2424
- id: ruff-check
2525
- id: ruff-format
@@ -32,7 +32,7 @@ repos:
3232
- tomli
3333

3434
- repo: https://github.com/tox-dev/pyproject-fmt
35-
rev: v2.11.0
35+
rev: v2.12.1
3636
hooks:
3737
- id: pyproject-fmt
3838

@@ -50,7 +50,7 @@ repos:
5050
- id: validate-pyproject
5151

5252
- repo: https://github.com/pre-commit/mirrors-mypy
53-
rev: v1.18.2
53+
rev: v1.19.1
5454
hooks:
5555
- id: mypy
5656
args:

DIRECTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@
398398
* [Minimum Squares To Represent A Number](dynamic_programming/minimum_squares_to_represent_a_number.py)
399399
* [Minimum Steps To One](dynamic_programming/minimum_steps_to_one.py)
400400
* [Minimum Tickets Cost](dynamic_programming/minimum_tickets_cost.py)
401+
* [Narcissistic Number](dynamic_programming/narcissistic_number.py)
401402
* [Optimal Binary Search Tree](dynamic_programming/optimal_binary_search_tree.py)
402403
* [Palindrome Partitioning](dynamic_programming/palindrome_partitioning.py)
403404
* [Range Sum Query](dynamic_programming/range_sum_query.py)
@@ -880,6 +881,7 @@
880881
* [Quine](other/quine.py)
881882
* [Scoring Algorithm](other/scoring_algorithm.py)
882883
* [Sdes](other/sdes.py)
884+
* [Sliding Window Maximum](other/sliding_window_maximum.py)
883885
* [Tower Of Hanoi](other/tower_of_hanoi.py)
884886
* [Word Search](other/word_search.py)
885887

ciphers/caesar_cipher.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def encrypt(input_string: str, key: int, alphabet: str | None = None) -> str:
4545
And our shift is ``2``
4646
4747
We can then encode the message, one letter at a time. ``H`` would become ``J``,
48-
since ``J`` is two letters away, and so on. If the shift is ever two large, or
48+
since ``J`` is two letters away, and so on. If the shift is ever too large, or
4949
our letter is at the end of the alphabet, we just start at the beginning
5050
(``Z`` would shift to ``a`` then ``b`` and so on).
5151
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)