absfuyu 5.6.1__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 +2 -2
- absfuyu/cli/__init__.py +13 -2
- absfuyu/cli/audio_group.py +98 -0
- absfuyu/cli/color.py +2 -2
- absfuyu/cli/config_group.py +2 -2
- absfuyu/cli/do_group.py +2 -2
- absfuyu/cli/game_group.py +20 -2
- absfuyu/cli/tool_group.py +68 -4
- absfuyu/config/__init__.py +3 -3
- absfuyu/core/__init__.py +10 -6
- absfuyu/core/baseclass.py +104 -34
- absfuyu/core/baseclass2.py +43 -2
- absfuyu/core/decorator.py +2 -2
- absfuyu/core/docstring.py +4 -2
- absfuyu/core/dummy_cli.py +3 -3
- absfuyu/core/dummy_func.py +2 -2
- absfuyu/dxt/__init__.py +2 -2
- absfuyu/dxt/base_type.py +93 -0
- absfuyu/dxt/dictext.py +188 -6
- absfuyu/dxt/dxt_support.py +2 -2
- absfuyu/dxt/intext.py +72 -4
- absfuyu/dxt/listext.py +495 -23
- absfuyu/dxt/strext.py +2 -2
- 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 +2 -2
- absfuyu/extra/da/__init__.py +39 -3
- absfuyu/extra/da/dadf.py +436 -29
- absfuyu/extra/da/dadf_base.py +2 -2
- absfuyu/extra/da/df_func.py +89 -5
- absfuyu/extra/da/mplt.py +2 -2
- 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 +4 -6
- absfuyu/extra/rclone.py +253 -0
- absfuyu/extra/xml.py +90 -0
- absfuyu/fun/__init__.py +2 -20
- absfuyu/fun/rubik.py +2 -2
- 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 -2
- absfuyu/game/wordle.py +6 -4
- absfuyu/general/__init__.py +2 -2
- absfuyu/general/content.py +2 -2
- 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 +2 -2
- absfuyu/tools/checksum.py +119 -4
- absfuyu/tools/converter.py +2 -2
- absfuyu/tools/generator.py +24 -7
- absfuyu/tools/inspector.py +2 -2
- absfuyu/tools/keygen.py +2 -2
- absfuyu/tools/obfuscator.py +2 -2
- absfuyu/tools/passwordlib.py +2 -2
- absfuyu/tools/shutdownizer.py +3 -8
- absfuyu/tools/sw.py +213 -10
- absfuyu/tools/web.py +10 -13
- absfuyu/typings.py +5 -8
- absfuyu/util/__init__.py +31 -2
- absfuyu/util/api.py +7 -4
- absfuyu/util/cli.py +119 -0
- absfuyu/util/gui.py +91 -0
- absfuyu/util/json_method.py +2 -2
- absfuyu/util/lunar.py +2 -2
- absfuyu/util/package.py +124 -0
- absfuyu/util/path.py +313 -4
- absfuyu/util/performance.py +2 -2
- absfuyu/util/shorten_number.py +206 -13
- absfuyu/util/text_table.py +2 -2
- absfuyu/util/zipped.py +2 -2
- absfuyu/version.py +22 -19
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/METADATA +37 -8
- absfuyu-6.1.2.dist-info/RECORD +105 -0
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/WHEEL +1 -1
- absfuyu/extra/data_analysis.py +0 -21
- absfuyu-5.6.1.dist-info/RECORD +0 -79
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/entry_points.txt +0 -0
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/licenses/LICENSE +0 -0
absfuyu/dxt/listext.py
CHANGED
|
@@ -3,10 +3,12 @@ Absfuyu: Data Extension
|
|
|
3
3
|
-----------------------
|
|
4
4
|
list extension
|
|
5
5
|
|
|
6
|
-
Version:
|
|
7
|
-
Date updated: 12/
|
|
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__ = ["ListExt"]
|
|
@@ -20,13 +22,16 @@ from collections import Counter
|
|
|
20
22
|
from collections.abc import Callable, Iterable
|
|
21
23
|
from heapq import heapreplace
|
|
22
24
|
from itertools import accumulate, chain, count, groupby, zip_longest
|
|
23
|
-
from typing import Any, Literal, Self, cast, overload
|
|
25
|
+
from typing import Any, Literal, Self, TypeVar, cast, overload
|
|
24
26
|
|
|
25
27
|
from absfuyu.core.baseclass import GetClassMembersMixin
|
|
26
28
|
from absfuyu.core.docstring import deprecated, versionadded, versionchanged
|
|
27
|
-
from absfuyu.typings import T as _T
|
|
28
29
|
from absfuyu.util import set_min_max
|
|
29
30
|
|
|
31
|
+
# Type
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
T = TypeVar("T")
|
|
34
|
+
R = TypeVar("R") # Return type - Can be anything
|
|
30
35
|
|
|
31
36
|
# Class
|
|
32
37
|
# ---------------------------------------------------------------------------
|
|
@@ -80,9 +85,7 @@ class ListExt(GetClassMembersMixin, list):
|
|
|
80
85
|
>>> ListExt(range(10)).head(2)
|
|
81
86
|
[0, 1]
|
|
82
87
|
"""
|
|
83
|
-
number_of_items = int(
|
|
84
|
-
set_min_max(number_of_items, min_value=0, max_value=len(self))
|
|
85
|
-
)
|
|
88
|
+
number_of_items = int(set_min_max(number_of_items, min_value=0, max_value=len(self)))
|
|
86
89
|
return self[:number_of_items]
|
|
87
90
|
|
|
88
91
|
def tail(self, number_of_items: int = 5) -> list:
|
|
@@ -106,9 +109,7 @@ class ListExt(GetClassMembersMixin, list):
|
|
|
106
109
|
>>> ListExt(range(10)).tail(2)
|
|
107
110
|
[8, 9]
|
|
108
111
|
"""
|
|
109
|
-
number_of_items = int(
|
|
110
|
-
set_min_max(number_of_items, min_value=0, max_value=len(self))
|
|
111
|
-
)
|
|
112
|
+
number_of_items = int(set_min_max(number_of_items, min_value=0, max_value=len(self)))
|
|
112
113
|
return self[::-1][:number_of_items][::-1]
|
|
113
114
|
|
|
114
115
|
# Misc
|
|
@@ -165,15 +166,13 @@ class ListExt(GetClassMembersMixin, list):
|
|
|
165
166
|
type_weights[type(x)] = len(type_weights)
|
|
166
167
|
# logger.debug(f"Type weight: {type_weights}")
|
|
167
168
|
|
|
168
|
-
output = sorted(
|
|
169
|
-
lst, key=lambda x: (type_weights[type(x)], str(x)), reverse=reverse
|
|
170
|
-
)
|
|
169
|
+
output = sorted(lst, key=lambda x: (type_weights[type(x)], str(x)), reverse=reverse)
|
|
171
170
|
|
|
172
171
|
# logger.debug(output)
|
|
173
172
|
return self.__class__(output)
|
|
174
173
|
|
|
175
174
|
@overload
|
|
176
|
-
def freq(self) -> dict: ...
|
|
175
|
+
def freq(self) -> dict: ... # type: ignore
|
|
177
176
|
|
|
178
177
|
@overload
|
|
179
178
|
def freq(
|
|
@@ -256,9 +255,7 @@ class ListExt(GetClassMembersMixin, list):
|
|
|
256
255
|
# logger.debug(times_appear)
|
|
257
256
|
|
|
258
257
|
if appear_increment:
|
|
259
|
-
times_appear_increment: list[int] = list(
|
|
260
|
-
accumulate(times_appear.values(), operator.add)
|
|
261
|
-
)
|
|
258
|
+
times_appear_increment: list[int] = list(accumulate(times_appear.values(), operator.add))
|
|
262
259
|
# logger.debug(times_appear_increment)
|
|
263
260
|
return times_appear_increment # incremental index list
|
|
264
261
|
else:
|
|
@@ -385,7 +382,7 @@ class ListExt(GetClassMembersMixin, list):
|
|
|
385
382
|
return self.__class__(zip(nums, self))
|
|
386
383
|
|
|
387
384
|
@versionadded("5.3.0") # no test case yet
|
|
388
|
-
def transpose(self, fillvalue:
|
|
385
|
+
def transpose(self, fillvalue: Any | None = None, /) -> Self:
|
|
389
386
|
"""
|
|
390
387
|
Transpose a list of iterable.
|
|
391
388
|
|
|
@@ -459,6 +456,11 @@ class ListExt(GetClassMembersMixin, list):
|
|
|
459
456
|
"""
|
|
460
457
|
return [self.pick_one() for _ in range(number_of_items)]
|
|
461
458
|
|
|
459
|
+
@versionadded("5.10.0")
|
|
460
|
+
def shuffle(self) -> Self:
|
|
461
|
+
random.shuffle(self)
|
|
462
|
+
return self
|
|
463
|
+
|
|
462
464
|
# Len
|
|
463
465
|
@versionchanged("5.2.0", reason="Handle more type")
|
|
464
466
|
def len_items(self) -> Self:
|
|
@@ -746,7 +748,7 @@ class ListExt(GetClassMembersMixin, list):
|
|
|
746
748
|
return self.slice_points(slice_points)
|
|
747
749
|
|
|
748
750
|
@versionadded("5.3.0") # no test case yet
|
|
749
|
-
def to_column(self, ncols: int, fillvalue:
|
|
751
|
+
def to_column(self, ncols: int, fillvalue: Any | None = None) -> Self:
|
|
750
752
|
"""
|
|
751
753
|
Smart convert 1 dimension list to 2 dimension list,
|
|
752
754
|
in which, number of columns = ``ncols``.
|
|
@@ -756,7 +758,7 @@ class ListExt(GetClassMembersMixin, list):
|
|
|
756
758
|
ncols : int
|
|
757
759
|
Number of columns
|
|
758
760
|
|
|
759
|
-
fillvalue :
|
|
761
|
+
fillvalue : Any | None, optional
|
|
760
762
|
Fill value, by default ``None``
|
|
761
763
|
|
|
762
764
|
Returns
|
|
@@ -857,9 +859,7 @@ class ListExt(GetClassMembersMixin, list):
|
|
|
857
859
|
fill = " "
|
|
858
860
|
|
|
859
861
|
# Calculate how many columns of text
|
|
860
|
-
column_count = (
|
|
861
|
-
max(1, available_width // max_item_length) if max_item_length > 0 else 1
|
|
862
|
-
)
|
|
862
|
+
column_count = max(1, available_width // max_item_length) if max_item_length > 0 else 1
|
|
863
863
|
|
|
864
864
|
# splitted_chunk: list[list[str]] = self.split_chunk(cols)
|
|
865
865
|
# mod_chunk = self.__class__(
|
|
@@ -876,3 +876,475 @@ class ListExt(GetClassMembersMixin, list):
|
|
|
876
876
|
mod_chunk = self.split_chunk(column_count).apply(mod_item)
|
|
877
877
|
|
|
878
878
|
return mod_chunk
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
class ListExt2(GetClassMembersMixin, list[T]):
|
|
882
|
+
"""
|
|
883
|
+
``list`` extension (with generic - W.I.P)
|
|
884
|
+
|
|
885
|
+
>>> # For a list of new methods
|
|
886
|
+
>>> ListExt2.show_all_methods()
|
|
887
|
+
"""
|
|
888
|
+
|
|
889
|
+
# MARK: Info
|
|
890
|
+
def head(self, number_of_items: int = 5, /) -> list[T]:
|
|
891
|
+
"""
|
|
892
|
+
Show first ``number_of_items`` items in ``ListExt``
|
|
893
|
+
|
|
894
|
+
Parameters
|
|
895
|
+
----------
|
|
896
|
+
number_of_items : int
|
|
897
|
+
| Number of items to shows at once
|
|
898
|
+
| (Default: ``5``)
|
|
899
|
+
|
|
900
|
+
Returns
|
|
901
|
+
-------
|
|
902
|
+
list
|
|
903
|
+
Filtered list
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
Example:
|
|
907
|
+
--------
|
|
908
|
+
>>> ListExt(range(10)).head(2)
|
|
909
|
+
[0, 1]
|
|
910
|
+
"""
|
|
911
|
+
number_of_items = int(set_min_max(number_of_items, min_value=0, max_value=len(self)))
|
|
912
|
+
return self[:number_of_items]
|
|
913
|
+
|
|
914
|
+
def tail(self, number_of_items: int = 5, /) -> list[T]:
|
|
915
|
+
"""
|
|
916
|
+
Show last ``number_of_items`` items in ``ListExt``
|
|
917
|
+
|
|
918
|
+
Parameters
|
|
919
|
+
----------
|
|
920
|
+
number_of_items : int
|
|
921
|
+
| Number of items to shows at once
|
|
922
|
+
| (Default: ``5``)
|
|
923
|
+
|
|
924
|
+
Returns
|
|
925
|
+
-------
|
|
926
|
+
list
|
|
927
|
+
Filtered list
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
Example:
|
|
931
|
+
--------
|
|
932
|
+
>>> ListExt(range(10)).tail(2)
|
|
933
|
+
[8, 9]
|
|
934
|
+
"""
|
|
935
|
+
number_of_items = int(set_min_max(number_of_items, min_value=0, max_value=len(self)))
|
|
936
|
+
return self[::-1][:number_of_items][::-1]
|
|
937
|
+
|
|
938
|
+
# MARK: Misc
|
|
939
|
+
def apply(self, func: Callable[[T], R]) -> ListExt2[R]:
|
|
940
|
+
"""
|
|
941
|
+
Apply function to each entry
|
|
942
|
+
|
|
943
|
+
Parameters
|
|
944
|
+
----------
|
|
945
|
+
func : Callable[[Any], Any]
|
|
946
|
+
Callable function
|
|
947
|
+
|
|
948
|
+
Returns
|
|
949
|
+
-------
|
|
950
|
+
Self
|
|
951
|
+
ListExt
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
Example:
|
|
955
|
+
--------
|
|
956
|
+
>>> test = ListExt([1, 2, 3])
|
|
957
|
+
>>> test.apply(str)
|
|
958
|
+
['1', '2', '3']
|
|
959
|
+
"""
|
|
960
|
+
# return self.__class__(map(func, self))
|
|
961
|
+
return self.__class__(func(x) for x in self) # type: ignore
|
|
962
|
+
|
|
963
|
+
def sorts(self, reverse: bool = False) -> Self:
|
|
964
|
+
"""
|
|
965
|
+
Sort all items (with different type) in ``list``
|
|
966
|
+
|
|
967
|
+
Parameters
|
|
968
|
+
----------
|
|
969
|
+
reverse : bool
|
|
970
|
+
- ``True`` then sort in descending order
|
|
971
|
+
- ``False`` then sort in ascending order (default value)
|
|
972
|
+
|
|
973
|
+
Returns
|
|
974
|
+
-------
|
|
975
|
+
Self
|
|
976
|
+
A sorted list
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
Example:
|
|
980
|
+
--------
|
|
981
|
+
>>> test = ListExt([9, "abc", 3.5, "aaa", 1, 1.4])
|
|
982
|
+
>>> test.sorts()
|
|
983
|
+
[1, 9, 'aaa', 'abc', 1.4, 3.5]
|
|
984
|
+
"""
|
|
985
|
+
lst = self.copy()
|
|
986
|
+
type_weights: dict = {}
|
|
987
|
+
for x in lst:
|
|
988
|
+
if type(x) not in type_weights:
|
|
989
|
+
type_weights[type(x)] = len(type_weights)
|
|
990
|
+
# logger.debug(f"Type weight: {type_weights}")
|
|
991
|
+
|
|
992
|
+
output = sorted(lst, key=lambda x: (type_weights[type(x)], str(x)), reverse=reverse)
|
|
993
|
+
|
|
994
|
+
# logger.debug(output)
|
|
995
|
+
return self.__class__(output)
|
|
996
|
+
|
|
997
|
+
@versionchanged("5.2.0", reason="New ``recursive`` parameter")
|
|
998
|
+
def flatten(self, recursive: bool = False) -> Self:
|
|
999
|
+
"""
|
|
1000
|
+
Flatten the list
|
|
1001
|
+
|
|
1002
|
+
Parameters
|
|
1003
|
+
----------
|
|
1004
|
+
recursive : bool
|
|
1005
|
+
Recursively flatten the list, by default ``False``
|
|
1006
|
+
|
|
1007
|
+
Returns
|
|
1008
|
+
-------
|
|
1009
|
+
Self
|
|
1010
|
+
Flattened list
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
Example:
|
|
1014
|
+
--------
|
|
1015
|
+
>>> test = ListExt([["test"], ["test", "test"], ["test"]])
|
|
1016
|
+
>>> test.flatten()
|
|
1017
|
+
['test', 'test', 'test', 'test']
|
|
1018
|
+
"""
|
|
1019
|
+
|
|
1020
|
+
def instance_checking(item):
|
|
1021
|
+
if isinstance(item, str):
|
|
1022
|
+
return [item]
|
|
1023
|
+
if isinstance(item, Iterable):
|
|
1024
|
+
return item
|
|
1025
|
+
return [item]
|
|
1026
|
+
|
|
1027
|
+
# Flatten
|
|
1028
|
+
list_of_list = (instance_checking(x) for x in self)
|
|
1029
|
+
flattened = self.__class__(chain(*list_of_list))
|
|
1030
|
+
|
|
1031
|
+
# Recursive
|
|
1032
|
+
if recursive:
|
|
1033
|
+
|
|
1034
|
+
def _condition(item) -> bool:
|
|
1035
|
+
if isinstance(item, str):
|
|
1036
|
+
return False
|
|
1037
|
+
return isinstance(item, Iterable)
|
|
1038
|
+
|
|
1039
|
+
while any(flattened.apply(_condition)):
|
|
1040
|
+
flattened = flattened.flatten()
|
|
1041
|
+
|
|
1042
|
+
# Return
|
|
1043
|
+
return flattened
|
|
1044
|
+
|
|
1045
|
+
@versionadded("5.2.0")
|
|
1046
|
+
def join(self, sep: str = " ", /) -> str:
|
|
1047
|
+
"""
|
|
1048
|
+
Join every element in list to str (``str.join()`` wrapper).
|
|
1049
|
+
|
|
1050
|
+
Parameters
|
|
1051
|
+
----------
|
|
1052
|
+
sep : str, optional
|
|
1053
|
+
Separator between each element, by default ``" "``
|
|
1054
|
+
|
|
1055
|
+
Returns
|
|
1056
|
+
-------
|
|
1057
|
+
str
|
|
1058
|
+
Joined list
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
Example:
|
|
1062
|
+
--------
|
|
1063
|
+
>>> ListExt(["a", "b", "c"]).join()
|
|
1064
|
+
a b c
|
|
1065
|
+
|
|
1066
|
+
>>> # Also work with non-str type
|
|
1067
|
+
>>> ListExt([1, 2, 3]).join()
|
|
1068
|
+
1 2 3
|
|
1069
|
+
"""
|
|
1070
|
+
try:
|
|
1071
|
+
return sep.join(self) # type: ignore
|
|
1072
|
+
except TypeError:
|
|
1073
|
+
return sep.join(self.apply(str))
|
|
1074
|
+
|
|
1075
|
+
def group_by_pair_value(self, max_loop: int = 3):
|
|
1076
|
+
"""
|
|
1077
|
+
Assume each ``list`` in ``list`` is a pair value,
|
|
1078
|
+
returns a ``list`` contain all paired value
|
|
1079
|
+
|
|
1080
|
+
Parameters
|
|
1081
|
+
----------
|
|
1082
|
+
max_loop : int
|
|
1083
|
+
Times to run functions (minimum: ``3``)
|
|
1084
|
+
|
|
1085
|
+
Returns
|
|
1086
|
+
-------
|
|
1087
|
+
list[list]
|
|
1088
|
+
Grouped value
|
|
1089
|
+
|
|
1090
|
+
|
|
1091
|
+
Example:
|
|
1092
|
+
--------
|
|
1093
|
+
>>> test = ListExt([[1, 2], [2, 3], [4, 3], [5, 6]])
|
|
1094
|
+
>>> test.group_by_pair_value()
|
|
1095
|
+
[[1, 2, 3, 4], [5, 6]]
|
|
1096
|
+
|
|
1097
|
+
>>> test = ListExt([[8, 3], [4, 6], [6, 3], [5, 2], [7, 2]])
|
|
1098
|
+
>>> test.group_by_pair_value()
|
|
1099
|
+
[[8, 3, 4, 6], [2, 5, 7]]
|
|
1100
|
+
|
|
1101
|
+
>>> test = ListExt([["a", 4], ["b", 4], [5, "c"]])
|
|
1102
|
+
>>> test.group_by_pair_value()
|
|
1103
|
+
[['a', 4, 'b'], ['c', 5]]
|
|
1104
|
+
"""
|
|
1105
|
+
|
|
1106
|
+
iter = self.copy()
|
|
1107
|
+
|
|
1108
|
+
# Init loop
|
|
1109
|
+
for _ in range(max(max_loop, 3)):
|
|
1110
|
+
temp: dict[Any, list] = {}
|
|
1111
|
+
# Make dict{key: all `item` that contains `key`}
|
|
1112
|
+
for item in iter:
|
|
1113
|
+
for x in item:
|
|
1114
|
+
if temp.get(x, None) is None:
|
|
1115
|
+
temp[x] = [item]
|
|
1116
|
+
else:
|
|
1117
|
+
temp[x].append(item)
|
|
1118
|
+
|
|
1119
|
+
# Flatten dict.values
|
|
1120
|
+
temp = {k: list(set(chain(*v))) for k, v in temp.items()}
|
|
1121
|
+
|
|
1122
|
+
iter = list(temp.values())
|
|
1123
|
+
|
|
1124
|
+
return list(x for x, _ in groupby(iter))
|
|
1125
|
+
|
|
1126
|
+
def split_equal(self, n: int, sort: bool = True) -> ListExt2[list[T]]:
|
|
1127
|
+
"""
|
|
1128
|
+
Try to equally split a list of number into ``n`` equal sum parts.
|
|
1129
|
+
|
|
1130
|
+
**Note:** Element in list must be a number.
|
|
1131
|
+
|
|
1132
|
+
Parameters
|
|
1133
|
+
----------
|
|
1134
|
+
n : int
|
|
1135
|
+
Split into how many parts. Must be >= 1.
|
|
1136
|
+
|
|
1137
|
+
sort : bool, optional
|
|
1138
|
+
Sort the instance before split, by default ``True``
|
|
1139
|
+
|
|
1140
|
+
Returns
|
|
1141
|
+
-------
|
|
1142
|
+
Self
|
|
1143
|
+
Splitted (list[list])
|
|
1144
|
+
|
|
1145
|
+
|
|
1146
|
+
Example:
|
|
1147
|
+
--------
|
|
1148
|
+
>>> ListExt(range(1, 11)).split_equal(2)
|
|
1149
|
+
[[10, 7, 5, 4, 1], [9, 8, 6, 3, 2]]
|
|
1150
|
+
"""
|
|
1151
|
+
|
|
1152
|
+
# https://stackoverflow.com/a/61649667
|
|
1153
|
+
bins: list[list[int]] = [[0] for _ in range(max(n, 1))]
|
|
1154
|
+
if sort:
|
|
1155
|
+
# self = self.sorts(reverse=True)
|
|
1156
|
+
self = self.__class__(sorted(self, reverse=True))
|
|
1157
|
+
for x in self:
|
|
1158
|
+
least = bins[0]
|
|
1159
|
+
least[0] += x
|
|
1160
|
+
least.append(x)
|
|
1161
|
+
heapreplace(bins, least)
|
|
1162
|
+
return self.__class__(x[1:] for x in bins)
|
|
1163
|
+
|
|
1164
|
+
# MARK: Random
|
|
1165
|
+
def pick_one(self) -> T:
|
|
1166
|
+
"""
|
|
1167
|
+
Pick one random items from ``list``
|
|
1168
|
+
|
|
1169
|
+
Returns
|
|
1170
|
+
-------
|
|
1171
|
+
Any
|
|
1172
|
+
Random value
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
Example:
|
|
1176
|
+
--------
|
|
1177
|
+
>>> test = ListExt(["foo", "bar"])
|
|
1178
|
+
>>> test.pick_one()
|
|
1179
|
+
'bar'
|
|
1180
|
+
"""
|
|
1181
|
+
if len(self) != 0:
|
|
1182
|
+
out = random.choice(self)
|
|
1183
|
+
# logger.debug(out)
|
|
1184
|
+
return out
|
|
1185
|
+
else:
|
|
1186
|
+
# logger.debug("List empty!")
|
|
1187
|
+
raise IndexError("List empty!")
|
|
1188
|
+
|
|
1189
|
+
def get_random(self, number_of_items: int = 5, /) -> list[T]:
|
|
1190
|
+
"""
|
|
1191
|
+
Get ``number_of_items`` random items in ``ListExt``
|
|
1192
|
+
|
|
1193
|
+
Parameters
|
|
1194
|
+
----------
|
|
1195
|
+
number_of_items : int
|
|
1196
|
+
Number random of items, by default ``5``
|
|
1197
|
+
|
|
1198
|
+
Returns
|
|
1199
|
+
-------
|
|
1200
|
+
list
|
|
1201
|
+
Filtered list
|
|
1202
|
+
"""
|
|
1203
|
+
return [self.pick_one() for _ in range(number_of_items)]
|
|
1204
|
+
|
|
1205
|
+
@versionadded("5.10.0")
|
|
1206
|
+
def shuffle(self) -> Self:
|
|
1207
|
+
"""
|
|
1208
|
+
Shuffle the list itself
|
|
1209
|
+
|
|
1210
|
+
Returns
|
|
1211
|
+
-------
|
|
1212
|
+
Self
|
|
1213
|
+
Shuffled list
|
|
1214
|
+
"""
|
|
1215
|
+
random.shuffle(self)
|
|
1216
|
+
return self
|
|
1217
|
+
|
|
1218
|
+
# MARK: Len
|
|
1219
|
+
@versionchanged("5.2.0", reason="Handle more type")
|
|
1220
|
+
def len_items(self) -> ListExt2[int]:
|
|
1221
|
+
"""
|
|
1222
|
+
``len()`` for every item in ``self``
|
|
1223
|
+
|
|
1224
|
+
Returns
|
|
1225
|
+
-------
|
|
1226
|
+
Self
|
|
1227
|
+
List of ``len()``'ed value
|
|
1228
|
+
|
|
1229
|
+
|
|
1230
|
+
Example:
|
|
1231
|
+
--------
|
|
1232
|
+
>>> test = ListExt(["foo", "bar", "pizza"])
|
|
1233
|
+
>>> test.len_items()
|
|
1234
|
+
[3, 3, 5]
|
|
1235
|
+
"""
|
|
1236
|
+
|
|
1237
|
+
def _len(item: Any) -> int:
|
|
1238
|
+
try:
|
|
1239
|
+
return len(item)
|
|
1240
|
+
except TypeError:
|
|
1241
|
+
return len(str(item))
|
|
1242
|
+
|
|
1243
|
+
return self.__class__([_len(x) for x in self]) # type: ignore
|
|
1244
|
+
|
|
1245
|
+
@versionchanged("5.2.0", reason="New ``recursive`` parameter")
|
|
1246
|
+
def mean_len(self, recursive: bool = False) -> float:
|
|
1247
|
+
"""
|
|
1248
|
+
Average length of every item. Returns zero if failed (empty list, ZeroDivisionError).
|
|
1249
|
+
|
|
1250
|
+
Parameters
|
|
1251
|
+
----------
|
|
1252
|
+
recursive : bool
|
|
1253
|
+
Recursively find the average length of items in nested lists, by default ``False``
|
|
1254
|
+
|
|
1255
|
+
Returns
|
|
1256
|
+
-------
|
|
1257
|
+
float
|
|
1258
|
+
Average length
|
|
1259
|
+
|
|
1260
|
+
|
|
1261
|
+
Example:
|
|
1262
|
+
--------
|
|
1263
|
+
>>> test = ListExt(["foo", "bar", "pizza"])
|
|
1264
|
+
>>> test.mean_len()
|
|
1265
|
+
3.6666666666666665
|
|
1266
|
+
"""
|
|
1267
|
+
|
|
1268
|
+
if recursive:
|
|
1269
|
+
dat = self.flatten(recursive=recursive)
|
|
1270
|
+
else:
|
|
1271
|
+
dat = self
|
|
1272
|
+
|
|
1273
|
+
try:
|
|
1274
|
+
return sum(dat.len_items()) / len(dat)
|
|
1275
|
+
except ZeroDivisionError:
|
|
1276
|
+
return 0.0
|
|
1277
|
+
|
|
1278
|
+
@versionadded("5.2.0")
|
|
1279
|
+
def max_item_len(self, recursive: bool = False) -> int:
|
|
1280
|
+
"""
|
|
1281
|
+
Find the maximum length of items in the list.
|
|
1282
|
+
|
|
1283
|
+
Parameters
|
|
1284
|
+
----------
|
|
1285
|
+
recursive : bool
|
|
1286
|
+
Recursively find the maximum length of items in nested lists, by default ``False``
|
|
1287
|
+
|
|
1288
|
+
Returns
|
|
1289
|
+
-------
|
|
1290
|
+
int
|
|
1291
|
+
Maximum length of items
|
|
1292
|
+
|
|
1293
|
+
|
|
1294
|
+
Example:
|
|
1295
|
+
--------
|
|
1296
|
+
>>> test = ListExt(["test", "longer_test"])
|
|
1297
|
+
>>> test.max_item_len()
|
|
1298
|
+
11
|
|
1299
|
+
|
|
1300
|
+
>>> test = ListExt([["short"], ["longer_test"]])
|
|
1301
|
+
>>> test.max_item_len(recursive=True)
|
|
1302
|
+
11
|
|
1303
|
+
"""
|
|
1304
|
+
if recursive:
|
|
1305
|
+
return cast(int, max(self.flatten(recursive=True).len_items()))
|
|
1306
|
+
return cast(int, max(self.len_items()))
|
|
1307
|
+
|
|
1308
|
+
# MARK: Group/Unique
|
|
1309
|
+
def unique(self) -> Self:
|
|
1310
|
+
"""
|
|
1311
|
+
Remove duplicates
|
|
1312
|
+
|
|
1313
|
+
Returns
|
|
1314
|
+
-------
|
|
1315
|
+
Self
|
|
1316
|
+
Duplicates removed list
|
|
1317
|
+
|
|
1318
|
+
|
|
1319
|
+
Example:
|
|
1320
|
+
--------
|
|
1321
|
+
>>> test = ListExt([1, 1, 1, 2, 2, 3])
|
|
1322
|
+
>>> test.unique()
|
|
1323
|
+
[1, 2, 3]
|
|
1324
|
+
"""
|
|
1325
|
+
return self.__class__(set(self))
|
|
1326
|
+
|
|
1327
|
+
def group_by_unique(self) -> ListExt2[list[T]]:
|
|
1328
|
+
"""
|
|
1329
|
+
Group duplicated elements into list
|
|
1330
|
+
|
|
1331
|
+
Returns
|
|
1332
|
+
-------
|
|
1333
|
+
Self
|
|
1334
|
+
Grouped value
|
|
1335
|
+
|
|
1336
|
+
|
|
1337
|
+
Example:
|
|
1338
|
+
--------
|
|
1339
|
+
>>> test = ListExt([1, 2, 3, 1, 3, 3, 2])
|
|
1340
|
+
>>> test.group_by_unique()
|
|
1341
|
+
[[1, 1], [2, 2], [3, 3, 3]]
|
|
1342
|
+
"""
|
|
1343
|
+
temp = groupby(self.sorts())
|
|
1344
|
+
return self.__class__([list(g) for _, g in temp])
|
|
1345
|
+
|
|
1346
|
+
|
|
1347
|
+
if __name__ == "__main__":
|
|
1348
|
+
test = ListExt2([1, 2, 3, 4, 5, "s"])
|
|
1349
|
+
a = test.group_by_unique()
|
|
1350
|
+
print(a, type(a))
|
absfuyu/dxt/strext.py
CHANGED
absfuyu/extra/__init__.py
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Audio
|
|
3
|
+
--------------
|
|
4
|
+
Audio convert, lossless checker
|
|
5
|
+
|
|
6
|
+
Version: 6.1.1
|
|
7
|
+
Date updated: 30/12/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Module level
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
__all__ = ["StatusCode", "ResultStatus"]
|
|
13
|
+
|
|
14
|
+
# Library
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
from enum import StrEnum
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
# Class Enum, Result
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
try:
|
|
22
|
+
from rich import print
|
|
23
|
+
|
|
24
|
+
class StatusCode(StrEnum):
|
|
25
|
+
OK = "[bold green]OK[/]"
|
|
26
|
+
SKIP = "[bold yellow]SKIP[/]"
|
|
27
|
+
ERROR = "[bold red]ERROR[/]"
|
|
28
|
+
LOSSLESS = "[bold green]LOSSLESS[/]"
|
|
29
|
+
NOT_LOSSLESS = "[bold red]NOT LOSSLESS[/]"
|
|
30
|
+
HIRES = "[bold blue]HIRES[/]"
|
|
31
|
+
|
|
32
|
+
except ImportError:
|
|
33
|
+
|
|
34
|
+
class StatusCode(StrEnum):
|
|
35
|
+
OK = "OK"
|
|
36
|
+
SKIP = "SKIP"
|
|
37
|
+
ERROR = "ERROR"
|
|
38
|
+
LOSSLESS = "LOSSLESS"
|
|
39
|
+
NOT_LOSSLESS = "NOT LOSSLESS"
|
|
40
|
+
HIRES = "HIRES"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ResultStatus:
|
|
44
|
+
"""
|
|
45
|
+
Result status
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, status: StatusCode, path: Path) -> None:
|
|
49
|
+
self.status = status
|
|
50
|
+
self.path = path
|
|
51
|
+
|
|
52
|
+
def __repr__(self) -> str:
|
|
53
|
+
return f"{self.status} : {self.path.name}"
|
|
54
|
+
|
|
55
|
+
def print(self) -> None:
|
|
56
|
+
"""Print repr (for rich package)"""
|
|
57
|
+
print(self.status, ": ", self.path.name)
|