numclassify 0.4.0__tar.gz → 0.4.1__tar.gz
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-0.4.0 → numclassify-0.4.1}/PKG-INFO +28 -17
- {numclassify-0.4.0 → numclassify-0.4.1}/README.md +27 -16
- numclassify-0.4.1/docs/api.md +141 -0
- numclassify-0.4.1/docs/changelog.md +124 -0
- numclassify-0.4.1/docs/cli.md +58 -0
- numclassify-0.4.1/docs/index.md +79 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/docs/playground.html +1 -1
- {numclassify-0.4.0 → numclassify-0.4.1}/docs/playground.js +7 -13
- {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/__init__.py +1 -1
- {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/digital.py +1 -1
- {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/divisors.py +3 -1
- {numclassify-0.4.0 → numclassify-0.4.1}/pyproject.toml +1 -1
- {numclassify-0.4.0 → numclassify-0.4.1}/tests/test_registry.py +38 -0
- numclassify-0.4.0/docs/api.md +0 -28
- numclassify-0.4.0/docs/changelog.md +0 -3
- numclassify-0.4.0/docs/cli.md +0 -22
- numclassify-0.4.0/docs/index.md +0 -23
- {numclassify-0.4.0 → numclassify-0.4.1}/.github/workflows/ci.yml +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/.github/workflows/docs.yml +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/.github/workflows/publish.yml +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/.gitignore +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/CHANGELOG.md +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/CONTRIBUTING.md +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/LICENSE +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/SECURITY.md +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/docs/extra/saffron.css +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/docs/playground.css +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/examples/basic_usage.py +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/examples/custom_type.py +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/examples/find_perfect_numbers.py +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/examples/random_classify.py +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/examples/stream_large_range.py +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/mkdocs.yml +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/__main__.py +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/__init__.py +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/combinatorial.py +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/exam_types.py +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/figurate.py +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/number_theory.py +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/powers.py +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/primes.py +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/recreational.py +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/sequences.py +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_registry.py +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/cli.py +0 -0
- {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: numclassify
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: The most comprehensive Python library for number classification - 3000+ number types
|
|
5
5
|
Project-URL: Homepage, https://github.com/aratrikghosh2011-tech/numclassify
|
|
6
6
|
Project-URL: Repository, https://github.com/aratrikghosh2011-tech/numclassify
|
|
@@ -67,9 +67,15 @@ import numclassify as nc
|
|
|
67
67
|
nc.is_prime(17) # True
|
|
68
68
|
nc.is_perfect(28) # True
|
|
69
69
|
|
|
70
|
-
# Classify a single number
|
|
70
|
+
# Classify a single number
|
|
71
71
|
nc.classify(1729)
|
|
72
|
-
# {
|
|
72
|
+
# {
|
|
73
|
+
# 'number': 1729,
|
|
74
|
+
# 'score': 22, # total true properties (incl. figurate)
|
|
75
|
+
# 'notable_score': 18, # score excluding polygonal figurate noise
|
|
76
|
+
# 'true_properties': ['Taxicab', 'Carmichael', ...],
|
|
77
|
+
# 'categories': {'primes': [...], 'sequences': [...], ...}
|
|
78
|
+
# }
|
|
73
79
|
|
|
74
80
|
# Batch classify
|
|
75
81
|
nc.classify_batch([6, 28, 496])
|
|
@@ -79,9 +85,12 @@ nc.find_by_property(start=1, end=1000, Perfect=True)
|
|
|
79
85
|
# [6, 28, 496]
|
|
80
86
|
|
|
81
87
|
# Stream over large ranges without loading everything into memory
|
|
82
|
-
for result in nc.stream(1, 1_000_000):
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
for result in nc.stream(1, 1_000_000, min_score=20):
|
|
89
|
+
print(result)
|
|
90
|
+
|
|
91
|
+
# Stream only numbers with a specific property
|
|
92
|
+
for result in nc.stream(1, 10_000, has_property="prime"):
|
|
93
|
+
print(result)
|
|
85
94
|
|
|
86
95
|
# All true properties of a number
|
|
87
96
|
nc.get_true_properties(1729)
|
|
@@ -131,17 +140,18 @@ numclassify info armstrong
|
|
|
131
140
|
|
|
132
141
|
| Category | Count | Examples |
|
|
133
142
|
|---|---|---|
|
|
134
|
-
| Polygonal figurate | ~
|
|
143
|
+
| Polygonal figurate | ~1003 | Triangular, Square, Pentagonal, Chiliagonal |
|
|
135
144
|
| Centered polygonal | ~998 | Centered Triangular, Centered Hexagonal |
|
|
136
|
-
| Prime families |
|
|
137
|
-
| Digital invariants |
|
|
145
|
+
| Prime families | 40 | Twin, Mersenne, Sophie Germain, Wilson, Safe |
|
|
146
|
+
| Digital invariants | 13 | Armstrong, Spy, Harshad, Disarium, Happy, Neon |
|
|
138
147
|
| Divisor-based | 27 | Perfect, Abundant, Weird, Amicable, Practical |
|
|
139
|
-
| Sequences |
|
|
148
|
+
| Sequences | 16 | Fibonacci, Lucas, Catalan, Bell, Padovan |
|
|
140
149
|
| Powers | 13 | Perfect Square, Taxicab, Sum of Two Squares |
|
|
141
150
|
| Number theory | 14 | Evil, Carmichael, Keith, Autobiographical |
|
|
142
151
|
| Combinatorial | 10 | Factorial, Primorial, Subfactorial |
|
|
143
|
-
| Recreational |
|
|
144
|
-
|
|
|
152
|
+
| Recreational | 6 | Kaprekar, Automorphic, Palindrome |
|
|
153
|
+
| Exam types | 8 | Armstrong, Strong, Sunny, Buzz, Magic, Unique |
|
|
154
|
+
| **Total** | **2140+** | |
|
|
145
155
|
|
|
146
156
|
---
|
|
147
157
|
|
|
@@ -169,18 +179,19 @@ See [`examples/`](examples/) for runnable scripts covering all major features.
|
|
|
169
179
|
|
|
170
180
|
| Function | Description |
|
|
171
181
|
|---|---|
|
|
172
|
-
| `classify(n)` | Returns `{number, true_properties,
|
|
182
|
+
| `classify(n)` | Returns `{number, score, notable_score, true_properties, categories}` |
|
|
173
183
|
| `classify_batch(numbers)` | Classify a list; returns list of dicts |
|
|
174
184
|
| `random_number(max_n)` | Classify a randomly selected number |
|
|
175
185
|
| `find_by_property(start, end, **filters)` | Numbers in range matching property filters |
|
|
176
|
-
| `stream(start, end)` | Generator — memory-safe range classification |
|
|
186
|
+
| `stream(start, end, min_score, has_property)` | Generator — memory-safe range classification |
|
|
177
187
|
| `get_all_properties(n)` | Dict of every type mapped to True/False |
|
|
178
188
|
| `get_true_properties(n)` | List of True property names only |
|
|
179
189
|
| `print_properties(n)` | Pretty-print property table to stdout |
|
|
180
190
|
| `count_properties(n)` | Count of True properties |
|
|
181
|
-
| `most_special_in_range(lo, hi)` | Number in range with the most True properties |
|
|
182
|
-
| `find_in_range(fn, lo, hi)` | Numbers where `fn` returns True |
|
|
183
|
-
| `
|
|
191
|
+
| `most_special_in_range(lo, hi, verbose)` | Number in range with the most True properties |
|
|
192
|
+
| `find_in_range(fn, lo, hi)` | Numbers where callable `fn` returns True |
|
|
193
|
+
| `find_any_in_range(predicates, lo, hi)` | Integers in range satisfying at least one predicate |
|
|
194
|
+
| `find_all_in_range(predicates, lo, hi)` | Integers in range satisfying all predicates |
|
|
184
195
|
| `register` | Decorator to add custom number types |
|
|
185
196
|
| `is_prime(n)` | Convenience boolean |
|
|
186
197
|
| `is_armstrong(n)` | Convenience boolean |
|
|
@@ -39,9 +39,15 @@ import numclassify as nc
|
|
|
39
39
|
nc.is_prime(17) # True
|
|
40
40
|
nc.is_perfect(28) # True
|
|
41
41
|
|
|
42
|
-
# Classify a single number
|
|
42
|
+
# Classify a single number
|
|
43
43
|
nc.classify(1729)
|
|
44
|
-
# {
|
|
44
|
+
# {
|
|
45
|
+
# 'number': 1729,
|
|
46
|
+
# 'score': 22, # total true properties (incl. figurate)
|
|
47
|
+
# 'notable_score': 18, # score excluding polygonal figurate noise
|
|
48
|
+
# 'true_properties': ['Taxicab', 'Carmichael', ...],
|
|
49
|
+
# 'categories': {'primes': [...], 'sequences': [...], ...}
|
|
50
|
+
# }
|
|
45
51
|
|
|
46
52
|
# Batch classify
|
|
47
53
|
nc.classify_batch([6, 28, 496])
|
|
@@ -51,9 +57,12 @@ nc.find_by_property(start=1, end=1000, Perfect=True)
|
|
|
51
57
|
# [6, 28, 496]
|
|
52
58
|
|
|
53
59
|
# Stream over large ranges without loading everything into memory
|
|
54
|
-
for result in nc.stream(1, 1_000_000):
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
for result in nc.stream(1, 1_000_000, min_score=20):
|
|
61
|
+
print(result)
|
|
62
|
+
|
|
63
|
+
# Stream only numbers with a specific property
|
|
64
|
+
for result in nc.stream(1, 10_000, has_property="prime"):
|
|
65
|
+
print(result)
|
|
57
66
|
|
|
58
67
|
# All true properties of a number
|
|
59
68
|
nc.get_true_properties(1729)
|
|
@@ -103,17 +112,18 @@ numclassify info armstrong
|
|
|
103
112
|
|
|
104
113
|
| Category | Count | Examples |
|
|
105
114
|
|---|---|---|
|
|
106
|
-
| Polygonal figurate | ~
|
|
115
|
+
| Polygonal figurate | ~1003 | Triangular, Square, Pentagonal, Chiliagonal |
|
|
107
116
|
| Centered polygonal | ~998 | Centered Triangular, Centered Hexagonal |
|
|
108
|
-
| Prime families |
|
|
109
|
-
| Digital invariants |
|
|
117
|
+
| Prime families | 40 | Twin, Mersenne, Sophie Germain, Wilson, Safe |
|
|
118
|
+
| Digital invariants | 13 | Armstrong, Spy, Harshad, Disarium, Happy, Neon |
|
|
110
119
|
| Divisor-based | 27 | Perfect, Abundant, Weird, Amicable, Practical |
|
|
111
|
-
| Sequences |
|
|
120
|
+
| Sequences | 16 | Fibonacci, Lucas, Catalan, Bell, Padovan |
|
|
112
121
|
| Powers | 13 | Perfect Square, Taxicab, Sum of Two Squares |
|
|
113
122
|
| Number theory | 14 | Evil, Carmichael, Keith, Autobiographical |
|
|
114
123
|
| Combinatorial | 10 | Factorial, Primorial, Subfactorial |
|
|
115
|
-
| Recreational |
|
|
116
|
-
|
|
|
124
|
+
| Recreational | 6 | Kaprekar, Automorphic, Palindrome |
|
|
125
|
+
| Exam types | 8 | Armstrong, Strong, Sunny, Buzz, Magic, Unique |
|
|
126
|
+
| **Total** | **2140+** | |
|
|
117
127
|
|
|
118
128
|
---
|
|
119
129
|
|
|
@@ -141,18 +151,19 @@ See [`examples/`](examples/) for runnable scripts covering all major features.
|
|
|
141
151
|
|
|
142
152
|
| Function | Description |
|
|
143
153
|
|---|---|
|
|
144
|
-
| `classify(n)` | Returns `{number, true_properties,
|
|
154
|
+
| `classify(n)` | Returns `{number, score, notable_score, true_properties, categories}` |
|
|
145
155
|
| `classify_batch(numbers)` | Classify a list; returns list of dicts |
|
|
146
156
|
| `random_number(max_n)` | Classify a randomly selected number |
|
|
147
157
|
| `find_by_property(start, end, **filters)` | Numbers in range matching property filters |
|
|
148
|
-
| `stream(start, end)` | Generator — memory-safe range classification |
|
|
158
|
+
| `stream(start, end, min_score, has_property)` | Generator — memory-safe range classification |
|
|
149
159
|
| `get_all_properties(n)` | Dict of every type mapped to True/False |
|
|
150
160
|
| `get_true_properties(n)` | List of True property names only |
|
|
151
161
|
| `print_properties(n)` | Pretty-print property table to stdout |
|
|
152
162
|
| `count_properties(n)` | Count of True properties |
|
|
153
|
-
| `most_special_in_range(lo, hi)` | Number in range with the most True properties |
|
|
154
|
-
| `find_in_range(fn, lo, hi)` | Numbers where `fn` returns True |
|
|
155
|
-
| `
|
|
163
|
+
| `most_special_in_range(lo, hi, verbose)` | Number in range with the most True properties |
|
|
164
|
+
| `find_in_range(fn, lo, hi)` | Numbers where callable `fn` returns True |
|
|
165
|
+
| `find_any_in_range(predicates, lo, hi)` | Integers in range satisfying at least one predicate |
|
|
166
|
+
| `find_all_in_range(predicates, lo, hi)` | Integers in range satisfying all predicates |
|
|
156
167
|
| `register` | Decorator to add custom number types |
|
|
157
168
|
| `is_prime(n)` | Convenience boolean |
|
|
158
169
|
| `is_armstrong(n)` | Convenience boolean |
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
## classify(n)
|
|
4
|
+
|
|
5
|
+
Classifies a single integer. Returns a dict:
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
{
|
|
9
|
+
"number": 1729,
|
|
10
|
+
"score": 22, # total true properties (includes all figurate types)
|
|
11
|
+
"notable_score": 18, # score excluding polygonal figurate noise — better for display
|
|
12
|
+
"true_properties": ["Taxicab", "Carmichael", ...], # sorted by category then name
|
|
13
|
+
"categories": {
|
|
14
|
+
"primes": ["Carmichael"],
|
|
15
|
+
"sequences": ["..."],
|
|
16
|
+
...
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`notable_score` excludes `figurate` and `figurate_centered` hits. Use it when displaying
|
|
22
|
+
a score to humans — otherwise n=1 (which is the first k-gonal number for every k) shows
|
|
23
|
+
a score of 2000+.
|
|
24
|
+
|
|
25
|
+
## classify_batch(numbers)
|
|
26
|
+
|
|
27
|
+
Classify a list of integers. Returns a list of dicts in the same format as `classify()`.
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
nc.classify_batch([6, 28, 496])
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## random_number(max_n=10000)
|
|
34
|
+
|
|
35
|
+
Classify a random integer from 1 to `max_n`.
|
|
36
|
+
|
|
37
|
+
## find_by_property(start, end, **filters)
|
|
38
|
+
|
|
39
|
+
Find integers in `[start, end]` matching keyword property filters.
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
nc.find_by_property(start=1, end=1000, Perfect=True)
|
|
43
|
+
# [6, 28, 496]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## stream(start, end, min_score=None, has_property=None)
|
|
47
|
+
|
|
48
|
+
Memory-safe generator over a range. Yields one result dict per integer.
|
|
49
|
+
|
|
50
|
+
- `min_score` — skip numbers with `notable_score` below this threshold
|
|
51
|
+
- `has_property` — skip numbers that don't have this property as True
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
for result in nc.stream(1, 1_000_000, min_score=20):
|
|
55
|
+
print(result)
|
|
56
|
+
|
|
57
|
+
for result in nc.stream(1, 10_000, has_property="prime"):
|
|
58
|
+
print(result)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## get_all_properties(n)
|
|
62
|
+
|
|
63
|
+
Returns a dict of every registered type mapped to `True` or `False`.
|
|
64
|
+
|
|
65
|
+
## get_true_properties(n)
|
|
66
|
+
|
|
67
|
+
Returns a list of only the `True` property names.
|
|
68
|
+
|
|
69
|
+
## print_properties(n)
|
|
70
|
+
|
|
71
|
+
Pretty-prints a formatted table of all True properties to stdout.
|
|
72
|
+
|
|
73
|
+
## count_properties(n)
|
|
74
|
+
|
|
75
|
+
Returns the count of True properties (equivalent to `score` in `classify()`).
|
|
76
|
+
|
|
77
|
+
## most_special_in_range(lo, hi, verbose=False)
|
|
78
|
+
|
|
79
|
+
Returns the integer in `[lo, hi]` with the highest `score`. Pass `verbose=True`
|
|
80
|
+
for progress output on large ranges.
|
|
81
|
+
|
|
82
|
+
## find_in_range(fn, lo, hi)
|
|
83
|
+
|
|
84
|
+
Returns all integers in `[lo, hi]` where callable `fn(n)` returns `True`.
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
nc.find_in_range(nc.is_prime, 1, 100)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## find_any_in_range(funcs_or_names, start, end)
|
|
91
|
+
|
|
92
|
+
Returns all integers in `[start, end]` that satisfy **at least one** of the given predicates.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
# Numbers that are prime OR palindrome in 1..100
|
|
96
|
+
nc.find_any_in_range(["prime", "palindrome"], 1, 100)
|
|
97
|
+
|
|
98
|
+
# Using callables
|
|
99
|
+
nc.find_any_in_range([nc.is_prime, nc.is_armstrong], 1, 500)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## find_all_in_range(funcs_or_names, start, end)
|
|
103
|
+
|
|
104
|
+
Returns all integers in `[start, end]` that satisfy **every** predicate given.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# Numbers that are BOTH prime AND palindrome in 1..1000
|
|
108
|
+
nc.find_all_in_range(["prime", "palindrome"], 1, 1000)
|
|
109
|
+
# => [2, 3, 5, 7, 11, 101, 131, 151, ...]
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## register
|
|
113
|
+
|
|
114
|
+
Decorator to add a custom number type. Registered types appear everywhere — `classify()`,
|
|
115
|
+
`find_by_property()`, the CLI, all of it.
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from numclassify import register
|
|
119
|
+
|
|
120
|
+
@register(name="My Type", category="recreational", oeis="A000000",
|
|
121
|
+
description="Numbers divisible by 7 starting with 4")
|
|
122
|
+
def is_my_type(n: int) -> bool:
|
|
123
|
+
return n > 0 and n % 7 == 0 and str(n)[0] == "4"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Parameters:
|
|
127
|
+
|
|
128
|
+
- `name` — display name (required)
|
|
129
|
+
- `category` — category string (required)
|
|
130
|
+
- `oeis` — OEIS sequence ID, e.g. `"A000040"` (optional)
|
|
131
|
+
- `description` — one-line description (optional)
|
|
132
|
+
|
|
133
|
+
## Convenience booleans
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
nc.is_prime(17) # True
|
|
137
|
+
nc.is_armstrong(153) # True
|
|
138
|
+
nc.is_perfect(28) # True
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
These are thin wrappers around the registered functions, exported for direct use.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to numclassify are documented here.
|
|
4
|
+
Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) — [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## [0.4.1] - 2026-06-14
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- `is_achilles(1)` now returns `False`. Previously returned `True` because
|
|
12
|
+
`_is_perfect_power(1)` incorrectly returned `False` (1 = 1² is a perfect power).
|
|
13
|
+
First Achilles number is 72, per OEIS A052486.
|
|
14
|
+
- `stream(min_score=N)` now filters on `notable_score` instead of raw `score`.
|
|
15
|
+
Previously, figurate inflation caused almost all numbers to pass any threshold.
|
|
16
|
+
- `is_spy(0)` now returns `False`. Spy numbers are defined for positive integers only.
|
|
17
|
+
- Playground version badge now always shows live installed version from PyPI
|
|
18
|
+
instead of the hardcoded version in the HTML source.
|
|
19
|
+
- Fixed `find_any_in_range` and `find_all_in_range` documentation — both functions
|
|
20
|
+
take a list of predicates as the first argument, not just `(lo, hi)`.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## [0.4.0] - 2026-06-13
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- `classify()` now returns `notable_score` — score excluding figurate and centered-figurate hits. Prevents misleading inflation for n=1 (first member of every polygonal sequence).
|
|
28
|
+
- `is_unique(n)` now returns `False` for negative `n`.
|
|
29
|
+
- `is_practical(0)` now returns `False`.
|
|
30
|
+
- Removed leaked internal names (`Optional`, `_version`, `_PackageNotFoundError`) from `dir(numclassify)`.
|
|
31
|
+
- Playground now displays `notable_score` instead of raw score.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- Development status updated to `5 - Production/Stable`.
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
- `SECURITY.md` with vulnerability reporting instructions.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## [0.3.3] - 2026-06-13
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
- 8 new exam number types: Strong, Sunny, Buzz, Magic, Fascinating, Trimorphic, Twisted Prime, Unique.
|
|
45
|
+
- `classify()` returns a `categories` dict grouping true properties by category.
|
|
46
|
+
- `stream()` accepts `min_score` and `has_property` filter parameters.
|
|
47
|
+
- `most_special_in_range()` accepts `verbose=True` for progress output.
|
|
48
|
+
- `find_any_in_range()` exported to public API.
|
|
49
|
+
- Auto-generated crash tests (every registered type tested on 0, 1, 2, −1).
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
- Infinite loop in `is_sum_of_three_squares(0)`: `while n%4==0` never exits for n=0.
|
|
53
|
+
- Dead `else` branch removed from `classify()`.
|
|
54
|
+
- Fixed wrong docstring example in `is_leyland_prime`.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## [0.3.2.1] - 2026-06-12
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
- Batch classify rejected comma/space input — changed `type="number"` to `type="text"` with `inputmode="numeric"`.
|
|
62
|
+
- Search autocomplete fallback if Pyodide load fails.
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
- MkDocs site theme updated to saffron (#FF9933).
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## [0.3.2] - 2026-06-12
|
|
70
|
+
|
|
71
|
+
### Added
|
|
72
|
+
- Search autocomplete dropdown.
|
|
73
|
+
- Confetti when score > 50.
|
|
74
|
+
- Keyboard shortcuts: `C`, `S`, `N`, `?`.
|
|
75
|
+
- `prefers-reduced-motion` support.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## [0.3.1] - 2026-06-12
|
|
80
|
+
|
|
81
|
+
### Added
|
|
82
|
+
- Version badge on playground (live from installed package).
|
|
83
|
+
- Category-colored property tags.
|
|
84
|
+
- Batch classify mode.
|
|
85
|
+
- Recent history panel (last 20 numbers).
|
|
86
|
+
- Property tooltips, fuzzy search, search pagination.
|
|
87
|
+
- Number of the Day date picker.
|
|
88
|
+
- Light/dark theme toggle.
|
|
89
|
+
- Download results as JSON.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## [0.3.0] - 2026-06-12
|
|
94
|
+
|
|
95
|
+
### Added
|
|
96
|
+
- Pyodide playground (`docs/playground.html`) — real Python in the browser.
|
|
97
|
+
- Auto GitHub Pages deployment on push to main.
|
|
98
|
+
- Version sync via `importlib.metadata`.
|
|
99
|
+
- PyPI badges in README.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## [0.2.1] - 2026-05-11
|
|
104
|
+
|
|
105
|
+
### Added
|
|
106
|
+
- `examples/` folder with 5 runnable scripts.
|
|
107
|
+
- `CONTRIBUTING.md`.
|
|
108
|
+
- Namespace cleanup.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## [0.2.0] - 2026-05
|
|
113
|
+
|
|
114
|
+
### Added
|
|
115
|
+
- OIDC trusted publishing via GitHub Actions.
|
|
116
|
+
- MkDocs Material documentation deployed to GitHub Pages.
|
|
117
|
+
- `py.typed` marker.
|
|
118
|
+
- 60 hand-written tests.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## [0.1.0] - 2026-05
|
|
123
|
+
|
|
124
|
+
Initial release.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# CLI Reference
|
|
2
|
+
|
|
3
|
+
Install the package and the `numclassify` command is available immediately.
|
|
4
|
+
|
|
5
|
+
## check
|
|
6
|
+
|
|
7
|
+
Classify a single number.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
numclassify check 1729
|
|
11
|
+
numclassify check 1729 --json # machine-readable JSON output
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## compare
|
|
15
|
+
|
|
16
|
+
Show properties shared between two numbers and properties unique to each.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
numclassify compare 6 28
|
|
20
|
+
numclassify compare 6 28 --json
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## find
|
|
24
|
+
|
|
25
|
+
Find numbers of a given type, starting from 1.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
numclassify find armstrong
|
|
29
|
+
numclassify find prime --limit 20
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## range
|
|
33
|
+
|
|
34
|
+
Classify or filter all integers in a range.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
numclassify range 1 100
|
|
38
|
+
numclassify range 1 100 --filter prime
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## info
|
|
42
|
+
|
|
43
|
+
Show the definition, category, and OEIS reference for a number type.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
numclassify info armstrong
|
|
47
|
+
numclassify info taxicab
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## list
|
|
51
|
+
|
|
52
|
+
List all registered number types.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
numclassify list
|
|
56
|
+
numclassify list --category primes
|
|
57
|
+
numclassify list --category divisors
|
|
58
|
+
```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# numclassify
|
|
2
|
+
|
|
3
|
+
**Given a number, what is it?**
|
|
4
|
+
|
|
5
|
+
Most number-theory libraries — `labmath`, `eulerlib`, `pyntlib` — compute things: factor integers, find GCDs, generate primes. `numclassify` solves a different problem. Hand it an integer and it tells you every named mathematical type that number belongs to, across 2140+ categories, with zero external dependencies.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
153 → Armstrong, Harshad, Triangular, Abundant, ...
|
|
9
|
+
1729 → Taxicab (Hardy–Ramanujan), Carmichael, Zeisel, ...
|
|
10
|
+
28 → Perfect, Triangular, Hexagonal, Semiprime, ...
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Try it live: **[numclassify Playground](playground.html)**
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install numclassify
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Python 3.8–3.13. No external dependencies.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Quick example
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import numclassify as nc
|
|
31
|
+
|
|
32
|
+
nc.classify(1729)
|
|
33
|
+
# {
|
|
34
|
+
# 'number': 1729,
|
|
35
|
+
# 'score': 22,
|
|
36
|
+
# 'notable_score': 18,
|
|
37
|
+
# 'true_properties': ['Taxicab', 'Carmichael', ...],
|
|
38
|
+
# 'categories': {'primes': [...], 'sequences': [...], ...}
|
|
39
|
+
# }
|
|
40
|
+
|
|
41
|
+
nc.is_prime(17) # True
|
|
42
|
+
nc.is_perfect(28) # True
|
|
43
|
+
nc.get_true_properties(6) # ['Perfect', 'Triangular', 'Pronic', ...]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Categories
|
|
49
|
+
|
|
50
|
+
| Category | Count | Examples |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| Polygonal figurate | ~1003 | Triangular, Square, Pentagonal |
|
|
53
|
+
| Centered polygonal | ~998 | Centered Triangular, Centered Hexagonal |
|
|
54
|
+
| Prime families | 40 | Twin, Mersenne, Sophie Germain, Safe |
|
|
55
|
+
| Digital invariants | 13 | Armstrong, Harshad, Disarium, Happy |
|
|
56
|
+
| Divisor-based | 27 | Perfect, Abundant, Weird, Practical |
|
|
57
|
+
| Sequences | 16 | Fibonacci, Lucas, Catalan, Bell |
|
|
58
|
+
| Powers | 13 | Perfect Square, Taxicab, Powerful |
|
|
59
|
+
| Number theory | 14 | Evil, Carmichael, Autobiographical |
|
|
60
|
+
| Combinatorial | 10 | Factorial, Primorial, Subfactorial |
|
|
61
|
+
| Recreational | 6 | Kaprekar, Automorphic, Palindrome |
|
|
62
|
+
| Exam types | 8 | Strong, Sunny, Buzz, Magic, Unique |
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Add your own types
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from numclassify import register
|
|
70
|
+
|
|
71
|
+
@register(name="My Type", category="recreational")
|
|
72
|
+
def is_my_type(n: int) -> bool:
|
|
73
|
+
return n > 0 and n % 7 == 0
|
|
74
|
+
|
|
75
|
+
import numclassify as nc
|
|
76
|
+
nc.is_my_type(42) # True — available everywhere immediately
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
See [API Reference](api.md) for the full function list, or the [CLI Reference](cli.md) for command-line usage.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<header>
|
|
20
20
|
<button class="theme-toggle" onclick="toggleTheme()" title="Toggle theme"><span id="theme-icon">🌙</span></button>
|
|
21
21
|
<div class="logo-badge">numclassify playground</div>
|
|
22
|
-
<div class="version-badge" id="version-badge" data-version="0.
|
|
22
|
+
<div class="version-badge" id="version-badge" data-version="0.4.1">v<span id="version-text">—</span></div>
|
|
23
23
|
<h1>Number Intelligence</h1>
|
|
24
24
|
<p class="subtitle">Classify any integer into 3000+ named mathematical types — powered by real Python in your browser.</p>
|
|
25
25
|
</header>
|
|
@@ -228,18 +228,12 @@ json.dumps(m)
|
|
|
228
228
|
console.warn('Could not load category map', e);
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
// Fetch version number —
|
|
232
|
-
const badge = $('version-badge');
|
|
233
|
-
const srcVersion = badge ? badge.dataset.version : null;
|
|
231
|
+
// Fetch version number — always use live installed version from PyPI
|
|
234
232
|
try {
|
|
235
233
|
const pypiVer = await pyodide.runPythonAsync('nc.__version__');
|
|
236
|
-
|
|
237
|
-
$('version-text').textContent = srcVersion || pypiVer;
|
|
238
|
-
if (srcVersion && pypiVer !== srcVersion) {
|
|
239
|
-
badge.title = 'Source: v' + srcVersion + ' | PyPI: v' + pypiVer;
|
|
240
|
-
}
|
|
234
|
+
$('version-text').textContent = pypiVer;
|
|
241
235
|
} catch(e) {
|
|
242
|
-
$('version-text').textContent =
|
|
236
|
+
$('version-text').textContent = '?';
|
|
243
237
|
}
|
|
244
238
|
|
|
245
239
|
setProgress(100, 'Ready!');
|
|
@@ -283,7 +277,7 @@ async function doClassify(n = null) {
|
|
|
283
277
|
const result = await pyodide.runPythonAsync(`
|
|
284
278
|
import json
|
|
285
279
|
r = nc.classify(${val})
|
|
286
|
-
json.dumps({"number": r["number"], "score": r["score"], "props": r["true_properties"]})
|
|
280
|
+
json.dumps({"number": r["number"], "score": r["notable_score"], "total_score": r["score"], "props": r["true_properties"]})
|
|
287
281
|
`);
|
|
288
282
|
const data = JSON.parse(result);
|
|
289
283
|
|
|
@@ -361,12 +355,12 @@ json.dumps(results)
|
|
|
361
355
|
results.forEach(r => {
|
|
362
356
|
const tags = r.true_properties.slice(0, 8).map(p => p.replace(/_/g, ' ')).join(', ');
|
|
363
357
|
const more = r.true_properties.length > 8 ? ` +${r.true_properties.length - 8} more` : '';
|
|
364
|
-
html += `<tr><td class="b-num" onclick="recallHistory(${r.number})">${r.number}</td><td>${r.
|
|
358
|
+
html += `<tr><td class="b-num" onclick="recallHistory(${r.number})">${r.number}</td><td>${r.notable_score}</td><td class="b-tags">${tags}${more}</td></tr>`;
|
|
365
359
|
});
|
|
366
360
|
html += '</tbody></table>';
|
|
367
361
|
$('classify-tags').innerHTML = html;
|
|
368
362
|
|
|
369
|
-
results.forEach(r => saveHistory({ n: r.number, score: r.
|
|
363
|
+
results.forEach(r => saveHistory({ n: r.number, score: r.notable_score }));
|
|
370
364
|
|
|
371
365
|
} catch(e) {
|
|
372
366
|
toast('Error: ' + e.message.slice(0, 80));
|
|
@@ -576,7 +570,7 @@ seed = today.year * 10000 + today.month * 100 + today.day
|
|
|
576
570
|
_random.seed(seed)
|
|
577
571
|
n = _random.randint(1, 9999)
|
|
578
572
|
r = nc.classify(n)
|
|
579
|
-
json.dumps({"number": r["number"], "score": r["
|
|
573
|
+
json.dumps({"number": r["number"], "score": r["notable_score"], "props": r["true_properties"][:12]})
|
|
580
574
|
`);
|
|
581
575
|
const data = JSON.parse(result);
|
|
582
576
|
$('notd-number').textContent = data.number;
|
|
@@ -201,7 +201,7 @@ def stream(start: int, end: int, min_score: int = 0, has_property: Optional[str]
|
|
|
201
201
|
|
|
202
202
|
for n in range(start, end + 1):
|
|
203
203
|
result = classify(n)
|
|
204
|
-
if result["
|
|
204
|
+
if result["notable_score"] < min_score:
|
|
205
205
|
continue
|
|
206
206
|
if prop_key is not None:
|
|
207
207
|
props = _gap(n)
|
|
@@ -97,7 +97,9 @@ def _factorization(n: int) -> List[tuple]:
|
|
|
97
97
|
|
|
98
98
|
def _is_perfect_power(n: int) -> bool:
|
|
99
99
|
"""Return True if n = k^m for some k>1, m>1."""
|
|
100
|
-
if n
|
|
100
|
+
if n == 1:
|
|
101
|
+
return True # 1 = 1^k for any k >= 2
|
|
102
|
+
if n <= 0:
|
|
101
103
|
return False
|
|
102
104
|
for exp in range(2, n.bit_length() + 1):
|
|
103
105
|
root = round(n ** (1 / exp))
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "numclassify"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.1"
|
|
8
8
|
description = "The most comprehensive Python library for number classification - 3000+ number types"
|
|
9
9
|
authors = [{name = "Aratrik Ghosh", email = "aratrikghosh2011@gmail.com"}]
|
|
10
10
|
readme = "README.md"
|
|
@@ -432,3 +432,41 @@ def test_no_leaked_names():
|
|
|
432
432
|
assert "Optional" not in public_names
|
|
433
433
|
assert "_version" not in public_names
|
|
434
434
|
assert "_PackageNotFoundError" not in public_names
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
# ---------------------------------------------------------------------------
|
|
438
|
+
# v0.4.1 regression tests
|
|
439
|
+
# ---------------------------------------------------------------------------
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def test_achilles_not_one():
|
|
443
|
+
"""1 is not an Achilles number — first is 72."""
|
|
444
|
+
from numclassify._core.divisors import is_achilles
|
|
445
|
+
assert is_achilles(1) is False
|
|
446
|
+
assert is_achilles(72) is True
|
|
447
|
+
assert is_achilles(108) is True
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def test_perfect_power_one():
|
|
451
|
+
"""1 must be considered a perfect power (1 = 1^2)."""
|
|
452
|
+
from numclassify._core.divisors import _is_perfect_power
|
|
453
|
+
assert _is_perfect_power(1) is True
|
|
454
|
+
assert _is_perfect_power(4) is True
|
|
455
|
+
assert _is_perfect_power(6) is False
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def test_stream_min_score_uses_notable():
|
|
459
|
+
"""stream min_score must filter on notable_score, not raw score."""
|
|
460
|
+
results = list(nc.stream(1, 10, min_score=40))
|
|
461
|
+
numbers = [r['number'] for r in results]
|
|
462
|
+
assert 4 not in numbers # notable_score=35
|
|
463
|
+
assert 9 not in numbers # notable_score=38
|
|
464
|
+
assert 1 in numbers # notable_score=63
|
|
465
|
+
assert 2 in numbers # notable_score=63
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def test_spy_not_zero():
|
|
469
|
+
"""is_spy(0) must return False."""
|
|
470
|
+
from numclassify._core.digital import is_spy
|
|
471
|
+
assert is_spy(0) is False
|
|
472
|
+
assert is_spy(1) is True
|
numclassify-0.4.0/docs/api.md
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# API Reference
|
|
2
|
-
|
|
3
|
-
## classify(n)
|
|
4
|
-
Returns dict with number, true_properties list, and score.
|
|
5
|
-
|
|
6
|
-
## classify_batch(numbers)
|
|
7
|
-
Classify a list of integers.
|
|
8
|
-
|
|
9
|
-
## random_number(max_n=10000)
|
|
10
|
-
Classify a random integer up to max_n.
|
|
11
|
-
|
|
12
|
-
## find_by_property(start, end, **filters)
|
|
13
|
-
Find numbers matching property filters.
|
|
14
|
-
|
|
15
|
-
## stream(start, end)
|
|
16
|
-
Memory-safe generator over large ranges.
|
|
17
|
-
|
|
18
|
-
## register(name, category, ...)
|
|
19
|
-
Decorator to register custom number types. See [examples/custom_type.py](https://github.com/aratrikghosh2011-tech/numclassify/blob/main/examples/custom_type.py).
|
|
20
|
-
|
|
21
|
-
## Utilities
|
|
22
|
-
- get_all_properties(n)
|
|
23
|
-
- get_true_properties(n)
|
|
24
|
-
- print_properties(n)
|
|
25
|
-
- count_properties(n)
|
|
26
|
-
- most_special_in_range(lo, hi)
|
|
27
|
-
- find_in_range(fn, lo, hi)
|
|
28
|
-
- is_prime(n), is_armstrong(n), is_perfect(n)
|
numclassify-0.4.0/docs/cli.md
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# CLI Reference
|
|
2
|
-
|
|
3
|
-
## check
|
|
4
|
-
numclassify check 1729
|
|
5
|
-
numclassify check 1729 --json
|
|
6
|
-
|
|
7
|
-
## compare
|
|
8
|
-
numclassify compare 6 28
|
|
9
|
-
numclassify compare 6 28 --json
|
|
10
|
-
|
|
11
|
-
## find
|
|
12
|
-
numclassify find armstrong --limit 10
|
|
13
|
-
|
|
14
|
-
## range
|
|
15
|
-
numclassify range 1 100 --filter prime
|
|
16
|
-
|
|
17
|
-
## info
|
|
18
|
-
numclassify info armstrong
|
|
19
|
-
|
|
20
|
-
## list
|
|
21
|
-
numclassify list
|
|
22
|
-
numclassify list --category primes
|
numclassify-0.4.0/docs/index.md
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# numclassify
|
|
2
|
-
|
|
3
|
-
The most comprehensive Python number classification library - 3000+ types, zero dependencies.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
pip install numclassify
|
|
8
|
-
|
|
9
|
-
## Quick Start
|
|
10
|
-
|
|
11
|
-
import numclassify as nc
|
|
12
|
-
nc.classify(1729)
|
|
13
|
-
nc.classify_batch([6, 28, 496])
|
|
14
|
-
nc.find_by_property(start=1, end=1000, Perfect=True)
|
|
15
|
-
nc.random_number()
|
|
16
|
-
|
|
17
|
-
## Why numclassify?
|
|
18
|
-
|
|
19
|
-
- 153: Armstrong, Narcissistic, Harshad, Triangular...
|
|
20
|
-
- 1729: Taxicab, Carmichael, Dodecagonal...
|
|
21
|
-
- 28: Perfect, Triangular, Hexagonal, Keith...
|
|
22
|
-
|
|
23
|
-
3000+ named number types. Instant lookup. Zero dependencies.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|