absfuyu 5.0.0__py3-none-any.whl → 6.1.2__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.
Potentially problematic release.
This version of absfuyu might be problematic. Click here for more details.
- absfuyu/__init__.py +5 -3
- absfuyu/__main__.py +3 -3
- absfuyu/cli/__init__.py +13 -2
- absfuyu/cli/audio_group.py +98 -0
- absfuyu/cli/color.py +30 -14
- absfuyu/cli/config_group.py +9 -2
- absfuyu/cli/do_group.py +23 -6
- absfuyu/cli/game_group.py +27 -2
- absfuyu/cli/tool_group.py +81 -11
- absfuyu/config/__init__.py +3 -3
- absfuyu/core/__init__.py +12 -8
- absfuyu/core/baseclass.py +929 -96
- absfuyu/core/baseclass2.py +44 -3
- absfuyu/core/decorator.py +70 -4
- absfuyu/core/docstring.py +64 -41
- absfuyu/core/dummy_cli.py +3 -3
- absfuyu/core/dummy_func.py +19 -6
- absfuyu/dxt/__init__.py +2 -2
- absfuyu/dxt/base_type.py +93 -0
- absfuyu/dxt/dictext.py +204 -16
- absfuyu/dxt/dxt_support.py +2 -2
- absfuyu/dxt/intext.py +151 -34
- absfuyu/dxt/listext.py +969 -127
- absfuyu/dxt/strext.py +77 -17
- absfuyu/extra/__init__.py +2 -2
- absfuyu/extra/audio/__init__.py +8 -0
- absfuyu/extra/audio/_util.py +57 -0
- absfuyu/extra/audio/convert.py +192 -0
- absfuyu/extra/audio/lossless.py +281 -0
- absfuyu/extra/beautiful.py +3 -2
- absfuyu/extra/da/__init__.py +72 -0
- absfuyu/extra/da/dadf.py +1600 -0
- absfuyu/extra/da/dadf_base.py +186 -0
- absfuyu/extra/da/df_func.py +181 -0
- absfuyu/extra/da/mplt.py +219 -0
- absfuyu/extra/ggapi/__init__.py +8 -0
- absfuyu/extra/ggapi/gdrive.py +223 -0
- absfuyu/extra/ggapi/glicense.py +148 -0
- absfuyu/extra/ggapi/glicense_df.py +186 -0
- absfuyu/extra/ggapi/gsheet.py +88 -0
- absfuyu/extra/img/__init__.py +30 -0
- absfuyu/extra/img/converter.py +402 -0
- absfuyu/extra/img/dup_check.py +291 -0
- absfuyu/extra/pdf.py +87 -0
- absfuyu/extra/rclone.py +253 -0
- absfuyu/extra/xml.py +90 -0
- absfuyu/fun/__init__.py +7 -20
- absfuyu/fun/rubik.py +442 -0
- absfuyu/fun/tarot.py +2 -2
- absfuyu/game/__init__.py +2 -2
- absfuyu/game/game_stat.py +2 -2
- absfuyu/game/schulte.py +78 -0
- absfuyu/game/sudoku.py +2 -2
- absfuyu/game/tictactoe.py +2 -3
- absfuyu/game/wordle.py +6 -4
- absfuyu/general/__init__.py +4 -4
- absfuyu/general/content.py +4 -4
- absfuyu/general/human.py +2 -2
- absfuyu/general/resrel.py +213 -0
- absfuyu/general/shape.py +3 -8
- absfuyu/general/tax.py +344 -0
- absfuyu/logger.py +806 -59
- absfuyu/numbers/__init__.py +13 -0
- absfuyu/numbers/number_to_word.py +321 -0
- absfuyu/numbers/shorten_number.py +303 -0
- absfuyu/numbers/time_duration.py +217 -0
- absfuyu/pkg_data/__init__.py +2 -2
- absfuyu/pkg_data/deprecated.py +2 -2
- absfuyu/pkg_data/logo.py +1462 -0
- absfuyu/sort.py +4 -4
- absfuyu/tools/__init__.py +28 -2
- absfuyu/tools/checksum.py +144 -9
- absfuyu/tools/converter.py +120 -34
- absfuyu/tools/generator.py +461 -0
- absfuyu/tools/inspector.py +752 -0
- absfuyu/tools/keygen.py +2 -2
- absfuyu/tools/obfuscator.py +47 -9
- absfuyu/tools/passwordlib.py +89 -25
- absfuyu/tools/shutdownizer.py +3 -8
- absfuyu/tools/sw.py +718 -0
- absfuyu/tools/web.py +10 -13
- absfuyu/typings.py +138 -0
- absfuyu/util/__init__.py +114 -6
- absfuyu/util/api.py +41 -18
- absfuyu/util/cli.py +119 -0
- absfuyu/util/gui.py +91 -0
- absfuyu/util/json_method.py +43 -14
- absfuyu/util/lunar.py +2 -2
- absfuyu/util/package.py +124 -0
- absfuyu/util/path.py +702 -82
- absfuyu/util/performance.py +122 -7
- absfuyu/util/shorten_number.py +244 -21
- absfuyu/util/text_table.py +481 -0
- absfuyu/util/zipped.py +8 -7
- absfuyu/version.py +79 -59
- {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/METADATA +52 -11
- absfuyu-6.1.2.dist-info/RECORD +105 -0
- {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/WHEEL +1 -1
- absfuyu/extra/data_analysis.py +0 -1078
- absfuyu/general/generator.py +0 -303
- absfuyu-5.0.0.dist-info/RECORD +0 -68
- {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/entry_points.txt +0 -0
- {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/licenses/LICENSE +0 -0
absfuyu/dxt/dictext.py
CHANGED
|
@@ -3,10 +3,12 @@ Absfuyu: Data Extension
|
|
|
3
3
|
-----------------------
|
|
4
4
|
dict extension
|
|
5
5
|
|
|
6
|
-
Version:
|
|
7
|
-
Date updated:
|
|
6
|
+
Version: 6.1.1
|
|
7
|
+
Date updated: 30/12/2025 (dd/mm/yyyy)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
10
12
|
# Module Package
|
|
11
13
|
# ---------------------------------------------------------------------------
|
|
12
14
|
__all__ = ["DictExt", "DictAnalyzeResult"]
|
|
@@ -16,9 +18,20 @@ __all__ = ["DictExt", "DictAnalyzeResult"]
|
|
|
16
18
|
# ---------------------------------------------------------------------------
|
|
17
19
|
import operator
|
|
18
20
|
from collections.abc import Callable
|
|
19
|
-
from typing import Any, NamedTuple, Self
|
|
21
|
+
from typing import Any, Literal, NamedTuple, Self, TypeVar, overload
|
|
22
|
+
|
|
23
|
+
from absfuyu.core import GetClassMembersMixin, versionadded, versionchanged
|
|
24
|
+
|
|
25
|
+
# from absfuyu.typings import KT as _KT
|
|
26
|
+
# from absfuyu.typings import VT as _VT
|
|
20
27
|
|
|
21
|
-
|
|
28
|
+
|
|
29
|
+
# Type
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
KT = TypeVar("KT")
|
|
32
|
+
VT = TypeVar("VT")
|
|
33
|
+
VT2 = TypeVar("VT2")
|
|
34
|
+
R = TypeVar("R") # Return type - Can be anything
|
|
22
35
|
|
|
23
36
|
|
|
24
37
|
# Class
|
|
@@ -34,9 +47,12 @@ class DictAnalyzeResult(NamedTuple):
|
|
|
34
47
|
min_list: list
|
|
35
48
|
|
|
36
49
|
|
|
37
|
-
class
|
|
50
|
+
class DictExtLegacy(GetClassMembersMixin, dict[KT, VT]):
|
|
38
51
|
"""
|
|
39
|
-
``dict`` extension
|
|
52
|
+
``dict`` extension (with no generic type-hints)
|
|
53
|
+
|
|
54
|
+
>>> # For a list of new methods
|
|
55
|
+
>>> DictExt.show_all_methods()
|
|
40
56
|
"""
|
|
41
57
|
|
|
42
58
|
@versionchanged("3.3.0", reason="Updated return type")
|
|
@@ -53,13 +69,7 @@ class DictExt(ShowAllMethodsMixin, dict):
|
|
|
53
69
|
|
|
54
70
|
Example:
|
|
55
71
|
--------
|
|
56
|
-
>>> test = DictExt({
|
|
57
|
-
... "abc": 9,
|
|
58
|
-
... "def": 9,
|
|
59
|
-
... "ghi": 8,
|
|
60
|
-
... "jkl": 1,
|
|
61
|
-
... "mno": 1
|
|
62
|
-
... })
|
|
72
|
+
>>> test = DictExt({"abc": 9, "def": 9, "ghi": 8, "jkl": 1, "mno": 1})
|
|
63
73
|
>>> test.analyze()
|
|
64
74
|
DictAnalyzeResult(max_value=9, min_value=1, max_list=[('abc', 9), ('def', 9)], min_list=[('jkl', 1), ('mno', 1)])
|
|
65
75
|
"""
|
|
@@ -101,9 +111,9 @@ class DictExt(ShowAllMethodsMixin, dict):
|
|
|
101
111
|
{9: 'abc'}
|
|
102
112
|
"""
|
|
103
113
|
# return self.__class__(zip(self.values(), self.keys()))
|
|
104
|
-
return self.__class__({v: k for k, v in self.items()})
|
|
114
|
+
return self.__class__({v: k for k, v in self.items()}) # type: ignore
|
|
105
115
|
|
|
106
|
-
def apply(self, func: Callable, apply_to_value: bool = True) -> Self:
|
|
116
|
+
def apply(self, func: Callable[[Any], Any], apply_to_value: bool = True) -> Self:
|
|
107
117
|
"""
|
|
108
118
|
Apply function to ``DictExt.keys()`` or ``DictExt.values()``
|
|
109
119
|
|
|
@@ -138,7 +148,7 @@ class DictExt(ShowAllMethodsMixin, dict):
|
|
|
138
148
|
@versionadded("3.4.0")
|
|
139
149
|
def aggregate(
|
|
140
150
|
self,
|
|
141
|
-
other_dict: dict,
|
|
151
|
+
other_dict: dict[KT, VT],
|
|
142
152
|
default_value: Any = 0,
|
|
143
153
|
operator_func: Callable[[Any, Any], Any] = operator.add, # operator add
|
|
144
154
|
) -> Self:
|
|
@@ -199,3 +209,181 @@ class DictExt(ShowAllMethodsMixin, dict):
|
|
|
199
209
|
merged_output[k] = [value_self, value_other]
|
|
200
210
|
|
|
201
211
|
return self.__class__(merged_output)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# Type-hints are hard coded, no work around Self[KT, VT] yet
|
|
215
|
+
class DictExt(GetClassMembersMixin, dict[KT, VT]):
|
|
216
|
+
"""
|
|
217
|
+
``dict`` extension
|
|
218
|
+
|
|
219
|
+
>>> # For a list of new methods
|
|
220
|
+
>>> DictExt.show_all_methods()
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
# Analyze
|
|
224
|
+
@versionchanged("3.3.0", reason="Updated return type")
|
|
225
|
+
def analyze(self) -> DictAnalyzeResult:
|
|
226
|
+
"""
|
|
227
|
+
Analyze all the key values (``int``, ``float``)
|
|
228
|
+
in ``dict`` then return highest/lowest index
|
|
229
|
+
|
|
230
|
+
Returns
|
|
231
|
+
-------
|
|
232
|
+
dict
|
|
233
|
+
Analyzed data
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
--------
|
|
238
|
+
>>> test = DictExt({"abc": 9, "def": 9, "ghi": 8, "jkl": 1, "mno": 1})
|
|
239
|
+
>>> test.analyze()
|
|
240
|
+
DictAnalyzeResult(max_value=9, min_value=1, max_list=[('abc', 9), ('def', 9)], min_list=[('jkl', 1), ('mno', 1)])
|
|
241
|
+
"""
|
|
242
|
+
try:
|
|
243
|
+
dct: dict = self.copy()
|
|
244
|
+
|
|
245
|
+
max_val: int | float = max(list(dct.values()))
|
|
246
|
+
min_val: int | float = min(list(dct.values()))
|
|
247
|
+
max_list = []
|
|
248
|
+
min_list = []
|
|
249
|
+
|
|
250
|
+
for k, v in dct.items():
|
|
251
|
+
if v == max_val:
|
|
252
|
+
max_list.append((k, v))
|
|
253
|
+
if v == min_val:
|
|
254
|
+
min_list.append((k, v))
|
|
255
|
+
|
|
256
|
+
return DictAnalyzeResult(max_val, min_val, max_list, min_list)
|
|
257
|
+
|
|
258
|
+
except TypeError:
|
|
259
|
+
err_msg = "Value must be int or float"
|
|
260
|
+
# logger.error(err_msg)
|
|
261
|
+
raise ValueError(err_msg) # noqa: B904
|
|
262
|
+
|
|
263
|
+
# Swap
|
|
264
|
+
def swap_items(self) -> DictExt[VT, KT]:
|
|
265
|
+
"""
|
|
266
|
+
Swap ``dict.keys()`` with ``dict.values()``
|
|
267
|
+
|
|
268
|
+
Returns
|
|
269
|
+
-------
|
|
270
|
+
DictExt
|
|
271
|
+
Swapped dict
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
Example:
|
|
275
|
+
--------
|
|
276
|
+
>>> test = DictExt({"abc": 9})
|
|
277
|
+
>>> test.swap_items()
|
|
278
|
+
{9: 'abc'}
|
|
279
|
+
"""
|
|
280
|
+
# return self.__class__(zip(self.values(), self.keys()))
|
|
281
|
+
out: dict[VT, KT] = {v: k for k, v in self.items()}
|
|
282
|
+
# return self.__class__({v: k for k, v in self.items()}) # type: ignore
|
|
283
|
+
return self.__class__(out) # type: ignore
|
|
284
|
+
|
|
285
|
+
# Apply
|
|
286
|
+
@overload
|
|
287
|
+
def apply(self, func: Callable[[VT], R]) -> DictExt[KT, R]: ...
|
|
288
|
+
|
|
289
|
+
@overload
|
|
290
|
+
def apply(self, func: Callable[[KT], R], apply_to_value: Literal[False] = ...) -> DictExt[R, VT]: ...
|
|
291
|
+
|
|
292
|
+
def apply(self, func: Callable[[KT | VT], R], apply_to_value: bool = True) -> DictExt[KT | R, VT | R]: # type: ignore
|
|
293
|
+
"""
|
|
294
|
+
Apply function to ``DictExt.keys()`` or ``DictExt.values()``
|
|
295
|
+
|
|
296
|
+
Parameters
|
|
297
|
+
----------
|
|
298
|
+
func : Callable
|
|
299
|
+
Callable function
|
|
300
|
+
|
|
301
|
+
apply_to_value : bool
|
|
302
|
+
| ``True``: Apply ``func`` to ``DictExt.values()``
|
|
303
|
+
| ``False``: Apply ``func`` to ``DictExt.keys()``
|
|
304
|
+
|
|
305
|
+
Returns
|
|
306
|
+
-------
|
|
307
|
+
DictExt
|
|
308
|
+
DictExt
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
Example:
|
|
312
|
+
--------
|
|
313
|
+
>>> test = DictExt({"abc": 9})
|
|
314
|
+
>>> test.apply(str)
|
|
315
|
+
{'abc': '9'}
|
|
316
|
+
"""
|
|
317
|
+
if apply_to_value:
|
|
318
|
+
new_dict_v: dict[KT, R] = {k: func(v) for k, v in self.items()}
|
|
319
|
+
return self.__class__(new_dict_v) # type: ignore
|
|
320
|
+
else:
|
|
321
|
+
new_dict_k: dict[R, VT] = {func(k): v for k, v in self.items()}
|
|
322
|
+
return self.__class__(new_dict_k) # type: ignore
|
|
323
|
+
|
|
324
|
+
# Aggregate
|
|
325
|
+
@versionchanged("5.0.0", reason="Updated to handle more types and operator")
|
|
326
|
+
@versionadded("3.4.0")
|
|
327
|
+
def aggregate(
|
|
328
|
+
self,
|
|
329
|
+
other_dict: dict[KT, VT | VT2],
|
|
330
|
+
default_value: Any = 0,
|
|
331
|
+
operator_func: Callable[[Any, Any], R] = operator.add, # operator add
|
|
332
|
+
):
|
|
333
|
+
"""
|
|
334
|
+
Aggregates the values of the current dictionary with another dictionary.
|
|
335
|
+
|
|
336
|
+
For each unique key, this method applies the specified operator to the values
|
|
337
|
+
from both dictionaries. If a key exists in only one dictionary, its value is used.
|
|
338
|
+
If an error occurs during aggregation (e.g., incompatible types), the values
|
|
339
|
+
from both dictionaries are returned as a list.
|
|
340
|
+
|
|
341
|
+
Parameters
|
|
342
|
+
----------
|
|
343
|
+
other_dict : dict
|
|
344
|
+
The dictionary to aggregate with.
|
|
345
|
+
|
|
346
|
+
default_value : Any, optional
|
|
347
|
+
The value to use for missing keys, by default ``0``
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
operator_func : Callable[[Any, Any], Any], optional
|
|
351
|
+
A function that takes two arguments and returns a single value,
|
|
352
|
+
by default ``operator.add``
|
|
353
|
+
|
|
354
|
+
Returns
|
|
355
|
+
-------
|
|
356
|
+
Self
|
|
357
|
+
A new instance of the aggregated dictionary.
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
Example:
|
|
361
|
+
--------
|
|
362
|
+
>>> test = DictExt({"test": 5, "test2": 9})
|
|
363
|
+
>>> agg = {"test1": 10, "test2": 1}
|
|
364
|
+
>>> print(test.aggregate(agg))
|
|
365
|
+
{'test1': 10, 'test': 5, 'test2': 10}
|
|
366
|
+
|
|
367
|
+
>>> test = DictExt({"test": 5, "test2": 9})
|
|
368
|
+
>>> agg = {"test1": 10, "test2": "1"}
|
|
369
|
+
>>> print(test.aggregate(agg))
|
|
370
|
+
{'test1': 10, 'test': 5, 'test2': [9, '1']}
|
|
371
|
+
"""
|
|
372
|
+
merged_output: dict[KT, VT | R | list[VT | VT2]] = {}
|
|
373
|
+
|
|
374
|
+
# Create a set of all unique keys from both dictionaries
|
|
375
|
+
all_keys = set(self) | set(other_dict)
|
|
376
|
+
|
|
377
|
+
for k in all_keys:
|
|
378
|
+
# Retrieve values with default fallback
|
|
379
|
+
value_self = self.get(k, default_value)
|
|
380
|
+
value_other = other_dict.get(k, default_value)
|
|
381
|
+
|
|
382
|
+
try:
|
|
383
|
+
# Attempt to apply the operator for existing keys
|
|
384
|
+
merged_output[k] = operator_func(value_self, value_other)
|
|
385
|
+
except TypeError:
|
|
386
|
+
# If a TypeError occurs (e.g., if values are not compatible), store values as a list
|
|
387
|
+
merged_output[k] = [value_self, value_other] # type: ignore
|
|
388
|
+
|
|
389
|
+
return self.__class__(merged_output) # type: ignore
|
absfuyu/dxt/dxt_support.py
CHANGED
absfuyu/dxt/intext.py
CHANGED
|
@@ -3,8 +3,8 @@ Absfuyu: Data Extension
|
|
|
3
3
|
-----------------------
|
|
4
4
|
int extension
|
|
5
5
|
|
|
6
|
-
Version:
|
|
7
|
-
Date updated:
|
|
6
|
+
Version: 6.1.1
|
|
7
|
+
Date updated: 30/12/2025 (dd/mm/yyyy)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
# Module Package
|
|
@@ -15,10 +15,12 @@ __all__ = ["IntExt", "Pow"]
|
|
|
15
15
|
# Library
|
|
16
16
|
# ---------------------------------------------------------------------------
|
|
17
17
|
import math
|
|
18
|
+
from abc import ABC
|
|
18
19
|
from collections import Counter
|
|
19
|
-
from typing import Any, Self
|
|
20
|
+
from typing import Any, Literal, Self, overload, override
|
|
20
21
|
|
|
21
|
-
from absfuyu.core import
|
|
22
|
+
from absfuyu.core.baseclass import GetClassMembersMixin
|
|
23
|
+
from absfuyu.core.docstring import deprecated, versionadded, versionchanged
|
|
22
24
|
from absfuyu.dxt.dxt_support import DictBoolTrue
|
|
23
25
|
|
|
24
26
|
|
|
@@ -32,20 +34,23 @@ class Pow:
|
|
|
32
34
|
self.power_by = power_by
|
|
33
35
|
|
|
34
36
|
def __str__(self) -> str:
|
|
37
|
+
return self.__repr__()
|
|
38
|
+
|
|
39
|
+
def __repr__(self) -> str:
|
|
35
40
|
if self.power_by == 1:
|
|
36
41
|
return str(self.number)
|
|
37
42
|
else:
|
|
38
43
|
return f"{self.number}^{self.power_by}"
|
|
39
44
|
# return f"{self.__class__.__name__}({self.number}, {self.power_by})"
|
|
40
45
|
|
|
41
|
-
def __repr__(self) -> str:
|
|
42
|
-
return self.__str__()
|
|
43
|
-
|
|
44
46
|
def to_list(self) -> list[int]:
|
|
45
47
|
"""
|
|
46
48
|
Convert into list
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
list[int | float]
|
|
53
|
+
list
|
|
49
54
|
"""
|
|
50
55
|
return [self.number] * self.power_by # type: ignore
|
|
51
56
|
|
|
@@ -53,18 +58,25 @@ class Pow:
|
|
|
53
58
|
"""
|
|
54
59
|
Calculate the ``self.number`` to the power of ``self.power_by``
|
|
55
60
|
|
|
56
|
-
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
float
|
|
64
|
+
Result
|
|
57
65
|
"""
|
|
58
66
|
# return self.number**self.power_by
|
|
59
67
|
return math.pow(self.number, self.power_by)
|
|
60
68
|
|
|
61
69
|
|
|
62
|
-
class IntExt(
|
|
70
|
+
class IntExt(GetClassMembersMixin, int):
|
|
63
71
|
"""
|
|
64
72
|
``int`` extension
|
|
73
|
+
|
|
74
|
+
>>> # For a list of new methods
|
|
75
|
+
>>> IntExt.show_all_methods()
|
|
65
76
|
"""
|
|
66
77
|
|
|
67
|
-
# convert
|
|
78
|
+
# convert
|
|
79
|
+
@deprecated("5.4.0", reason="Use format(...) instead")
|
|
68
80
|
def to_binary(self) -> str:
|
|
69
81
|
"""
|
|
70
82
|
Convert to binary number
|
|
@@ -77,7 +89,7 @@ class IntExt(ShowAllMethodsMixin, int):
|
|
|
77
89
|
|
|
78
90
|
Example:
|
|
79
91
|
--------
|
|
80
|
-
>>> test =
|
|
92
|
+
>>> test = IntExt(10)
|
|
81
93
|
>>> test.to_binary()
|
|
82
94
|
'1010'
|
|
83
95
|
"""
|
|
@@ -95,7 +107,7 @@ class IntExt(ShowAllMethodsMixin, int):
|
|
|
95
107
|
|
|
96
108
|
Example:
|
|
97
109
|
--------
|
|
98
|
-
>>> test =
|
|
110
|
+
>>> test = IntExt(10)
|
|
99
111
|
>>> test.to_celcius_degree()
|
|
100
112
|
-12.222222222222221
|
|
101
113
|
"""
|
|
@@ -114,7 +126,7 @@ class IntExt(ShowAllMethodsMixin, int):
|
|
|
114
126
|
|
|
115
127
|
Example:
|
|
116
128
|
--------
|
|
117
|
-
>>> test =
|
|
129
|
+
>>> test = IntExt(10)
|
|
118
130
|
>>> test.to_fahrenheit_degree()
|
|
119
131
|
50.0
|
|
120
132
|
"""
|
|
@@ -127,13 +139,13 @@ class IntExt(ShowAllMethodsMixin, int):
|
|
|
127
139
|
|
|
128
140
|
Returns
|
|
129
141
|
-------
|
|
130
|
-
|
|
131
|
-
Reversed number
|
|
142
|
+
Self
|
|
143
|
+
Reversed number.
|
|
132
144
|
|
|
133
145
|
|
|
134
146
|
Example:
|
|
135
147
|
--------
|
|
136
|
-
>>> test =
|
|
148
|
+
>>> test = IntExt(102)
|
|
137
149
|
>>> test.reverse()
|
|
138
150
|
201
|
|
139
151
|
"""
|
|
@@ -381,13 +393,13 @@ class IntExt(ShowAllMethodsMixin, int):
|
|
|
381
393
|
|
|
382
394
|
Returns
|
|
383
395
|
-------
|
|
384
|
-
|
|
385
|
-
Least common multiple
|
|
396
|
+
Self
|
|
397
|
+
Least common multiple.
|
|
386
398
|
|
|
387
399
|
|
|
388
400
|
Example:
|
|
389
401
|
--------
|
|
390
|
-
>>> test =
|
|
402
|
+
>>> test = IntExt(102)
|
|
391
403
|
>>> test.lcm(5)
|
|
392
404
|
510
|
|
393
405
|
"""
|
|
@@ -405,13 +417,13 @@ class IntExt(ShowAllMethodsMixin, int):
|
|
|
405
417
|
|
|
406
418
|
Returns
|
|
407
419
|
-------
|
|
408
|
-
|
|
409
|
-
Greatest common divisor
|
|
420
|
+
Self
|
|
421
|
+
Greatest common divisor.
|
|
410
422
|
|
|
411
423
|
|
|
412
424
|
Example:
|
|
413
425
|
--------
|
|
414
|
-
>>> test =
|
|
426
|
+
>>> test = IntExt(1024)
|
|
415
427
|
>>> test.gcd(8)
|
|
416
428
|
8
|
|
417
429
|
"""
|
|
@@ -430,28 +442,31 @@ class IntExt(ShowAllMethodsMixin, int):
|
|
|
430
442
|
|
|
431
443
|
Returns
|
|
432
444
|
-------
|
|
433
|
-
|
|
434
|
-
|
|
445
|
+
Self
|
|
446
|
+
IntExt
|
|
435
447
|
|
|
436
448
|
|
|
437
449
|
Example:
|
|
438
450
|
--------
|
|
439
|
-
>>> test =
|
|
451
|
+
>>> test = IntExt(119)
|
|
440
452
|
>>> test.add_to_one_digit()
|
|
441
453
|
2
|
|
442
454
|
|
|
443
|
-
>>> test =
|
|
455
|
+
>>> test = IntExt(119)
|
|
444
456
|
>>> test.add_to_one_digit(master_number=True)
|
|
445
457
|
11
|
|
446
458
|
"""
|
|
459
|
+
|
|
447
460
|
number = int(self)
|
|
448
|
-
if number < 0:
|
|
461
|
+
if number < 0: # Convert positive
|
|
449
462
|
number *= -1
|
|
463
|
+
|
|
450
464
|
while len(str(number)) != 1:
|
|
451
465
|
number = sum(map(int, str(number)))
|
|
452
466
|
if master_number:
|
|
453
467
|
if number == 22 or number == 11:
|
|
454
468
|
break # Master number
|
|
469
|
+
|
|
455
470
|
return self.__class__(number)
|
|
456
471
|
|
|
457
472
|
@versionchanged("5.0.0", reason="Removed ``short_form`` parameter")
|
|
@@ -467,7 +482,7 @@ class IntExt(ShowAllMethodsMixin, int):
|
|
|
467
482
|
|
|
468
483
|
Example:
|
|
469
484
|
--------
|
|
470
|
-
>>> test =
|
|
485
|
+
>>> test = IntExt(1024)
|
|
471
486
|
>>> test.divisible_list()
|
|
472
487
|
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
|
|
473
488
|
"""
|
|
@@ -478,6 +493,12 @@ class IntExt(ShowAllMethodsMixin, int):
|
|
|
478
493
|
|
|
479
494
|
return divi_list
|
|
480
495
|
|
|
496
|
+
@overload
|
|
497
|
+
def prime_factor(self) -> list[Pow]: ... # type: ignore
|
|
498
|
+
|
|
499
|
+
@overload
|
|
500
|
+
def prime_factor(self, short_form: Literal[False] = ...) -> list[int]: ...
|
|
501
|
+
|
|
481
502
|
def prime_factor(self, short_form: bool = True) -> list[int] | list[Pow]:
|
|
482
503
|
"""
|
|
483
504
|
Prime factor
|
|
@@ -500,14 +521,15 @@ class IntExt(ShowAllMethodsMixin, int):
|
|
|
500
521
|
|
|
501
522
|
Example:
|
|
502
523
|
--------
|
|
503
|
-
>>> test =
|
|
524
|
+
>>> test = IntExt(1024)
|
|
504
525
|
>>> test.prime_factor()
|
|
505
526
|
[2^10]
|
|
506
527
|
|
|
507
|
-
>>> test =
|
|
528
|
+
>>> test = IntExt(1024)
|
|
508
529
|
>>> test.prime_factor(short_form=False)
|
|
509
530
|
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
|
|
510
531
|
"""
|
|
532
|
+
|
|
511
533
|
# Generate list
|
|
512
534
|
factors = []
|
|
513
535
|
divisor = 2
|
|
@@ -530,7 +552,7 @@ class IntExt(ShowAllMethodsMixin, int):
|
|
|
530
552
|
# analyze
|
|
531
553
|
def analyze(self, short_form: bool = True) -> dict[str, dict[str, Any]]:
|
|
532
554
|
"""
|
|
533
|
-
Analyze the number with almost all ``
|
|
555
|
+
Analyze the number with almost all ``IntExt`` method
|
|
534
556
|
|
|
535
557
|
Parameters
|
|
536
558
|
----------
|
|
@@ -546,7 +568,7 @@ class IntExt(ShowAllMethodsMixin, int):
|
|
|
546
568
|
|
|
547
569
|
Example:
|
|
548
570
|
--------
|
|
549
|
-
>>> test =
|
|
571
|
+
>>> test = IntExt(1024)
|
|
550
572
|
>>> test.analyze()
|
|
551
573
|
{
|
|
552
574
|
'summary': {'number': 1024, 'length': 4, 'even': True, 'prime factor': [2^10], 'divisible': [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]},
|
|
@@ -559,7 +581,7 @@ class IntExt(ShowAllMethodsMixin, int):
|
|
|
559
581
|
"number": self,
|
|
560
582
|
"length": len(str(self)),
|
|
561
583
|
"even": self.is_even(),
|
|
562
|
-
"prime factor": self.prime_factor(short_form=short_form),
|
|
584
|
+
"prime factor": self.prime_factor(short_form=short_form), # type: ignore[call-overload]
|
|
563
585
|
"divisible": self.divisible_list(),
|
|
564
586
|
},
|
|
565
587
|
"convert": {
|
|
@@ -584,3 +606,98 @@ class IntExt(ShowAllMethodsMixin, int):
|
|
|
584
606
|
|
|
585
607
|
output["characteristic"] = characteristic
|
|
586
608
|
return output # type: ignore
|
|
609
|
+
|
|
610
|
+
@versionadded("5.1.0")
|
|
611
|
+
def split(self, parts: int) -> list[int]:
|
|
612
|
+
"""
|
|
613
|
+
Evenly split the number into ``parts`` parts
|
|
614
|
+
|
|
615
|
+
Parameters
|
|
616
|
+
----------
|
|
617
|
+
parts : int
|
|
618
|
+
Split by how many parts
|
|
619
|
+
|
|
620
|
+
Returns
|
|
621
|
+
-------
|
|
622
|
+
list[int]
|
|
623
|
+
List of evenly splitted numbers
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
Example:
|
|
627
|
+
--------
|
|
628
|
+
>>> IntExt(10).split(4)
|
|
629
|
+
[2, 2, 3, 3]
|
|
630
|
+
"""
|
|
631
|
+
p = max(1, parts)
|
|
632
|
+
if p == 1:
|
|
633
|
+
return [int(self)]
|
|
634
|
+
|
|
635
|
+
quotient, remainder = divmod(self, p)
|
|
636
|
+
return [quotient + (i >= (p - remainder)) for i in range(p)]
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
# Class
|
|
640
|
+
# ---------------------------------------------------------------------------
|
|
641
|
+
class IntBase(int, ABC):
|
|
642
|
+
"""
|
|
643
|
+
A base class for creating custom integer-like types.
|
|
644
|
+
Provides a hook for validation and extension.
|
|
645
|
+
|
|
646
|
+
Usage:
|
|
647
|
+
------
|
|
648
|
+
Use ``_validate(cls, value: int) -> None:`` classmethod to create custom validator
|
|
649
|
+
"""
|
|
650
|
+
|
|
651
|
+
def __new__(cls, value: int = 0) -> Self:
|
|
652
|
+
# Ensure the value is an integer
|
|
653
|
+
if not isinstance(value, int):
|
|
654
|
+
raise TypeError(f"{cls.__name__} must be initialized with an int, got {type(value).__name__}")
|
|
655
|
+
# Optional validation hook
|
|
656
|
+
cls._validate(value)
|
|
657
|
+
return int.__new__(cls, value)
|
|
658
|
+
|
|
659
|
+
@classmethod
|
|
660
|
+
def _validate(cls, value: int) -> None:
|
|
661
|
+
"""Override in subclasses to add custom validation."""
|
|
662
|
+
pass
|
|
663
|
+
|
|
664
|
+
def __repr__(self) -> str:
|
|
665
|
+
return f"{self.__class__.__name__}({int(self)})"
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
class PositiveInt(IntBase):
|
|
669
|
+
"""Only allows positive int"""
|
|
670
|
+
|
|
671
|
+
@classmethod
|
|
672
|
+
@override
|
|
673
|
+
def _validate(cls, value: int) -> None:
|
|
674
|
+
if value < 0:
|
|
675
|
+
raise ValueError(f"{cls.__name__} must be non-negative")
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
class NegativeInt(IntBase):
|
|
679
|
+
"""Only allows negative int"""
|
|
680
|
+
|
|
681
|
+
@classmethod
|
|
682
|
+
@override
|
|
683
|
+
def _validate(cls, value: int) -> None:
|
|
684
|
+
if value >= 0:
|
|
685
|
+
raise ValueError(f"{cls.__name__} must be non-positive")
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
class IntWithNote(IntBase):
|
|
689
|
+
"""Int with additional note"""
|
|
690
|
+
|
|
691
|
+
def __new__(cls, value: int = 0) -> Self:
|
|
692
|
+
ins = super().__new__(cls, value)
|
|
693
|
+
ins.note = ""
|
|
694
|
+
return ins
|
|
695
|
+
|
|
696
|
+
def __repr__(self) -> str:
|
|
697
|
+
if self.note == "":
|
|
698
|
+
return f"{int(self)}"
|
|
699
|
+
# return super().__repr__()
|
|
700
|
+
return f"{self.__class__.__name__}({int(self)}, note={repr(self.note)})"
|
|
701
|
+
|
|
702
|
+
def add_note(self, note: str) -> None:
|
|
703
|
+
self.note = note
|