absfuyu 4.1.1__py3-none-any.whl → 5.0.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.

Potentially problematic release.


This version of absfuyu might be problematic. Click here for more details.

Files changed (76) hide show
  1. absfuyu/__init__.py +4 -4
  2. absfuyu/__main__.py +13 -1
  3. absfuyu/cli/__init__.py +4 -2
  4. absfuyu/cli/color.py +7 -0
  5. absfuyu/cli/do_group.py +9 -91
  6. absfuyu/cli/tool_group.py +136 -0
  7. absfuyu/config/__init__.py +17 -34
  8. absfuyu/core/__init__.py +49 -0
  9. absfuyu/core/baseclass.py +299 -0
  10. absfuyu/core/baseclass2.py +165 -0
  11. absfuyu/core/decorator.py +67 -0
  12. absfuyu/core/docstring.py +163 -0
  13. absfuyu/core/dummy_cli.py +67 -0
  14. absfuyu/core/dummy_func.py +47 -0
  15. absfuyu/dxt/__init__.py +42 -0
  16. absfuyu/dxt/dictext.py +201 -0
  17. absfuyu/dxt/dxt_support.py +79 -0
  18. absfuyu/dxt/intext.py +586 -0
  19. absfuyu/dxt/listext.py +508 -0
  20. absfuyu/dxt/strext.py +530 -0
  21. absfuyu/{extensions → extra}/__init__.py +3 -2
  22. absfuyu/extra/beautiful.py +251 -0
  23. absfuyu/{extensions/extra → extra}/data_analysis.py +51 -82
  24. absfuyu/fun/__init__.py +110 -135
  25. absfuyu/fun/tarot.py +9 -17
  26. absfuyu/game/__init__.py +6 -0
  27. absfuyu/game/game_stat.py +6 -0
  28. absfuyu/game/sudoku.py +7 -1
  29. absfuyu/game/tictactoe.py +12 -5
  30. absfuyu/game/wordle.py +14 -8
  31. absfuyu/general/__init__.py +6 -79
  32. absfuyu/general/content.py +22 -36
  33. absfuyu/general/generator.py +17 -42
  34. absfuyu/general/human.py +108 -228
  35. absfuyu/general/shape.py +1334 -0
  36. absfuyu/logger.py +8 -13
  37. absfuyu/pkg_data/__init__.py +137 -99
  38. absfuyu/pkg_data/deprecated.py +133 -0
  39. absfuyu/pkg_data/passwordlib_lzma.pkl +0 -0
  40. absfuyu/sort.py +6 -130
  41. absfuyu/tools/__init__.py +2 -2
  42. absfuyu/tools/checksum.py +44 -22
  43. absfuyu/tools/converter.py +82 -50
  44. absfuyu/tools/keygen.py +25 -30
  45. absfuyu/tools/obfuscator.py +246 -112
  46. absfuyu/tools/passwordlib.py +330 -0
  47. absfuyu/tools/shutdownizer.py +287 -0
  48. absfuyu/tools/web.py +2 -9
  49. absfuyu/util/__init__.py +15 -15
  50. absfuyu/util/api.py +10 -15
  51. absfuyu/util/json_method.py +7 -24
  52. absfuyu/util/lunar.py +3 -9
  53. absfuyu/util/path.py +22 -27
  54. absfuyu/util/performance.py +43 -67
  55. absfuyu/util/shorten_number.py +65 -14
  56. absfuyu/util/zipped.py +9 -15
  57. absfuyu-5.0.0.dist-info/METADATA +143 -0
  58. absfuyu-5.0.0.dist-info/RECORD +68 -0
  59. absfuyu/core.py +0 -57
  60. absfuyu/everything.py +0 -32
  61. absfuyu/extensions/beautiful.py +0 -188
  62. absfuyu/extensions/dev/__init__.py +0 -244
  63. absfuyu/extensions/dev/password_hash.py +0 -80
  64. absfuyu/extensions/dev/passwordlib.py +0 -258
  65. absfuyu/extensions/dev/project_starter.py +0 -60
  66. absfuyu/extensions/dev/shutdownizer.py +0 -156
  67. absfuyu/extensions/extra/__init__.py +0 -24
  68. absfuyu/fun/WGS.py +0 -134
  69. absfuyu/general/data_extension.py +0 -1796
  70. absfuyu/tools/stats.py +0 -226
  71. absfuyu/util/pkl.py +0 -67
  72. absfuyu-4.1.1.dist-info/METADATA +0 -121
  73. absfuyu-4.1.1.dist-info/RECORD +0 -61
  74. {absfuyu-4.1.1.dist-info → absfuyu-5.0.0.dist-info}/WHEEL +0 -0
  75. {absfuyu-4.1.1.dist-info → absfuyu-5.0.0.dist-info}/entry_points.txt +0 -0
  76. {absfuyu-4.1.1.dist-info → absfuyu-5.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,1796 +0,0 @@
1
- """
2
- Absfuyu: Data extension
3
- -----------------------
4
- Extension for data type such as ``list``, ``str``, ``dict``, ...
5
-
6
- Version: 1.15.2
7
- Date updated: 06/01/2025 (dd/mm/yyyy)
8
-
9
- Features:
10
- ---------
11
- - DictExt
12
- - IntNumber
13
- - ListExt
14
- - Text
15
-
16
- Usage:
17
- ------
18
- >>> from absfuyu.general.data_extension import DictExt, IntNumber, ListExt, Text
19
- """
20
-
21
- # Module level
22
- ###########################################################################
23
- __all__ = [
24
- # Main
25
- "Text",
26
- "IntNumber",
27
- "ListExt",
28
- "DictExt",
29
- # Sub
30
- "Pow",
31
- "ListREPR",
32
- "ListNoDunder",
33
- "DictBoolTrue",
34
- "DictBoolFalse",
35
- # "DictNoDunder",
36
- # Dict format
37
- "TextAnalyzeDictFormat",
38
- # Support
39
- "DictAnalyzeResult",
40
- ]
41
-
42
-
43
- # Library
44
- ###########################################################################
45
- import math
46
- import operator
47
- import random
48
- from collections import Counter
49
- from itertools import accumulate, chain, groupby
50
- from typing import Any, Callable, NamedTuple, NotRequired, Self, TypedDict, Union
51
-
52
- from deprecated.sphinx import versionadded, versionchanged
53
-
54
- from absfuyu.general.generator import Charset, Generator
55
- from absfuyu.logger import _compress_list_for_print, logger
56
- from absfuyu.util import set_min, set_min_max
57
-
58
-
59
- # Function
60
- ###########################################################################
61
- def _dict_bool(dict_object: dict, option: bool) -> dict | None:
62
- """
63
- Support function DictBool class
64
- """
65
- out = dict()
66
- for k, v in dict_object.items():
67
- if v == option:
68
- out[k] = v
69
- if out:
70
- return out
71
- else:
72
- return None
73
-
74
-
75
- # MARK: Sub class
76
- ###########################################################################
77
- class Pow:
78
- """Number power by a number"""
79
-
80
- def __init__(self, number: int | float, power_by: int | float) -> None:
81
- self.number = number
82
- self.power_by = power_by
83
-
84
- def __str__(self) -> str:
85
- if self.power_by == 1:
86
- return str(self.number)
87
- else:
88
- return f"{self.number}^{self.power_by}"
89
- # return f"{self.__class__.__name__}({self.number}, {self.power_by})"
90
-
91
- def __repr__(self) -> str:
92
- return self.__str__()
93
-
94
- def to_list(self) -> list[int | float]:
95
- """
96
- Convert into list
97
-
98
- :rtype: list[int | float]
99
- """
100
- return [self.number] * int(self.power_by)
101
-
102
- def calculate(self) -> float:
103
- """
104
- Calculate the ``self.number`` to the power of ``self.power_by``
105
-
106
- :rtype: float
107
- """
108
- # return self.number**self.power_by
109
- return math.pow(self.number, self.power_by)
110
-
111
-
112
- class ListREPR(list):
113
- """Show ``list`` in shorter form"""
114
-
115
- def __repr__(self) -> str:
116
- return _compress_list_for_print(self, 9)
117
-
118
-
119
- class ListNoDunder(list[str]):
120
- """Use with ``object.__dir__()``"""
121
-
122
- def __repr__(self) -> str:
123
- out = [x for x in self if not x.startswith("__")]
124
- return out.__repr__()
125
-
126
-
127
- class DictBoolTrue(dict[Any, bool]):
128
- """Only show items when ``values == True`` in ``__repr__()``"""
129
-
130
- def __repr__(self) -> str:
131
- temp = self.copy()
132
- return _dict_bool(temp, True).__repr__()
133
-
134
-
135
- class DictBoolFalse(dict[Any, bool]):
136
- """Only show items when ``values == False`` in ``__repr__()``"""
137
-
138
- def __repr__(self) -> str:
139
- temp = self.copy()
140
- return _dict_bool(temp, False).__repr__()
141
-
142
-
143
- # class DictNoDunder(dict): # W.I.P
144
- # """Remove dunder methods in ``__repr__()`` of dict"""
145
-
146
- # def __repr__(self) -> str:
147
- # temp = self.copy()
148
- # out = dict()
149
- # for k, v in temp.items():
150
- # if not str(k).startswith("__"):
151
- # out.__setattr__(k, v)
152
- # return out.__repr__()
153
-
154
-
155
- class TextAnalyzeDictFormat(TypedDict):
156
- """
157
- Dict format for ``Text.analyze()`` method
158
-
159
- Parameters
160
- ----------
161
- digit : int
162
- Number of digit characters
163
-
164
- uppercase : int
165
- Number of uppercase characters
166
-
167
- lowercase : int
168
- Number of lowercase characters
169
-
170
- other : int
171
- Number of other printable characters
172
-
173
- is_pangram : NotRequired[bool]
174
- Is a pangram (Not required)
175
-
176
- is_palindrome : NotRequired[bool]
177
- Is a palindrome (Not required)
178
- """
179
-
180
- digit: int
181
- uppercase: int
182
- lowercase: int
183
- other: int
184
- is_pangram: NotRequired[bool]
185
- is_palindrome: NotRequired[bool]
186
-
187
-
188
- class DictAnalyzeResult(NamedTuple):
189
- """
190
- Result for ``DictExt.analyze()``
191
- """
192
-
193
- max_value: int | float
194
- min_value: int | float
195
- max_list: list
196
- min_list: list
197
-
198
-
199
- # Class
200
- ###########################################################################
201
- # MARK: Text
202
- class Text(str):
203
- """
204
- ``str`` extension
205
- """
206
-
207
- def divide(self, string_split_size: int = 60) -> list[str]:
208
- """
209
- Divide long string into smaller size
210
-
211
- Parameters
212
- ----------
213
- string_split_size : int
214
- Divide string every ``x`` character
215
- (Default: ``60``)
216
-
217
- Returns
218
- -------
219
- list[str]
220
- A list in which each item is a smaller
221
- string with the size of ``string_split_size``
222
- (need to be concaternate later)
223
-
224
-
225
- Example:
226
- --------
227
- >>> test = Text("This is an extremely long line of text!")
228
- >>> test.divide(string_split_size=20)
229
- ['This is an extremely', ' long line of text!']
230
- """
231
- temp = str(self)
232
- output = []
233
- while len(temp) != 0:
234
- output.append(temp[:string_split_size])
235
- temp = temp[string_split_size:]
236
- return output
237
-
238
- def divide_with_variable(
239
- self,
240
- split_size: int = 60,
241
- split_var_len: int = 12,
242
- custom_var_name: str | None = None,
243
- ) -> list[str]:
244
- """
245
- Divide long string into smaller size,
246
- then assign a random variable to splited
247
- string for later use
248
-
249
- Parameters
250
- ----------
251
- split_size : int
252
- Divide string every ``x`` character
253
- (Default: ``60``)
254
-
255
- split_var_len : int
256
- Length of variable name assigned to each item
257
- (Default: ``12``)
258
-
259
- custom_var_name : str
260
- Custom variable name when join string
261
-
262
- Returns
263
- -------
264
- list[str]
265
- A list in which each item is a smaller
266
- string with the size of ``split_size``
267
- and a way to concaternate them (when using ``print()``)
268
-
269
-
270
- Example:
271
- --------
272
- >>> test = Text("This is an extremely long line of text!")
273
- >>> test.divide_with_variable(split_size=20)
274
- [
275
- "qNTCnmkFPTJg='This is an extremely'",
276
- "vkmLBUykYYDG=' long line of text!'",
277
- 'sBoSwEfoxBIH=qNTCnmkFPTJg+vkmLBUykYYDG',
278
- 'sBoSwEfoxBIH'
279
- ]
280
-
281
- >>> test = Text("This is an extremely long line of text!")
282
- >>> test.divide_with_variable(split_size=20, custom_var_name="test")
283
- [
284
- "test1='This is an extremely'",
285
- "test2=' long line of text!'",
286
- 'test=test1+test2',
287
- 'test'
288
- ]
289
- """
290
-
291
- temp = self.divide(split_size)
292
- output = []
293
-
294
- # split variable
295
- splt_len = len(temp)
296
-
297
- if custom_var_name is None:
298
- splt_name = Generator.generate_string(
299
- charset=Charset.ALPHABET, size=split_var_len, times=splt_len + 1
300
- )
301
- for i in range(splt_len):
302
- output.append(f"{splt_name[i]}='{temp[i]}'")
303
- else:
304
- for i in range(splt_len):
305
- output.append(f"{custom_var_name}{i + 1}='{temp[i]}'")
306
-
307
- # joined variable
308
- temp = []
309
- if custom_var_name is None:
310
- for i in range(splt_len):
311
- if i == 0:
312
- temp.append(f"{splt_name[-1]}=")
313
- if i == splt_len - 1:
314
- temp.append(f"{splt_name[i]}")
315
- else:
316
- temp.append(f"{splt_name[i]}+")
317
- else:
318
- for i in range(splt_len):
319
- if i == 0:
320
- temp.append(f"{custom_var_name}=")
321
- if i == splt_len - 1:
322
- temp.append(f"{custom_var_name}{i + 1}")
323
- else:
324
- temp.append(f"{custom_var_name}{i + 1}+")
325
-
326
- output.append("".join(temp))
327
- if custom_var_name is None:
328
- output.append(splt_name[-1])
329
- else:
330
- output.append(custom_var_name)
331
-
332
- return output
333
-
334
- @versionchanged(version="3.3.0", reason="Update functionality")
335
- def analyze(self, full: bool = False) -> TextAnalyzeDictFormat:
336
- """
337
- String analyze (count number of type of character)
338
-
339
- Parameters
340
- ----------
341
- full : bool
342
- Full analyze when ``True``
343
- (Default: ``False``)
344
-
345
- Returns
346
- -------
347
- dict | TextAnalyzeDictFormat
348
- A dictionary contains number of digit character,
349
- uppercase character, lowercase character, and
350
- special character
351
-
352
-
353
- Example:
354
- --------
355
- >>> test = Text("Random T3xt!")
356
- >>> test.analyze()
357
- {'digit': 1, 'uppercase': 2, 'lowercase': 7, 'other': 2}
358
- """
359
-
360
- temp = self
361
-
362
- detail: TextAnalyzeDictFormat = {
363
- "digit": 0,
364
- "uppercase": 0,
365
- "lowercase": 0,
366
- "other": 0,
367
- }
368
-
369
- for x in temp:
370
- if ord(x) in range(48, 58): # num
371
- detail["digit"] += 1
372
- elif ord(x) in range(65, 91): # cap
373
- detail["uppercase"] += 1
374
- elif ord(x) in range(97, 123): # low
375
- detail["lowercase"] += 1
376
- else:
377
- detail["other"] += 1
378
-
379
- if full:
380
- detail["is_palindrome"] = self.is_palindrome()
381
- detail["is_pangram"] = self.is_pangram()
382
-
383
- return detail
384
-
385
- def reverse(self) -> Self:
386
- """
387
- Reverse the string
388
-
389
- Returns
390
- -------
391
- Text
392
- Reversed string
393
-
394
-
395
- Example:
396
- --------
397
- >>> test = Text("Hello, World!")
398
- >>> test.reverse()
399
- '!dlroW ,olleH'
400
- """
401
- return self.__class__(self[::-1])
402
-
403
- def is_pangram(self) -> bool:
404
- """
405
- Check if string is a pangram
406
-
407
- A pangram is a unique sentence in which
408
- every letter of the alphabet is used at least once
409
-
410
- Returns
411
- -------
412
- bool
413
- | ``True`` if string is a pangram
414
- | ``False`` if string is not a pangram
415
- """
416
- text = self
417
- alphabet = set("abcdefghijklmnopqrstuvwxyz")
418
- return not set(alphabet) - set(text.lower())
419
-
420
- def is_palindrome(self) -> bool:
421
- """
422
- Check if string is a palindrome
423
-
424
- A palindrome is a word, verse, or sentence
425
- or a number that reads the same backward or forward
426
-
427
- Returns
428
- -------
429
- bool
430
- | ``True`` if string is a palindrome
431
- | ``False`` if string is not a palindrome
432
- """
433
- text = self
434
- # Use string slicing [start:end:step]
435
- return text == text[::-1]
436
-
437
- def to_hex(self, raw: bool = False) -> str:
438
- r"""
439
- Convert string to hex form
440
-
441
- Parameters
442
- ----------
443
- raw : bool
444
- | ``False``: hex string in the form of ``\x`` (default)
445
- | ``True``: normal hex string
446
-
447
- Returns
448
- -------
449
- str
450
- Hexed string
451
-
452
-
453
- Example:
454
- --------
455
- >>> test = Text("Hello, World!")
456
- >>> test.to_hex()
457
- '\\x48\\x65\\x6c\\x6c\\x6f\\x2c\\x20\\x57\\x6f\\x72\\x6c\\x64\\x21'
458
- """
459
- text = self
460
-
461
- byte_str = text.encode("utf-8")
462
- hex_str = byte_str.hex()
463
-
464
- if not raw:
465
- temp = []
466
- str_len = len(hex_str)
467
-
468
- for i in range(str_len):
469
- if i % 2 == 0:
470
- temp.append("\\x")
471
- temp.append(hex_str[i])
472
- return "".join(temp)
473
- else:
474
- return hex_str
475
-
476
- def random_capslock(self, probability: int = 50) -> Self:
477
- """
478
- Randomly capslock letter in string
479
-
480
- Parameters
481
- ----------
482
- probability : int
483
- Probability in range [0, 100]
484
- (Default: ``50``)
485
-
486
- Returns
487
- -------
488
- Text
489
- Random capslocked text
490
-
491
-
492
- Example:
493
- --------
494
- >>> test = Text("This is an extremely long line of text!")
495
- >>> test.random_capslock()
496
- 'tHis iS An ExtREmELY loNg liNE oF tExT!'
497
- """
498
- probability = int(set_min_max(probability))
499
- text = self.lower()
500
-
501
- temp = []
502
- for x in text:
503
- if random.randint(1, 100) <= probability:
504
- x = x.upper()
505
- temp.append(x)
506
- logger.debug(temp)
507
- return self.__class__("".join(temp))
508
-
509
- def reverse_capslock(self) -> Self:
510
- """
511
- Reverse capslock in string
512
-
513
- Returns
514
- -------
515
- Text
516
- Reversed capslock ``Text``
517
-
518
-
519
- Example:
520
- --------
521
- >>> test = Text("Foo")
522
- >>> test.reverse_capslock()
523
- 'fOO'
524
- """
525
- temp = list(self)
526
- for i, x in enumerate(temp):
527
- if x.isupper():
528
- temp[i] = x.lower()
529
- else:
530
- temp[i] = x.upper()
531
- return self.__class__("".join(temp))
532
-
533
- def to_list(self) -> list[str]:
534
- """
535
- Convert into list
536
-
537
- Returns
538
- -------
539
- list[str]
540
- List of string
541
-
542
-
543
- Example:
544
- --------
545
- >>> test = Text("test")
546
- >>> test.to_list()
547
- ['t', 'e', 's', 't']
548
- """
549
- return list(self)
550
-
551
- @versionadded(version="3.3.0")
552
- def to_listext(self) -> "ListExt":
553
- """
554
- Convert into ``ListExt``
555
-
556
- Returns
557
- -------
558
- ListExt[str]
559
- List of string
560
-
561
-
562
- Example:
563
- --------
564
- >>> test = Text("test")
565
- >>> test.to_listext()
566
- ['t', 'e', 's', 't']
567
- """
568
- return ListExt(self)
569
-
570
- @versionadded(version="3.3.0")
571
- def count_pattern(self, pattern: str, ignore_capslock: bool = False) -> int:
572
- """
573
- Returns how many times ``pattern`` appears in text
574
- (Inspired by hackerrank exercise)
575
-
576
- Parameters
577
- ----------
578
- pattern : str
579
- Pattern to count
580
-
581
- ignore_capslock : bool
582
- Ignore the pattern uppercase or lowercase
583
- (Default: ``False`` - Exact match)
584
-
585
- Returns
586
- -------
587
- int
588
- How many times pattern appeared
589
-
590
-
591
- Example:
592
- --------
593
- >>> Text("test").count_pattern("t")
594
- 2
595
- """
596
- if len(pattern) > len(self):
597
- raise ValueError(f"len(pattern) must not larger than {len(self)}")
598
-
599
- temp = str(self)
600
- if ignore_capslock:
601
- pattern = pattern.lower()
602
- temp = temp.lower()
603
-
604
- out = [
605
- 1
606
- for i in range(len(temp) - len(pattern) + 1)
607
- if temp[i : i + len(pattern)] == pattern
608
- ]
609
- return sum(out)
610
-
611
- @versionadded(version="3.3.0")
612
- def hapax(self, strict: bool = False) -> list[str]:
613
- """
614
- A hapax legomenon (often abbreviated to hapax)
615
- is a word which occurs only once in either
616
- the written record of a language, the works of
617
- an author, or in a single text.
618
-
619
- This function returns a list of hapaxes (if any)
620
- (Lettercase is ignored)
621
-
622
- Parameters
623
- ----------
624
- strict : bool
625
- Remove all special characters before checking for hapax
626
- (Default: ``False``)
627
-
628
- Returns
629
- -------
630
- list[str]
631
- A list of hapaxes
632
-
633
-
634
- Example:
635
- --------
636
- >>> test = Text("A a. a, b c c= C| d d")
637
- >>> test.hapax()
638
- ['a', 'a.', 'a,', 'b', 'c', 'c=', 'c|']
639
-
640
- >>> test.hapax(strict=True)
641
- ['b']
642
- """
643
- word_list: list[str] = self.lower().split()
644
- if strict:
645
- remove_characters: list[str] = list(r"\"'.,:;|()[]{}\/!@#$%^&*-_=+?<>`~")
646
- temp = str(self)
647
- for x in remove_characters:
648
- temp = temp.replace(x, "")
649
- word_list = temp.lower().split()
650
-
651
- hapaxes = filter(lambda x: word_list.count(x) == 1, word_list)
652
- return list(hapaxes)
653
-
654
-
655
- # MARK: IntNumber
656
- class IntNumber(int):
657
- """
658
- ``int`` extension
659
- """
660
-
661
- # convert stuff
662
- def to_binary(self) -> str:
663
- """
664
- Convert to binary number
665
-
666
- Returns
667
- -------
668
- str
669
- Binary number
670
-
671
-
672
- Example:
673
- --------
674
- >>> test = IntNumber(10)
675
- >>> test.to_binary()
676
- '1010'
677
- """
678
- return format(self, "b")
679
-
680
- def to_celcius_degree(self) -> float:
681
- """
682
- Convert into Celcius degree as if ``self`` is Fahrenheit degree
683
-
684
- Returns
685
- -------
686
- float
687
- Celcius degree
688
-
689
-
690
- Example:
691
- --------
692
- >>> test = IntNumber(10)
693
- >>> test.to_celcius_degree()
694
- -12.222222222222221
695
- """
696
- c_degree = (self - 32) / 1.8
697
- return c_degree
698
-
699
- def to_fahrenheit_degree(self) -> float:
700
- """
701
- Convert into Fahrenheit degree as if ``self`` is Celcius degree
702
-
703
- Returns
704
- -------
705
- float
706
- Fahrenheit degree
707
-
708
-
709
- Example:
710
- --------
711
- >>> test = IntNumber(10)
712
- >>> test.to_fahrenheit_degree()
713
- 50.0
714
- """
715
- f_degree = (self * 1.8) + 32
716
- return f_degree
717
-
718
- # is_stuff
719
- def is_even(self) -> bool:
720
- """
721
- An even number is a number which divisible by 2
722
-
723
- Returns
724
- -------
725
- bool
726
- | ``True`` if an even number
727
- | ``False`` if not an even number
728
- """
729
- return self % 2 == 0
730
-
731
- def is_prime(self) -> bool:
732
- """
733
- Check if the integer is a prime number or not
734
-
735
- A prime number is a natural number greater than ``1``
736
- that is not a product of two smaller natural numbers.
737
- A natural number greater than ``1`` that is not prime
738
- is called a composite number.
739
-
740
- Returns
741
- -------
742
- bool
743
- | ``True`` if a prime number
744
- | ``False`` if not a prime number
745
- """
746
- number = self
747
-
748
- if number <= 1:
749
- return False
750
- for i in range(2, int(math.sqrt(number)) + 1): # divisor range
751
- if number % i == 0:
752
- return False
753
- return True
754
-
755
- def is_twisted_prime(self) -> bool:
756
- """
757
- A number is said to be twisted prime if
758
- it is a prime number and
759
- reverse of the number is also a prime number
760
-
761
- Returns
762
- -------
763
- bool
764
- | ``True`` if a twisted prime number
765
- | ``False`` if not a twisted prime number
766
- """
767
- prime = self.is_prime()
768
- logger.debug(f"is prime: {prime}")
769
- rev = self.reverse().is_prime()
770
- logger.debug(f"is prime when reversed: {rev}")
771
- return prime and rev
772
-
773
- def is_perfect(self) -> bool:
774
- """
775
- Check if integer is perfect number
776
-
777
- Perfect number: a positive integer that is
778
- equal to the sum of its proper divisors.
779
- The smallest perfect number is ``6``, which is
780
- the sum of ``1``, ``2``, and ``3``.
781
- Other perfect numbers are ``28``, ``496``, and ``8,128``.
782
-
783
- Returns
784
- -------
785
- bool
786
- | ``True`` if a perfect number
787
- | ``False`` if not a perfect number
788
- """
789
- # ---
790
- """
791
- # List of known perfect number
792
- # Source: https://en.wikipedia.org/wiki/List_of_Mersenne_primes_and_perfect_numbers
793
- perfect_number_index = [
794
- 2, 3, 5, 7,
795
- 13, 17, 19, 31, 61, 89,
796
- 107, 127, 521, 607,
797
- 1279, 2203, 2281, 3217, 4253, 4423, 9689, 9941,
798
- 11_213, 19_937, 21_701, 23_209, 44_497, 86_243,
799
- 110_503, 132_049, 216_091, 756_839, 859_433,
800
- # 1_257_787, 1_398_269, 2_976_221, 3_021_377, 6_972_593,
801
- # 13_466_917, 20_996_011, 24_036_583, 25_964_951,
802
- # 30_402_457, 32_582_657, 37_156_667, 42_643_801,
803
- # 43_112_609, 57_885_161,
804
- ## 74_207_281, 77_232_917, 82_589_933
805
- ]
806
- perfect_number = []
807
- for x in perfect_number_index:
808
- # a perfect number have a form of (2**(n-1))*((2**n)-1)
809
- perfect_number.append((2**(x-1))*((2**x)-1))
810
- """
811
- number = int(self)
812
-
813
- perfect_number = [
814
- 6,
815
- 28,
816
- 496,
817
- 8128,
818
- 33_550_336,
819
- 8_589_869_056,
820
- 137_438_691_328,
821
- 2_305_843_008_139_952_128,
822
- ]
823
-
824
- if number in perfect_number:
825
- return True
826
-
827
- elif number < perfect_number[-1]:
828
- return False
829
-
830
- else:
831
- # Faster way to check
832
- perfect_number_index: list[int] = [
833
- 61,
834
- 89,
835
- 107,
836
- 127,
837
- 521,
838
- 607,
839
- 1279,
840
- 2203,
841
- 2281,
842
- 3217,
843
- 4253,
844
- 4423,
845
- 9689,
846
- 9941,
847
- 11_213,
848
- 19_937,
849
- 21_701,
850
- 23_209,
851
- 44_497,
852
- 86_243,
853
- 110_503,
854
- 132_049,
855
- 216_091,
856
- 756_839,
857
- 859_433,
858
- 1_257_787,
859
- # 1_398_269,
860
- # 2_976_221,
861
- # 3_021_377,
862
- # 6_972_593,
863
- # 13_466_917,
864
- # 20_996_011,
865
- # 24_036_583,
866
- # 25_964_951,
867
- # 30_402_457,
868
- # 32_582_657,
869
- # 37_156_667,
870
- # 42_643_801,
871
- # 43_112_609,
872
- # 57_885_161,
873
- ## 74_207_281,
874
- ## 77_232_917,
875
- ## 82_589_933
876
- ]
877
- for x in perfect_number_index:
878
- # a perfect number have a form of (2**(n-1))*((2**n)-1)
879
- perfect_number = (2 ** (x - 1)) * ((2**x) - 1)
880
- if number < perfect_number: # type: ignore
881
- return False
882
- elif number == perfect_number: # type: ignore
883
- return True
884
-
885
- # Manual way when above method not working
886
- # sum
887
- s = 1
888
- # add all divisors
889
- i = 2
890
- while i * i <= number:
891
- if number % i == 0:
892
- s += +i + number / i # type: ignore
893
- i += 1
894
- # s == number -> perfect
895
- return True if s == number and number != 1 else False
896
-
897
- def is_narcissistic(self) -> bool:
898
- """
899
- Check if a narcissistic number
900
-
901
- In number theory, a narcissistic number
902
- (also known as a pluperfect digital invariant (PPDI),
903
- an Armstrong number (after Michael F. Armstrong)
904
- or a plus perfect number) in a given number base ``b``
905
- is a number that is the sum of its own digits
906
- each raised to the power of the number of digits.
907
-
908
- Returns
909
- -------
910
- bool
911
- | ``True`` if a narcissistic number
912
- | ``False`` if not a narcissistic number
913
- """
914
- try:
915
- check = sum([int(x) ** len(str(self)) for x in str(self)])
916
- res = int(self) == check
917
- return res # type: ignore
918
- except Exception:
919
- return False
920
-
921
- def reverse(self) -> Self:
922
- """
923
- Reverse a number. Reverse ``abs(number)`` if ``number < 0``
924
-
925
- Returns
926
- -------
927
- IntNumber
928
- Reversed number
929
-
930
-
931
- Example:
932
- --------
933
- >>> test = IntNumber(102)
934
- >>> test.reverse()
935
- 201
936
- """
937
- number = int(self)
938
- if number <= 1:
939
- number *= -1
940
- return self.__class__(str(number)[::-1])
941
-
942
- def is_palindromic(self) -> bool:
943
- """
944
- A palindromic number (also known as a numeral palindrome
945
- or a numeric palindrome) is a number (such as ``16461``)
946
- that remains the same when its digits are reversed.
947
-
948
- Returns
949
- -------
950
- bool
951
- | ``True`` if a palindromic number
952
- | ``False`` if not a palindromic number
953
- """
954
- return self == self.reverse()
955
-
956
- def is_palindromic_prime(self) -> bool:
957
- """
958
- A palindormic prime is a number which is both palindromic and prime
959
-
960
- Returns
961
- -------
962
- bool
963
- | ``True`` if a palindormic prime number
964
- | ``False`` if not a palindormic prime number
965
- """
966
- return self.is_palindromic() and self.is_prime()
967
-
968
- # calculation stuff
969
- @versionchanged(version="4.0.0", reason="Update")
970
- def lcm(self, with_number: int) -> Self:
971
- """
972
- Least common multiple of ``self`` and ``with_number``
973
-
974
- Parameters
975
- ----------
976
- with_number : int
977
- The number that want to find LCM with
978
-
979
- Returns
980
- -------
981
- IntNumber
982
- Least common multiple
983
-
984
-
985
- Example:
986
- --------
987
- >>> test = IntNumber(102)
988
- >>> test.lcm(5)
989
- 510
990
- """
991
- return self.__class__(math.lcm(self, with_number))
992
-
993
- @versionchanged(version="3.3.0", reason="Fix bug")
994
- def gcd(self, with_number: int) -> Self:
995
- """
996
- Greatest common divisor of ``self`` and ``with_number``
997
-
998
- Parameters
999
- ----------
1000
- with_number : int
1001
- The number that want to find GCD with
1002
-
1003
- Returns
1004
- -------
1005
- IntNumber
1006
- Greatest common divisor
1007
-
1008
-
1009
- Example:
1010
- --------
1011
- >>> test = IntNumber(1024)
1012
- >>> test.gcd(8)
1013
- 8
1014
- """
1015
- return self.__class__(math.gcd(self, with_number))
1016
-
1017
- def add_to_one_digit(self, master_number: bool = False) -> Self:
1018
- """
1019
- Convert ``self`` into 1-digit number
1020
- by adding all of the digits together
1021
-
1022
- Parameters
1023
- ----------
1024
- master_number : bool
1025
- | Break when sum = ``22`` or ``11`` (numerology)
1026
- | (Default: ``False``)
1027
-
1028
- Returns
1029
- -------
1030
- IntNumber
1031
- IntNumber
1032
-
1033
-
1034
- Example:
1035
- --------
1036
- >>> test = IntNumber(119)
1037
- >>> test.add_to_one_digit()
1038
- 2
1039
-
1040
- >>> test = IntNumber(119)
1041
- >>> test.add_to_one_digit(master_number=True)
1042
- 11
1043
- """
1044
- number = int(self)
1045
- if number < 0:
1046
- number *= -1
1047
- logger.debug(f"Current number: {number}")
1048
- while len(str(number)) != 1:
1049
- number = sum(map(int, str(number)))
1050
- if master_number:
1051
- if number == 22 or number == 11:
1052
- break # Master number
1053
- logger.debug(f"Sum after loop: {number}")
1054
- return self.__class__(number)
1055
-
1056
- def divisible_list(self, short_form: bool = True) -> list[int]:
1057
- """
1058
- A list of divisible number
1059
-
1060
- Parameters
1061
- ----------
1062
- short_form : bool
1063
- | Show divisible list in short form
1064
- | Normal example: ``[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]``
1065
- | Short form example: ``[1, 2, 4, 8, ...,128, 256, 512, 1024] [Len: 11]``
1066
- | (Default: ``True``)
1067
-
1068
- Returns
1069
- -------
1070
- list[int]
1071
- A list of divisible number
1072
-
1073
-
1074
- Example:
1075
- --------
1076
- >>> test = IntNumber(1024)
1077
- >>> test.divisible_list(short_form=False)
1078
- [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
1079
- """
1080
-
1081
- if self <= 1:
1082
- return [1]
1083
- divi_list = [x for x in range(1, int(self / 2) + 1) if self % x == 0] + [self]
1084
-
1085
- if short_form:
1086
- return divi_list
1087
- # return ListREPR(divi_list) ## FIX LATER
1088
- return divi_list
1089
-
1090
- def prime_factor(self, short_form: bool = True) -> Union[list[int], list[Pow]]:
1091
- """
1092
- Prime factor
1093
-
1094
- Parameters
1095
- ----------
1096
- short_form : bool
1097
- | Show prime list in short form
1098
- | Normal example: ``[2, 2, 2, 3, 3]``
1099
- | Short form example: ``[2^3, 3^2]``
1100
- | (Default: ``True``)
1101
-
1102
- Returns
1103
- -------
1104
- list[int] | list[Pow]
1105
- | List of prime number that when multiplied together == ``self``
1106
- | list[int]: Long form
1107
- | list[Pow]: Short form
1108
-
1109
-
1110
- Example:
1111
- --------
1112
- >>> test = IntNumber(1024)
1113
- >>> test.prime_factor()
1114
- [2^10]
1115
-
1116
- >>> test = IntNumber(1024)
1117
- >>> test.prime_factor(short_form=False)
1118
- [2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
1119
- """
1120
- # Generate list
1121
- factors = []
1122
- divisor = 2
1123
- number = int(self)
1124
- if number <= 1:
1125
- return [number]
1126
- while divisor <= number:
1127
- if number % divisor == 0:
1128
- factors.append(divisor)
1129
- number //= divisor # number = number // divisor
1130
- else:
1131
- divisor += 1
1132
-
1133
- # Output
1134
- if short_form:
1135
- temp = dict(Counter(factors))
1136
- return [Pow(k, v) for k, v in temp.items()]
1137
- return factors
1138
-
1139
- # analyze
1140
- def analyze(self, short_form: bool = True) -> dict[str, dict[str, Any]]:
1141
- """
1142
- Analyze the number with almost all ``IntNumber`` method
1143
-
1144
- Parameters
1145
- ----------
1146
- short_form : bool
1147
- | Enable short form for some items
1148
- | (Default: ``True``)
1149
-
1150
- Returns
1151
- -------
1152
- dict[str, dict[str, Any]]
1153
- Detailed analysis
1154
-
1155
-
1156
- Example:
1157
- --------
1158
- >>> test = IntNumber(1024)
1159
- >>> test.analyze()
1160
- {
1161
- 'summary': {'number': 1024, 'length': 4, 'even': True, 'prime factor': [2^10], 'divisible': [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]},
1162
- 'convert': {'binary': '10000000000', 'octa': '2000', 'hex': '400', 'reverse': 4201, 'add to one': 7},
1163
- 'characteristic': {'prime': False, 'twisted prime': False, 'perfect': False, 'narcissistic': False, 'palindromic': False, 'palindromic prime': False}
1164
- }
1165
- """
1166
- output = {
1167
- "summary": {
1168
- "number": self,
1169
- "length": len(str(self)),
1170
- "even": self.is_even(),
1171
- "prime factor": self.prime_factor(short_form=short_form),
1172
- "divisible": self.divisible_list(short_form=short_form),
1173
- },
1174
- "convert": {
1175
- "binary": bin(self)[2:],
1176
- "octa": oct(self)[2:],
1177
- "hex": hex(self)[2:],
1178
- # "hash": hash(self),
1179
- "reverse": self.reverse(),
1180
- "add to one": self.add_to_one_digit(),
1181
- },
1182
- }
1183
- characteristic = {
1184
- "prime": self.is_prime(),
1185
- "twisted prime": self.is_twisted_prime(),
1186
- "perfect": self.is_perfect(),
1187
- "narcissistic": self.is_narcissistic(),
1188
- "palindromic": self.is_palindromic(),
1189
- "palindromic prime": self.is_palindromic_prime(),
1190
- }
1191
- if short_form:
1192
- characteristic = DictBoolTrue(characteristic)
1193
-
1194
- output["characteristic"] = characteristic
1195
- return output # type: ignore
1196
-
1197
-
1198
- # MARK: ListExt
1199
- class ListExt(list):
1200
- """
1201
- ``list`` extension
1202
- """
1203
-
1204
- def stringify(self) -> Self:
1205
- """
1206
- Convert all item in ``list`` into string
1207
-
1208
- Returns
1209
- -------
1210
- ListExt
1211
- A list with all items with type <str`>
1212
-
1213
-
1214
- Example:
1215
- --------
1216
- >>> test = ListExt([1, 1, 1, 2, 2, 3])
1217
- >>> test.stringify()
1218
- ['1', '1', '1', '2', '2', '3']
1219
- """
1220
- return self.__class__(map(str, self))
1221
-
1222
- def head(self, number_of_items: int = 5) -> list:
1223
- """
1224
- Show first ``number_of_items`` items in ``ListExt``
1225
-
1226
- Parameters
1227
- ----------
1228
- number_of_items : int
1229
- | Number of items to shows at once
1230
- | (Default: ``5``)
1231
-
1232
- Returns
1233
- -------
1234
- list
1235
- Filtered list
1236
- """
1237
- number_of_items = int(
1238
- set_min_max(number_of_items, min_value=0, max_value=len(self))
1239
- )
1240
- return self[:number_of_items]
1241
-
1242
- def tail(self, number_of_items: int = 5) -> list:
1243
- """
1244
- Show last ``number_of_items`` items in ``ListExt``
1245
-
1246
- Parameters
1247
- ----------
1248
- number_of_items : int
1249
- | Number of items to shows at once
1250
- | (Default: ``5``)
1251
-
1252
- Returns
1253
- -------
1254
- list
1255
- Filtered list
1256
- """
1257
- number_of_items = int(
1258
- set_min_max(number_of_items, min_value=0, max_value=len(self))
1259
- )
1260
- return self[::-1][:number_of_items][::-1]
1261
-
1262
- def sorts(self, reverse: bool = False) -> Self:
1263
- """
1264
- Sort all items (with different type) in ``list``
1265
-
1266
- Parameters
1267
- ----------
1268
- reverse : bool
1269
- | if ``True`` then sort in descending order
1270
- | if ``False`` then sort in ascending order
1271
- | (Default: ``False``)
1272
-
1273
- Returns
1274
- -------
1275
- ListExt
1276
- A sorted list
1277
-
1278
-
1279
- Example:
1280
- --------
1281
- >>> test = ListExt([9, "abc", 3.5, "aaa", 1, 1.4])
1282
- >>> test.sorts()
1283
- [1, 9, 'aaa', 'abc', 1.4, 3.5]
1284
- """
1285
- lst = self.copy()
1286
- type_weights: dict = {}
1287
- for x in lst:
1288
- if type(x) not in type_weights:
1289
- type_weights[type(x)] = len(type_weights)
1290
- logger.debug(f"Type weight: {type_weights}")
1291
-
1292
- output = sorted(
1293
- lst, key=lambda x: (type_weights[type(x)], str(x)), reverse=reverse
1294
- )
1295
-
1296
- logger.debug(output)
1297
- return self.__class__(output)
1298
-
1299
- def freq(
1300
- self,
1301
- sort: bool = False,
1302
- num_of_first_char: int | None = None,
1303
- appear_increment: bool = False,
1304
- ) -> Union[dict, list[int]]:
1305
- """
1306
- Find frequency of each item in list
1307
-
1308
- Parameters
1309
- ----------
1310
- sort : bool
1311
- | if ``True``: Sorts the output in ascending order
1312
- | if ``False``: No sort
1313
-
1314
- num_of_first_char : int | None
1315
- | Number of first character taken into account to sort
1316
- | (Default: ``None``)
1317
- | (num_of_first_char = ``1``: first character in each item)
1318
-
1319
- appear_increment : bool
1320
- | return incremental index list of each item when sort
1321
- | (Default: ``False``)
1322
-
1323
- Returns
1324
- -------
1325
- dict
1326
- A dict that show frequency
1327
-
1328
- list[int]
1329
- Incremental index list
1330
-
1331
-
1332
- Example:
1333
- --------
1334
- >>> test = ListExt([1, 1, 2, 3, 5, 5])
1335
- >>> test.freq()
1336
- {1: 2, 2: 1, 3: 1, 5: 2}
1337
-
1338
- >>> test = ListExt([1, 1, 2, 3, 3, 4, 5, 6])
1339
- >>> test.freq(appear_increment=True)
1340
- [2, 3, 5, 6, 7, 8]
1341
- """
1342
-
1343
- if sort:
1344
- data = self.sorts().copy()
1345
- else:
1346
- data = self.copy()
1347
-
1348
- if num_of_first_char is None:
1349
- temp = Counter(data)
1350
- else:
1351
- max_char: int = min([len(str(x)) for x in data])
1352
- logger.debug(f"Max character: {max_char}")
1353
- if num_of_first_char not in range(1, max_char):
1354
- logger.debug(f"Not in {range(1, max_char)}. Using default value...")
1355
- temp = Counter(data)
1356
- else:
1357
- logger.debug(f"Freq of first {num_of_first_char} char")
1358
- temp = Counter([str(x)[:num_of_first_char] for x in data])
1359
-
1360
- try:
1361
- times_appear = dict(sorted(temp.items()))
1362
- except Exception:
1363
- times_appear = dict(self.__class__(temp.items()).sorts())
1364
- logger.debug(times_appear)
1365
-
1366
- if appear_increment:
1367
- times_appear_increment: list[int] = list(
1368
- accumulate(times_appear.values(), operator.add)
1369
- )
1370
- logger.debug(times_appear_increment)
1371
- return times_appear_increment
1372
- else:
1373
- return times_appear
1374
-
1375
- def slice_points(self, points: list) -> list[list]:
1376
- """
1377
- Slices a list at specific indices into constituent lists.
1378
-
1379
- Parameters
1380
- ----------
1381
- points : list
1382
- List of indices to slice
1383
-
1384
- Returns
1385
- -------
1386
- list[list]
1387
- Sliced list
1388
-
1389
-
1390
- Example:
1391
- --------
1392
- >>> test = ListExt([1, 1, 2, 3, 3, 4, 5, 6])
1393
- >>> test.slice_points([2, 5])
1394
- [[1, 1], [2, 3, 3], [4, 5, 6]]
1395
- """
1396
- points.append(len(self))
1397
- data = self.copy()
1398
- # return [data[points[i]:points[i+1]] for i in range(len(points)-1)]
1399
- return [data[i1:i2] for i1, i2 in zip([0] + points[:-1], points)]
1400
-
1401
- def pick_one(self) -> Any:
1402
- """
1403
- Pick one random items from ``list``
1404
-
1405
- Returns
1406
- -------
1407
- Any
1408
- Random value
1409
-
1410
-
1411
- Example:
1412
- --------
1413
- >>> test = ListExt(["foo", "bar"])
1414
- >>> test.pick_one()
1415
- 'bar'
1416
- """
1417
- if len(self) != 0:
1418
- out = random.choice(self)
1419
- logger.debug(out)
1420
- return out
1421
- else:
1422
- logger.debug("List empty!")
1423
- raise IndexError("List empty!")
1424
-
1425
- def get_random(self, number_of_items: int = 5) -> list:
1426
- """
1427
- Get ``number_of_items`` random items in ``ListExt``
1428
-
1429
- Parameters
1430
- ----------
1431
- number_of_items : int
1432
- | Number random of items
1433
- | (Default: ``5``)
1434
-
1435
- Returns
1436
- -------
1437
- list
1438
- Filtered list
1439
- """
1440
- return [self.pick_one() for _ in range(number_of_items)]
1441
-
1442
- def len_items(self) -> Self:
1443
- """
1444
- ``len()`` for every item in ``list[str]``
1445
-
1446
- Returns
1447
- -------
1448
- ListExt
1449
- List of ``len()``'ed value
1450
-
1451
-
1452
- Example:
1453
- --------
1454
- >>> test = ListExt(["foo", "bar", "pizza"])
1455
- >>> test.len_items()
1456
- [3, 3, 5]
1457
- """
1458
- out = self.__class__([len(str(x)) for x in self])
1459
- # out = ListExt(map(lambda x: len(str(x)), self))
1460
- logger.debug(out)
1461
- return out
1462
-
1463
- def mean_len(self) -> float:
1464
- """
1465
- Average length of every item
1466
-
1467
- Returns
1468
- -------
1469
- float
1470
- Average length
1471
-
1472
-
1473
- Example:
1474
- --------
1475
- >>> test = ListExt(["foo", "bar", "pizza"])
1476
- >>> test.mean_len()
1477
- 3.6666666666666665
1478
- """
1479
- out = sum(self.len_items()) / len(self)
1480
- logger.debug(out)
1481
- return out
1482
-
1483
- def apply(self, func: Callable) -> Self:
1484
- """
1485
- Apply function to each entry
1486
-
1487
- Parameters
1488
- ----------
1489
- func : Callable
1490
- Callable function
1491
-
1492
- Returns
1493
- -------
1494
- ListExt
1495
- ListExt
1496
-
1497
-
1498
- Example:
1499
- --------
1500
- >>> test = ListExt([1, 2, 3])
1501
- >>> test.apply(str)
1502
- ['1', '2', '3']
1503
- """
1504
- # return __class__(func(x) for x in self)
1505
- return self.__class__(map(func, self))
1506
-
1507
- def unique(self) -> Self:
1508
- """
1509
- Remove duplicates
1510
-
1511
- Returns
1512
- -------
1513
- ListExt
1514
- Duplicates removed list
1515
-
1516
-
1517
- Example:
1518
- --------
1519
- >>> test = ListExt([1, 1, 1, 2, 2, 3])
1520
- >>> test.unique()
1521
- [1, 2, 3]
1522
- """
1523
- return self.__class__(set(self))
1524
-
1525
- def group_by_unique(self) -> Self:
1526
- """
1527
- Group duplicated elements into list
1528
-
1529
- Returns
1530
- -------
1531
- ListExt
1532
- Grouped value
1533
-
1534
-
1535
- Example:
1536
- --------
1537
- >>> test = ListExt([1, 2, 3, 1, 3, 3, 2])
1538
- >>> test.group_by_unique()
1539
- [[1, 1], [2, 2], [3, 3, 3]]
1540
- """
1541
- # Old
1542
- # out = self.sorts().slice_points(self.freq(appear_increment=True))
1543
- # return __class__(out[:-1])
1544
-
1545
- # New
1546
- temp = groupby(self.sorts())
1547
- return self.__class__([list(g) for _, g in temp])
1548
-
1549
- @staticmethod
1550
- def _group_by_unique(iterable: list) -> list[list]:
1551
- """
1552
- Static method for ``group_by_unique``
1553
- """
1554
- return list([list(g) for _, g in groupby(iterable)])
1555
-
1556
- def group_by_pair_value(self, max_loop: int = 3) -> list[list]:
1557
- """
1558
- Assume each ``list`` in ``list`` is a pair value,
1559
- returns a ``list`` contain all paired value
1560
-
1561
- Parameters
1562
- ----------
1563
- max_loop : int
1564
- Times to run functions (minimum: ``3``)
1565
-
1566
- Returns
1567
- -------
1568
- list[list]
1569
- Grouped value
1570
-
1571
-
1572
- Example:
1573
- --------
1574
- >>> test = ListExt([[1, 2], [2, 3], [4, 3], [5, 6]])
1575
- >>> test.group_by_pair_value()
1576
- [[1, 2, 3, 4], [5, 6]]
1577
-
1578
- >>> test = ListExt([[8, 3], [4, 6], [6, 3], [5, 2], [7, 2]])
1579
- >>> test.group_by_pair_value()
1580
- [[8, 3, 4, 6], [2, 5, 7]]
1581
-
1582
- >>> test = ListExt([["a", 4], ["b", 4], [5, "c"]])
1583
- >>> test.group_by_pair_value()
1584
- [['a', 4, 'b'], ['c', 5]]
1585
- """
1586
-
1587
- iter = self.copy()
1588
-
1589
- # Init loop
1590
- for _ in range(int(set_min(max_loop, min_value=3))):
1591
- temp: dict[Any, list] = {}
1592
- # Make dict{key: all `item` that contains `key`}
1593
- for item in iter:
1594
- for x in item:
1595
- if temp.get(x, None) is None:
1596
- temp[x] = [item]
1597
- else:
1598
- temp[x].append(item)
1599
-
1600
- # Flatten dict.values
1601
- for k, v in temp.items():
1602
- temp[k] = list(set(chain(*v)))
1603
-
1604
- iter = list(temp.values())
1605
-
1606
- return list(x for x, _ in groupby(iter))
1607
-
1608
- def flatten(self) -> Self:
1609
- """
1610
- Flatten the list
1611
-
1612
- Returns
1613
- -------
1614
- ListExt
1615
- Flattened list
1616
-
1617
-
1618
- Example:
1619
- --------
1620
- >>> test = ListExt([["test"], ["test", "test"], ["test"]])
1621
- >>> test.flatten()
1622
- ['test', 'test', 'test', 'test']
1623
- """
1624
- try:
1625
- return self.__class__(chain(*self))
1626
- except Exception:
1627
- temp = list(map(lambda x: x if isinstance(x, list) else [x], self))
1628
- return self.__class__(chain(*temp))
1629
-
1630
- def numbering(self, start: int = 0) -> Self:
1631
- """
1632
- Number the item in list
1633
- (``enumerate`` wrapper)
1634
-
1635
- Parameters
1636
- ----------
1637
- start : int
1638
- Start from which number
1639
- (Default: ``0``)
1640
-
1641
- Returns
1642
- -------
1643
- ListExt
1644
- Counted list
1645
-
1646
-
1647
- Example:
1648
- --------
1649
- >>> test = ListExt([9, 9, 9])
1650
- >>> test.numbering()
1651
- [(0, 9), (1, 9), (2, 9)]
1652
- """
1653
- start = int(set_min(start, min_value=0))
1654
- return self.__class__(enumerate(self, start=start))
1655
-
1656
- @staticmethod
1657
- def _numbering(iterable: list, start: int = 0) -> list[tuple[int, Any]]:
1658
- """
1659
- Static method for ``numbering``
1660
- """
1661
- start = int(set_min(start, min_value=0))
1662
- return list(enumerate(iterable, start=start))
1663
-
1664
-
1665
- # MARK: DictExt
1666
- class DictExt(dict):
1667
- """
1668
- ``dict`` extension
1669
- """
1670
-
1671
- @versionchanged(
1672
- version="3.3.0",
1673
- reason="Change function output; Before: <dict>, Now: DictAnalyzeResult",
1674
- )
1675
- def analyze(self) -> DictAnalyzeResult:
1676
- """
1677
- Analyze all the key values (``int``, ``float``)
1678
- in ``dict`` then return highest/lowest index
1679
-
1680
- Returns
1681
- -------
1682
- dict
1683
- Analyzed data
1684
-
1685
-
1686
- Example:
1687
- --------
1688
- >>> test = DictExt({
1689
- >>> "abc": 9,
1690
- >>> "def": 9,
1691
- >>> "ghi": 8,
1692
- >>> "jkl": 1,
1693
- >>> "mno": 1
1694
- >>> })
1695
- >>> test.analyze()
1696
- DictAnalyzeResult(max_value=9, min_value=1, max_list=[('abc', 9), ('def', 9)], min_list=[('jkl', 1), ('mno', 1)])
1697
- """
1698
- try:
1699
- dct: dict = self.copy()
1700
-
1701
- max_val: int | float = max(list(dct.values()))
1702
- min_val: int | float = min(list(dct.values()))
1703
- max_list = []
1704
- min_list = []
1705
-
1706
- for k, v in dct.items():
1707
- if v == max_val:
1708
- max_list.append((k, v))
1709
- if v == min_val:
1710
- min_list.append((k, v))
1711
-
1712
- # output = {
1713
- # "max_value": max_val,
1714
- # "min_value": min_val,
1715
- # "max": max_list,
1716
- # "min": min_list,
1717
- # }
1718
-
1719
- # logger.debug(output)
1720
- # return output
1721
- return DictAnalyzeResult(max_val, min_val, max_list, min_list)
1722
-
1723
- except Exception:
1724
- err_msg = "Value must be int or float"
1725
- logger.error(err_msg)
1726
- raise ValueError(err_msg) # noqa: B904
1727
-
1728
- def swap_items(self) -> Self:
1729
- """
1730
- Swap ``dict.keys()`` with ``dict.values()``
1731
-
1732
- Returns
1733
- -------
1734
- DictExt
1735
- Swapped dict
1736
-
1737
-
1738
- Example:
1739
- --------
1740
- >>> test = DictExt({"abc": 9})
1741
- >>> test.swap_items()
1742
- {9: 'abc'}
1743
- """
1744
- return self.__class__(zip(self.values(), self.keys()))
1745
-
1746
- def apply(self, func: Callable, apply_to_value: bool = True) -> Self:
1747
- """
1748
- Apply function to ``DictExt.keys()`` or ``DictExt.values()``
1749
-
1750
- Parameters
1751
- ----------
1752
- func : Callable
1753
- Callable function
1754
-
1755
- apply_to_value : bool
1756
- | ``True``: Apply ``func`` to ``DictExt.values()``
1757
- | ``False``: Apply ``func`` to ``DictExt.keys()``
1758
-
1759
- Returns
1760
- -------
1761
- DictExt
1762
- DictExt
1763
-
1764
-
1765
- Example:
1766
- --------
1767
- >>> test = DictExt({"abc": 9})
1768
- >>> test.apply(str)
1769
- {'abc': '9'}
1770
- """
1771
- if apply_to_value:
1772
- k = self.keys()
1773
- v = map(func, self.values())
1774
- else:
1775
- k = map(func, self.keys()) # type: ignore
1776
- v = self.values() # type: ignore
1777
- return self.__class__(zip(k, v))
1778
-
1779
- @versionadded(version="3.4.0")
1780
- def aggregate(
1781
- self,
1782
- other_dict: dict[Any, int | float],
1783
- default_value: int | float = 0,
1784
- ) -> Self:
1785
- """Dict with value type int or float"""
1786
- out = {
1787
- k: self.get(k, default_value) + other_dict.get(k, default_value)
1788
- for k in set(self | other_dict)
1789
- }
1790
- return self.__class__(out)
1791
-
1792
-
1793
- # Run
1794
- ###########################################################################
1795
- if __name__ == "__main__":
1796
- logger.setLevel(10)