numclassify 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- numclassify/__init__.py +63 -0
- numclassify/__main__.py +4 -0
- numclassify/_core/__init__.py +0 -0
- numclassify/_core/combinatorial.py +392 -0
- numclassify/_core/digital.py +403 -0
- numclassify/_core/divisors.py +756 -0
- numclassify/_core/figurate.py +357 -0
- numclassify/_core/number_theory.py +533 -0
- numclassify/_core/powers.py +349 -0
- numclassify/_core/primes.py +2100 -0
- numclassify/_core/recreational.py +245 -0
- numclassify/_core/sequences.py +488 -0
- numclassify/_registry.py +417 -0
- numclassify/cli.py +525 -0
- numclassify-0.1.0.dist-info/METADATA +220 -0
- numclassify-0.1.0.dist-info/RECORD +19 -0
- numclassify-0.1.0.dist-info/WHEEL +4 -0
- numclassify-0.1.0.dist-info/entry_points.txt +2 -0
- numclassify-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
"""
|
|
2
|
+
numclassify/_core/divisors.py
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
Divisor-based number classification functions.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import math
|
|
9
|
+
from typing import List, Set
|
|
10
|
+
|
|
11
|
+
from numclassify._registry import register
|
|
12
|
+
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
# Helpers (NOT registered)
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
def proper_divisors(n: int) -> List[int]:
|
|
18
|
+
"""Return all divisors of n except n itself."""
|
|
19
|
+
if n <= 1:
|
|
20
|
+
return []
|
|
21
|
+
divs = [1]
|
|
22
|
+
i = 2
|
|
23
|
+
while i * i <= n:
|
|
24
|
+
if n % i == 0:
|
|
25
|
+
divs.append(i)
|
|
26
|
+
if i != n // i:
|
|
27
|
+
divs.append(n // i)
|
|
28
|
+
i += 1
|
|
29
|
+
return sorted(divs)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def sigma(n: int) -> int:
|
|
33
|
+
"""Return the sum of all divisors of n, including n itself."""
|
|
34
|
+
if n <= 0:
|
|
35
|
+
return 0
|
|
36
|
+
total = 0
|
|
37
|
+
i = 1
|
|
38
|
+
while i * i <= n:
|
|
39
|
+
if n % i == 0:
|
|
40
|
+
total += i
|
|
41
|
+
if i != n // i:
|
|
42
|
+
total += n // i
|
|
43
|
+
i += 1
|
|
44
|
+
return total
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def num_divisors(n: int) -> int:
|
|
48
|
+
"""Return the count of all divisors of n."""
|
|
49
|
+
if n <= 0:
|
|
50
|
+
return 0
|
|
51
|
+
count = 0
|
|
52
|
+
i = 1
|
|
53
|
+
while i * i <= n:
|
|
54
|
+
if n % i == 0:
|
|
55
|
+
count += 2 if i != n // i else 1
|
|
56
|
+
i += 1
|
|
57
|
+
return count
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def prime_factors_set(n: int) -> Set[int]:
|
|
61
|
+
"""Return the set of unique prime factors of n."""
|
|
62
|
+
factors: Set[int] = set()
|
|
63
|
+
if n <= 1:
|
|
64
|
+
return factors
|
|
65
|
+
while n % 2 == 0:
|
|
66
|
+
factors.add(2)
|
|
67
|
+
n //= 2
|
|
68
|
+
f = 3
|
|
69
|
+
while f * f <= n:
|
|
70
|
+
while n % f == 0:
|
|
71
|
+
factors.add(f)
|
|
72
|
+
n //= f
|
|
73
|
+
f += 2
|
|
74
|
+
if n > 1:
|
|
75
|
+
factors.add(n)
|
|
76
|
+
return factors
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _factorization(n: int) -> List[tuple]:
|
|
80
|
+
"""Return list of (prime, exponent) pairs for n."""
|
|
81
|
+
result = []
|
|
82
|
+
if n <= 1:
|
|
83
|
+
return result
|
|
84
|
+
f = 2
|
|
85
|
+
while f * f <= n:
|
|
86
|
+
if n % f == 0:
|
|
87
|
+
exp = 0
|
|
88
|
+
while n % f == 0:
|
|
89
|
+
exp += 1
|
|
90
|
+
n //= f
|
|
91
|
+
result.append((f, exp))
|
|
92
|
+
f += 1
|
|
93
|
+
if n > 1:
|
|
94
|
+
result.append((n, 1))
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _is_perfect_power(n: int) -> bool:
|
|
99
|
+
"""Return True if n = k^m for some k>1, m>1."""
|
|
100
|
+
if n <= 1:
|
|
101
|
+
return False
|
|
102
|
+
for exp in range(2, n.bit_length() + 1):
|
|
103
|
+
root = round(n ** (1 / exp))
|
|
104
|
+
for r in (root - 1, root, root + 1):
|
|
105
|
+
if r > 1 and r ** exp == n:
|
|
106
|
+
return True
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
# Precompute untouchable numbers up to 10000 via sieve
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
def _build_untouchable_set(limit: int = 10000) -> Set[int]:
|
|
115
|
+
"""Sieve: find all values that ARE proper-divisor-sums, complement is untouchable."""
|
|
116
|
+
reachable: Set[int] = set()
|
|
117
|
+
for k in range(2, limit + 1):
|
|
118
|
+
s = sum(proper_divisors(k))
|
|
119
|
+
if s <= limit:
|
|
120
|
+
reachable.add(s)
|
|
121
|
+
# 1 is never a proper divisor sum of anything >= 2 (pd(1)=[], pd(prime)=[1])
|
|
122
|
+
# Actually pd(p)=[1] for prime p, so 1 is reachable. Untouchable = not reachable.
|
|
123
|
+
return set(range(1, limit + 1)) - reachable
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
_UNTOUCHABLE: Set[int] = _build_untouchable_set(10000)
|
|
127
|
+
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
# Registered classifiers
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
@register(name="Perfect", category="divisors", oeis="A000396",
|
|
133
|
+
description="Equal to the sum of its proper divisors.")
|
|
134
|
+
def is_perfect(n: int) -> bool:
|
|
135
|
+
"""Return True if n equals the sum of its proper divisors.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
n : int
|
|
140
|
+
Positive integer.
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
bool
|
|
145
|
+
|
|
146
|
+
Examples
|
|
147
|
+
--------
|
|
148
|
+
>>> is_perfect(6)
|
|
149
|
+
True
|
|
150
|
+
>>> is_perfect(28)
|
|
151
|
+
True
|
|
152
|
+
>>> is_perfect(12)
|
|
153
|
+
False
|
|
154
|
+
"""
|
|
155
|
+
if n < 2:
|
|
156
|
+
return False
|
|
157
|
+
return sigma(n) - n == n
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@register(name="Abundant", category="divisors", oeis="A005101",
|
|
161
|
+
description="Proper divisor sum exceeds n.")
|
|
162
|
+
def is_abundant(n: int) -> bool:
|
|
163
|
+
"""Return True if the sum of proper divisors of n exceeds n.
|
|
164
|
+
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
n : int
|
|
168
|
+
|
|
169
|
+
Returns
|
|
170
|
+
-------
|
|
171
|
+
bool
|
|
172
|
+
|
|
173
|
+
Examples
|
|
174
|
+
--------
|
|
175
|
+
>>> is_abundant(12)
|
|
176
|
+
True
|
|
177
|
+
>>> is_abundant(8)
|
|
178
|
+
False
|
|
179
|
+
"""
|
|
180
|
+
if n < 1:
|
|
181
|
+
return False
|
|
182
|
+
return sigma(n) - n > n
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@register(name="Deficient", category="divisors", oeis="A005100",
|
|
186
|
+
description="Proper divisor sum is less than n.")
|
|
187
|
+
def is_deficient(n: int) -> bool:
|
|
188
|
+
"""Return True if the sum of proper divisors of n is less than n.
|
|
189
|
+
|
|
190
|
+
Parameters
|
|
191
|
+
----------
|
|
192
|
+
n : int
|
|
193
|
+
|
|
194
|
+
Returns
|
|
195
|
+
-------
|
|
196
|
+
bool
|
|
197
|
+
|
|
198
|
+
Examples
|
|
199
|
+
--------
|
|
200
|
+
>>> is_deficient(8)
|
|
201
|
+
True
|
|
202
|
+
>>> is_deficient(6)
|
|
203
|
+
False
|
|
204
|
+
"""
|
|
205
|
+
if n < 1:
|
|
206
|
+
return False
|
|
207
|
+
return sigma(n) - n < n
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@register(name="Semiperfect", category="divisors", oeis="A005835",
|
|
211
|
+
description="Equal to the sum of some subset of its proper divisors.")
|
|
212
|
+
def is_semiperfect(n: int) -> bool:
|
|
213
|
+
"""Return True if n equals the sum of some subset of its proper divisors.
|
|
214
|
+
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
n : int
|
|
218
|
+
|
|
219
|
+
Returns
|
|
220
|
+
-------
|
|
221
|
+
bool
|
|
222
|
+
"""
|
|
223
|
+
if n < 1:
|
|
224
|
+
return False
|
|
225
|
+
divs = proper_divisors(n)
|
|
226
|
+
# DP subset-sum
|
|
227
|
+
possible = {0}
|
|
228
|
+
for d in divs:
|
|
229
|
+
possible = possible | {x + d for x in possible}
|
|
230
|
+
if n in possible:
|
|
231
|
+
return True
|
|
232
|
+
return n in possible
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@register(name="Weird", category="divisors", oeis="A006037",
|
|
236
|
+
description="Abundant but not semiperfect.")
|
|
237
|
+
def is_weird(n: int) -> bool:
|
|
238
|
+
"""Return True if n is abundant but not semiperfect.
|
|
239
|
+
|
|
240
|
+
Parameters
|
|
241
|
+
----------
|
|
242
|
+
n : int
|
|
243
|
+
|
|
244
|
+
Returns
|
|
245
|
+
-------
|
|
246
|
+
bool
|
|
247
|
+
|
|
248
|
+
Examples
|
|
249
|
+
--------
|
|
250
|
+
>>> is_weird(70)
|
|
251
|
+
True
|
|
252
|
+
"""
|
|
253
|
+
return is_abundant(n) and not is_semiperfect(n)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@register(name="Amicable", category="divisors", oeis="A063990",
|
|
257
|
+
description="Pair where each number's proper divisor sum is the other.")
|
|
258
|
+
def is_amicable(n: int) -> bool:
|
|
259
|
+
"""Return True if n is part of an amicable pair.
|
|
260
|
+
|
|
261
|
+
Parameters
|
|
262
|
+
----------
|
|
263
|
+
n : int
|
|
264
|
+
|
|
265
|
+
Returns
|
|
266
|
+
-------
|
|
267
|
+
bool
|
|
268
|
+
"""
|
|
269
|
+
if n < 2:
|
|
270
|
+
return False
|
|
271
|
+
m = sigma(n) - n
|
|
272
|
+
return m != n and sigma(m) - m == n
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@register(name="Sociable", category="divisors", oeis="A003416",
|
|
276
|
+
description="Part of an aliquot cycle of length > 2 (checked up to length 6).")
|
|
277
|
+
def is_sociable(n: int) -> bool:
|
|
278
|
+
"""Return True if n is part of a sociable cycle of length 3–6.
|
|
279
|
+
|
|
280
|
+
Parameters
|
|
281
|
+
----------
|
|
282
|
+
n : int
|
|
283
|
+
|
|
284
|
+
Returns
|
|
285
|
+
-------
|
|
286
|
+
bool
|
|
287
|
+
"""
|
|
288
|
+
if n < 2:
|
|
289
|
+
return False
|
|
290
|
+
seen = [n]
|
|
291
|
+
current = sigma(n) - n
|
|
292
|
+
for _ in range(5): # check up to length 6
|
|
293
|
+
if current <= 1:
|
|
294
|
+
return False
|
|
295
|
+
if current == n and len(seen) > 2:
|
|
296
|
+
return True
|
|
297
|
+
if current in seen:
|
|
298
|
+
return False
|
|
299
|
+
seen.append(current)
|
|
300
|
+
current = sigma(current) - current
|
|
301
|
+
return current == n and len(seen) > 2
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
@register(name="Untouchable", category="divisors", oeis="A005114",
|
|
305
|
+
description="No integer has n as its proper divisor sum.")
|
|
306
|
+
def is_untouchable(n: int) -> bool:
|
|
307
|
+
"""Return True if no integer has n as its proper divisor sum.
|
|
308
|
+
|
|
309
|
+
Uses a precomputed sieve for values up to 10000.
|
|
310
|
+
|
|
311
|
+
Parameters
|
|
312
|
+
----------
|
|
313
|
+
n : int
|
|
314
|
+
|
|
315
|
+
Returns
|
|
316
|
+
-------
|
|
317
|
+
bool
|
|
318
|
+
"""
|
|
319
|
+
if n <= 10000:
|
|
320
|
+
return n in _UNTOUCHABLE
|
|
321
|
+
# Fallback: check a reasonable range
|
|
322
|
+
limit = n * 2
|
|
323
|
+
for k in range(2, limit + 1):
|
|
324
|
+
if sum(proper_divisors(k)) == n:
|
|
325
|
+
return False
|
|
326
|
+
return True
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@register(name="Superperfect", category="divisors", oeis="A019279",
|
|
330
|
+
description="sigma(sigma(n)) == 2*n.")
|
|
331
|
+
def is_superperfect(n: int) -> bool:
|
|
332
|
+
"""Return True if sigma(sigma(n)) == 2*n.
|
|
333
|
+
|
|
334
|
+
Parameters
|
|
335
|
+
----------
|
|
336
|
+
n : int
|
|
337
|
+
|
|
338
|
+
Returns
|
|
339
|
+
-------
|
|
340
|
+
bool
|
|
341
|
+
"""
|
|
342
|
+
if n < 1:
|
|
343
|
+
return False
|
|
344
|
+
return sigma(sigma(n)) == 2 * n
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
@register(name="Harmonic Divisor", category="divisors", oeis="A001599",
|
|
348
|
+
description="Harmonic mean of divisors is an integer.")
|
|
349
|
+
def is_harmonic_divisor(n: int) -> bool:
|
|
350
|
+
"""Return True if the harmonic mean of n's divisors is an integer.
|
|
351
|
+
|
|
352
|
+
The harmonic mean of divisors = n * num_divisors(n) / sigma(n).
|
|
353
|
+
|
|
354
|
+
Parameters
|
|
355
|
+
----------
|
|
356
|
+
n : int
|
|
357
|
+
|
|
358
|
+
Returns
|
|
359
|
+
-------
|
|
360
|
+
bool
|
|
361
|
+
"""
|
|
362
|
+
if n < 1:
|
|
363
|
+
return False
|
|
364
|
+
nd = num_divisors(n)
|
|
365
|
+
s = sigma(n)
|
|
366
|
+
# harmonic mean = n * nd / s must be integer
|
|
367
|
+
return (n * nd) % s == 0
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
@register(name="Practical", category="divisors", oeis="A005153",
|
|
371
|
+
description="Every integer 1..n can be expressed as a sum of distinct divisors of n.")
|
|
372
|
+
def is_practical(n: int) -> bool:
|
|
373
|
+
"""Return True if every integer 1..n is a sum of distinct divisors of n.
|
|
374
|
+
|
|
375
|
+
Uses the standard criterion: n=1, or n=2, or for each prime p^a || n
|
|
376
|
+
in sorted order, p <= 1 + sigma(product of earlier prime powers).
|
|
377
|
+
|
|
378
|
+
Parameters
|
|
379
|
+
----------
|
|
380
|
+
n : int
|
|
381
|
+
|
|
382
|
+
Returns
|
|
383
|
+
-------
|
|
384
|
+
bool
|
|
385
|
+
"""
|
|
386
|
+
if n == 1:
|
|
387
|
+
return True
|
|
388
|
+
if n % 2 != 0 and n != 1:
|
|
389
|
+
return False
|
|
390
|
+
factors = _factorization(n)
|
|
391
|
+
sigma_prefix = 1
|
|
392
|
+
for p, e in factors:
|
|
393
|
+
if p > sigma_prefix + 1:
|
|
394
|
+
return False
|
|
395
|
+
# sigma of p^e = (p^(e+1) - 1) / (p - 1)
|
|
396
|
+
sigma_prefix *= (p ** (e + 1) - 1) // (p - 1)
|
|
397
|
+
return True
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
@register(name="Refactorable", category="divisors", oeis="A033950",
|
|
401
|
+
description="Number of divisors divides n.")
|
|
402
|
+
def is_refactorable(n: int) -> bool:
|
|
403
|
+
"""Return True if the number of divisors of n divides n.
|
|
404
|
+
|
|
405
|
+
Parameters
|
|
406
|
+
----------
|
|
407
|
+
n : int
|
|
408
|
+
|
|
409
|
+
Returns
|
|
410
|
+
-------
|
|
411
|
+
bool
|
|
412
|
+
"""
|
|
413
|
+
if n < 1:
|
|
414
|
+
return False
|
|
415
|
+
return n % num_divisors(n) == 0
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
@register(name="Highly Composite", category="divisors", oeis="A002182",
|
|
419
|
+
description="Has more divisors than any smaller positive integer.")
|
|
420
|
+
def is_highly_composite(n: int) -> bool:
|
|
421
|
+
"""Return True if n has more divisors than any smaller positive integer.
|
|
422
|
+
|
|
423
|
+
Parameters
|
|
424
|
+
----------
|
|
425
|
+
n : int
|
|
426
|
+
|
|
427
|
+
Returns
|
|
428
|
+
-------
|
|
429
|
+
bool
|
|
430
|
+
"""
|
|
431
|
+
if n < 1:
|
|
432
|
+
return False
|
|
433
|
+
nd = num_divisors(n)
|
|
434
|
+
for k in range(1, n):
|
|
435
|
+
if num_divisors(k) >= nd:
|
|
436
|
+
return False
|
|
437
|
+
return True
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
@register(name="Squarefree", category="divisors", oeis="A005117",
|
|
441
|
+
description="Not divisible by any perfect square greater than 1.")
|
|
442
|
+
def is_squarefree(n: int) -> bool:
|
|
443
|
+
"""Return True if n is not divisible by any perfect square > 1.
|
|
444
|
+
|
|
445
|
+
Parameters
|
|
446
|
+
----------
|
|
447
|
+
n : int
|
|
448
|
+
|
|
449
|
+
Returns
|
|
450
|
+
-------
|
|
451
|
+
bool
|
|
452
|
+
|
|
453
|
+
Examples
|
|
454
|
+
--------
|
|
455
|
+
>>> is_squarefree(6)
|
|
456
|
+
True
|
|
457
|
+
>>> is_squarefree(4)
|
|
458
|
+
False
|
|
459
|
+
"""
|
|
460
|
+
if n <= 0:
|
|
461
|
+
return False
|
|
462
|
+
if n == 1:
|
|
463
|
+
return True
|
|
464
|
+
f = 2
|
|
465
|
+
while f * f <= n:
|
|
466
|
+
if n % (f * f) == 0:
|
|
467
|
+
return False
|
|
468
|
+
f += 1
|
|
469
|
+
return True
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
@register(name="Powerful", category="divisors", oeis="A001694",
|
|
473
|
+
description="For every prime p dividing n, p^2 also divides n.")
|
|
474
|
+
def is_powerful(n: int) -> bool:
|
|
475
|
+
"""Return True if for every prime p dividing n, p^2 also divides n.
|
|
476
|
+
|
|
477
|
+
Parameters
|
|
478
|
+
----------
|
|
479
|
+
n : int
|
|
480
|
+
|
|
481
|
+
Returns
|
|
482
|
+
-------
|
|
483
|
+
bool
|
|
484
|
+
|
|
485
|
+
Examples
|
|
486
|
+
--------
|
|
487
|
+
>>> is_powerful(4)
|
|
488
|
+
True
|
|
489
|
+
>>> is_powerful(6)
|
|
490
|
+
False
|
|
491
|
+
"""
|
|
492
|
+
if n <= 0:
|
|
493
|
+
return False
|
|
494
|
+
if n == 1:
|
|
495
|
+
return True
|
|
496
|
+
for p, e in _factorization(n):
|
|
497
|
+
if e < 2:
|
|
498
|
+
return False
|
|
499
|
+
return True
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
@register(name="Achilles", category="divisors", oeis="A052486",
|
|
503
|
+
description="Powerful but not a perfect power. Smallest: 72.")
|
|
504
|
+
def is_achilles(n: int) -> bool:
|
|
505
|
+
"""Return True if n is powerful but not a perfect power.
|
|
506
|
+
|
|
507
|
+
Parameters
|
|
508
|
+
----------
|
|
509
|
+
n : int
|
|
510
|
+
|
|
511
|
+
Returns
|
|
512
|
+
-------
|
|
513
|
+
bool
|
|
514
|
+
|
|
515
|
+
Examples
|
|
516
|
+
--------
|
|
517
|
+
>>> is_achilles(72)
|
|
518
|
+
True
|
|
519
|
+
"""
|
|
520
|
+
return is_powerful(n) and not _is_perfect_power(n)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
@register(name="Sphenic", category="divisors", oeis="A007304",
|
|
524
|
+
description="Product of exactly 3 distinct primes.")
|
|
525
|
+
def is_sphenic(n: int) -> bool:
|
|
526
|
+
"""Return True if n is the product of exactly 3 distinct primes.
|
|
527
|
+
|
|
528
|
+
Parameters
|
|
529
|
+
----------
|
|
530
|
+
n : int
|
|
531
|
+
|
|
532
|
+
Returns
|
|
533
|
+
-------
|
|
534
|
+
bool
|
|
535
|
+
|
|
536
|
+
Examples
|
|
537
|
+
--------
|
|
538
|
+
>>> is_sphenic(30)
|
|
539
|
+
True
|
|
540
|
+
>>> is_sphenic(12)
|
|
541
|
+
False
|
|
542
|
+
"""
|
|
543
|
+
factors = _factorization(n)
|
|
544
|
+
return len(factors) == 3 and all(e == 1 for _, e in factors)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
@register(name="2-smooth", category="divisors", oeis="A000079",
|
|
548
|
+
description="Only prime factor is 2 (powers of 2).")
|
|
549
|
+
def is_smooth_2(n: int) -> bool:
|
|
550
|
+
"""Return True if the only prime factor of n is 2.
|
|
551
|
+
|
|
552
|
+
Parameters
|
|
553
|
+
----------
|
|
554
|
+
n : int
|
|
555
|
+
|
|
556
|
+
Returns
|
|
557
|
+
-------
|
|
558
|
+
bool
|
|
559
|
+
"""
|
|
560
|
+
if n < 1:
|
|
561
|
+
return False
|
|
562
|
+
return n > 0 and (n & (n - 1)) == 0
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
@register(name="3-smooth", category="divisors", oeis="A003586",
|
|
566
|
+
description="All prime factors are at most 3.")
|
|
567
|
+
def is_smooth_3(n: int) -> bool:
|
|
568
|
+
"""Return True if all prime factors of n are ≤ 3.
|
|
569
|
+
|
|
570
|
+
Parameters
|
|
571
|
+
----------
|
|
572
|
+
n : int
|
|
573
|
+
|
|
574
|
+
Returns
|
|
575
|
+
-------
|
|
576
|
+
bool
|
|
577
|
+
"""
|
|
578
|
+
if n < 1:
|
|
579
|
+
return False
|
|
580
|
+
while n % 2 == 0:
|
|
581
|
+
n //= 2
|
|
582
|
+
while n % 3 == 0:
|
|
583
|
+
n //= 3
|
|
584
|
+
return n == 1
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
@register(name="5-smooth", category="divisors", oeis="A051037",
|
|
588
|
+
description="All prime factors are at most 5 (regular numbers).")
|
|
589
|
+
def is_smooth_5(n: int) -> bool:
|
|
590
|
+
"""Return True if all prime factors of n are ≤ 5.
|
|
591
|
+
|
|
592
|
+
Parameters
|
|
593
|
+
----------
|
|
594
|
+
n : int
|
|
595
|
+
|
|
596
|
+
Returns
|
|
597
|
+
-------
|
|
598
|
+
bool
|
|
599
|
+
"""
|
|
600
|
+
if n < 1:
|
|
601
|
+
return False
|
|
602
|
+
for p in (2, 3, 5):
|
|
603
|
+
while n % p == 0:
|
|
604
|
+
n //= p
|
|
605
|
+
return n == 1
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
@register(name="7-smooth", category="divisors", oeis="A002473",
|
|
609
|
+
description="All prime factors are at most 7.")
|
|
610
|
+
def is_smooth_7(n: int) -> bool:
|
|
611
|
+
"""Return True if all prime factors of n are ≤ 7.
|
|
612
|
+
|
|
613
|
+
Parameters
|
|
614
|
+
----------
|
|
615
|
+
n : int
|
|
616
|
+
|
|
617
|
+
Returns
|
|
618
|
+
-------
|
|
619
|
+
bool
|
|
620
|
+
"""
|
|
621
|
+
if n < 1:
|
|
622
|
+
return False
|
|
623
|
+
for p in (2, 3, 5, 7):
|
|
624
|
+
while n % p == 0:
|
|
625
|
+
n //= p
|
|
626
|
+
return n == 1
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
@register(name="3-rough", category="divisors",
|
|
630
|
+
description="No prime factor less than 3 (i.e. odd numbers > 1).")
|
|
631
|
+
def is_rough_3(n: int) -> bool:
|
|
632
|
+
"""Return True if n has no prime factor less than 3 (i.e. n is odd).
|
|
633
|
+
|
|
634
|
+
Parameters
|
|
635
|
+
----------
|
|
636
|
+
n : int
|
|
637
|
+
|
|
638
|
+
Returns
|
|
639
|
+
-------
|
|
640
|
+
bool
|
|
641
|
+
"""
|
|
642
|
+
return n > 1 and n % 2 != 0
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
@register(name="5-rough", category="divisors",
|
|
646
|
+
description="No prime factor less than 5.")
|
|
647
|
+
def is_rough_5(n: int) -> bool:
|
|
648
|
+
"""Return True if n has no prime factor less than 5.
|
|
649
|
+
|
|
650
|
+
Parameters
|
|
651
|
+
----------
|
|
652
|
+
n : int
|
|
653
|
+
|
|
654
|
+
Returns
|
|
655
|
+
-------
|
|
656
|
+
bool
|
|
657
|
+
"""
|
|
658
|
+
return n > 1 and n % 2 != 0 and n % 3 != 0
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def _factorization_digit_count(n: int) -> int:
|
|
662
|
+
"""Count digits used to express the prime factorization of n.
|
|
663
|
+
|
|
664
|
+
For each prime^exp: digits(prime) + (digits(exp) if exp>1 else 0).
|
|
665
|
+
"""
|
|
666
|
+
if n == 1:
|
|
667
|
+
return 1
|
|
668
|
+
total = 0
|
|
669
|
+
for p, e in _factorization(n):
|
|
670
|
+
total += len(str(p))
|
|
671
|
+
if e > 1:
|
|
672
|
+
total += len(str(e))
|
|
673
|
+
return total
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
@register(name="Economical", category="divisors", oeis="A046759",
|
|
677
|
+
description="Prime factorization uses fewer digits than n.")
|
|
678
|
+
def is_economical(n: int) -> bool:
|
|
679
|
+
"""Return True if the prime factorization of n uses fewer digits than n.
|
|
680
|
+
|
|
681
|
+
Parameters
|
|
682
|
+
----------
|
|
683
|
+
n : int
|
|
684
|
+
|
|
685
|
+
Returns
|
|
686
|
+
-------
|
|
687
|
+
bool
|
|
688
|
+
"""
|
|
689
|
+
if n < 2:
|
|
690
|
+
return False
|
|
691
|
+
return _factorization_digit_count(n) < len(str(n))
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
@register(name="Equidigital", category="divisors", oeis="A046758",
|
|
695
|
+
description="Prime factorization uses the same number of digits as n.")
|
|
696
|
+
def is_equidigital(n: int) -> bool:
|
|
697
|
+
"""Return True if the prime factorization of n uses the same digits as n.
|
|
698
|
+
|
|
699
|
+
Parameters
|
|
700
|
+
----------
|
|
701
|
+
n : int
|
|
702
|
+
|
|
703
|
+
Returns
|
|
704
|
+
-------
|
|
705
|
+
bool
|
|
706
|
+
"""
|
|
707
|
+
if n < 2:
|
|
708
|
+
return False
|
|
709
|
+
return _factorization_digit_count(n) == len(str(n))
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
@register(name="Wasteful", category="divisors", oeis="A046760",
|
|
713
|
+
description="Prime factorization uses more digits than n.")
|
|
714
|
+
def is_wasteful(n: int) -> bool:
|
|
715
|
+
"""Return True if the prime factorization of n uses more digits than n.
|
|
716
|
+
|
|
717
|
+
Parameters
|
|
718
|
+
----------
|
|
719
|
+
n : int
|
|
720
|
+
|
|
721
|
+
Returns
|
|
722
|
+
-------
|
|
723
|
+
bool
|
|
724
|
+
"""
|
|
725
|
+
if n < 2:
|
|
726
|
+
return False
|
|
727
|
+
return _factorization_digit_count(n) > len(str(n))
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
@register(name="Zumkeller", category="divisors", oeis="A083207",
|
|
731
|
+
description="Divisors can be partitioned into two sets with equal sum.")
|
|
732
|
+
def is_zumkeller(n: int) -> bool:
|
|
733
|
+
"""Return True if the divisors of n can be split into two sets with equal sum.
|
|
734
|
+
|
|
735
|
+
Parameters
|
|
736
|
+
----------
|
|
737
|
+
n : int
|
|
738
|
+
|
|
739
|
+
Returns
|
|
740
|
+
-------
|
|
741
|
+
bool
|
|
742
|
+
"""
|
|
743
|
+
if n < 1:
|
|
744
|
+
return False
|
|
745
|
+
divs_all = proper_divisors(n) + [n]
|
|
746
|
+
total = sum(divs_all)
|
|
747
|
+
if total % 2 != 0:
|
|
748
|
+
return False
|
|
749
|
+
target = total // 2
|
|
750
|
+
# DP subset-sum
|
|
751
|
+
possible = {0}
|
|
752
|
+
for d in divs_all:
|
|
753
|
+
possible = possible | {x + d for x in possible if x + d <= target}
|
|
754
|
+
if target in possible:
|
|
755
|
+
return True
|
|
756
|
+
return target in possible
|