Skip to content

Commit d8d58fa

Browse files
committed
add kaprekar numbers implementation with doctests
1 parent f5988cc commit d8d58fa

1 file changed

Lines changed: 173 additions & 0 deletions

File tree

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
"""
2+
A Kaprekar number for a given base is a non-negative integer, the
3+
representation of whose square in that base can be split into two parts that
4+
add up to the original number again.
5+
6+
For example, 45 is a Kaprekar number because:
7+
45^2 = 2025 -> split as 20 + 25 = 45 ✓
8+
9+
9 is a Kaprekar number because:
10+
9^2 = 81 -> split as 8 + 1 = 9 ✓
11+
12+
297 is a Kaprekar number because:
13+
297^2 = 88209 -> split as 88 + 209 = 297 ✓
14+
15+
Note: The number 1 is a trivial Kaprekar number in every base.
16+
17+
Reference: https://en.wikipedia.org/wiki/Kaprekar_number
18+
OEIS sequence: https://oeis.org/A006886
19+
"""
20+
21+
22+
def is_kaprekar_number(number: int, base: int = 10) -> bool:
23+
"""
24+
Return True if *number* is a Kaprekar number in the given *base*,
25+
False otherwise.
26+
27+
A Kaprekar number n satisfies: let s = n^2 represented in *base*.
28+
Split s into a right part r (non-empty) and a left part l such that
29+
l + r == n. We try every valid split position.
30+
31+
Parameters
32+
----------
33+
number : int
34+
A positive integer to test.
35+
base : int
36+
The numeric base to use (default 10). Must be >= 2.
37+
38+
Raises
39+
------
40+
ValueError
41+
If *number* is not a positive integer or *base* < 2.
42+
43+
>>> is_kaprekar_number(1)
44+
True
45+
>>> is_kaprekar_number(9)
46+
True
47+
>>> is_kaprekar_number(45)
48+
True
49+
>>> is_kaprekar_number(55)
50+
True
51+
>>> is_kaprekar_number(99)
52+
True
53+
>>> is_kaprekar_number(297)
54+
True
55+
>>> is_kaprekar_number(703)
56+
True
57+
>>> is_kaprekar_number(999)
58+
True
59+
>>> is_kaprekar_number(2)
60+
False
61+
>>> is_kaprekar_number(10)
62+
False
63+
>>> is_kaprekar_number(0)
64+
Traceback (most recent call last):
65+
...
66+
ValueError: number=0 must be a positive integer
67+
>>> is_kaprekar_number(-9)
68+
Traceback (most recent call last):
69+
...
70+
ValueError: number=-9 must be a positive integer
71+
>>> is_kaprekar_number(9.0)
72+
Traceback (most recent call last):
73+
...
74+
ValueError: number=9.0 must be a positive integer
75+
>>> is_kaprekar_number(9, base=1)
76+
Traceback (most recent call last):
77+
...
78+
ValueError: base=1 must be an integer >= 2
79+
"""
80+
if not isinstance(number, int) or isinstance(number, bool) or number < 1:
81+
msg = f"{number=} must be a positive integer"
82+
raise ValueError(msg)
83+
if not isinstance(base, int) or isinstance(base, bool) or base < 2:
84+
msg = f"{base=} must be an integer >= 2"
85+
raise ValueError(msg)
86+
87+
# 1 is a trivial Kaprekar number in every base (1^2 = 1, special case)
88+
if number == 1:
89+
return True
90+
91+
square = number * number
92+
93+
# Convert square to a list of digits in the given base (most-significant first)
94+
digits: list[int] = []
95+
temp = square
96+
while temp > 0:
97+
digits.append(temp % base)
98+
temp //= base
99+
digits.reverse()
100+
101+
num_digits = len(digits)
102+
103+
# Try every split position.
104+
# The right part must be NON-EMPTY and NON-ZERO (right == 0 gives trivial
105+
# splits like 10^2=100 -> 10+0=10 which are excluded by convention).
106+
for split in range(1, num_digits):
107+
# Left part: digits[0 : split]
108+
left = 0
109+
for d in digits[:split]:
110+
left = left * base + d
111+
112+
# Right part: digits[split:]
113+
right = 0
114+
for d in digits[split:]:
115+
right = right * base + d
116+
117+
if right > 0 and left + right == number:
118+
return True
119+
120+
return False
121+
122+
123+
def get_kaprekar_numbers(limit: int, base: int = 10) -> list[int]:
124+
"""
125+
Return a list of all Kaprekar numbers in the range [1, limit] for the
126+
given *base*.
127+
128+
Parameters
129+
----------
130+
limit : int
131+
Upper bound (inclusive) for the search range. Must be >= 1.
132+
base : int
133+
The numeric base to use (default 10). Must be >= 2.
134+
135+
Raises
136+
------
137+
ValueError
138+
If *limit* < 1 or *base* < 2.
139+
140+
>>> get_kaprekar_numbers(1000)
141+
[1, 9, 45, 55, 99, 297, 703, 999]
142+
>>> get_kaprekar_numbers(100)
143+
[1, 9, 45, 55, 99]
144+
>>> get_kaprekar_numbers(10)
145+
[1, 9]
146+
>>> get_kaprekar_numbers(1)
147+
[1]
148+
>>> get_kaprekar_numbers(0)
149+
Traceback (most recent call last):
150+
...
151+
ValueError: limit=0 must be a positive integer (>= 1)
152+
>>> get_kaprekar_numbers(10, base=1)
153+
Traceback (most recent call last):
154+
...
155+
ValueError: base=1 must be an integer >= 2
156+
"""
157+
if not isinstance(limit, int) or isinstance(limit, bool) or limit < 1:
158+
msg = f"{limit=} must be a positive integer (>= 1)"
159+
raise ValueError(msg)
160+
if not isinstance(base, int) or isinstance(base, bool) or base < 2:
161+
msg = f"{base=} must be an integer >= 2"
162+
raise ValueError(msg)
163+
164+
return [n for n in range(1, limit + 1) if is_kaprekar_number(n, base)]
165+
166+
167+
if __name__ == "__main__":
168+
import doctest
169+
170+
doctest.testmod()
171+
172+
print("Kaprekar numbers up to 10,000:")
173+
print(get_kaprekar_numbers(10_000))

0 commit comments

Comments
 (0)