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/listext.py
CHANGED
|
@@ -3,10 +3,12 @@ Absfuyu: Data Extension
|
|
|
3
3
|
-----------------------
|
|
4
4
|
list 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__ = ["ListExt"]
|
|
@@ -17,28 +19,39 @@ __all__ = ["ListExt"]
|
|
|
17
19
|
import operator
|
|
18
20
|
import random
|
|
19
21
|
from collections import Counter
|
|
20
|
-
from collections.abc import Callable
|
|
21
|
-
from
|
|
22
|
-
from
|
|
22
|
+
from collections.abc import Callable, Iterable
|
|
23
|
+
from heapq import heapreplace
|
|
24
|
+
from itertools import accumulate, chain, count, groupby, zip_longest
|
|
25
|
+
from typing import Any, Literal, Self, TypeVar, cast, overload
|
|
23
26
|
|
|
24
|
-
from absfuyu.core import
|
|
25
|
-
from absfuyu.
|
|
27
|
+
from absfuyu.core.baseclass import GetClassMembersMixin
|
|
28
|
+
from absfuyu.core.docstring import deprecated, versionadded, versionchanged
|
|
29
|
+
from absfuyu.util import set_min_max
|
|
26
30
|
|
|
31
|
+
# Type
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
T = TypeVar("T")
|
|
34
|
+
R = TypeVar("R") # Return type - Can be anything
|
|
27
35
|
|
|
28
36
|
# Class
|
|
29
37
|
# ---------------------------------------------------------------------------
|
|
30
|
-
class ListExt(
|
|
38
|
+
class ListExt(GetClassMembersMixin, list):
|
|
31
39
|
"""
|
|
32
40
|
``list`` extension
|
|
41
|
+
|
|
42
|
+
>>> # For a list of new methods
|
|
43
|
+
>>> ListExt.show_all_methods()
|
|
33
44
|
"""
|
|
34
45
|
|
|
46
|
+
# Deprecated
|
|
47
|
+
@deprecated("5.2.0", reason="Use ListExt.apply(str) instead")
|
|
35
48
|
def stringify(self) -> Self:
|
|
36
49
|
"""
|
|
37
50
|
Convert all item in ``list`` into string
|
|
38
51
|
|
|
39
52
|
Returns
|
|
40
53
|
-------
|
|
41
|
-
|
|
54
|
+
Self
|
|
42
55
|
A list with all items with type <str`>
|
|
43
56
|
|
|
44
57
|
|
|
@@ -50,6 +63,7 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
50
63
|
"""
|
|
51
64
|
return self.__class__(map(str, self))
|
|
52
65
|
|
|
66
|
+
# Info
|
|
53
67
|
def head(self, number_of_items: int = 5) -> list:
|
|
54
68
|
"""
|
|
55
69
|
Show first ``number_of_items`` items in ``ListExt``
|
|
@@ -64,10 +78,14 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
64
78
|
-------
|
|
65
79
|
list
|
|
66
80
|
Filtered list
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
Example:
|
|
84
|
+
--------
|
|
85
|
+
>>> ListExt(range(10)).head(2)
|
|
86
|
+
[0, 1]
|
|
67
87
|
"""
|
|
68
|
-
number_of_items = int(
|
|
69
|
-
set_min_max(number_of_items, min_value=0, max_value=len(self))
|
|
70
|
-
)
|
|
88
|
+
number_of_items = int(set_min_max(number_of_items, min_value=0, max_value=len(self)))
|
|
71
89
|
return self[:number_of_items]
|
|
72
90
|
|
|
73
91
|
def tail(self, number_of_items: int = 5) -> list:
|
|
@@ -84,12 +102,41 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
84
102
|
-------
|
|
85
103
|
list
|
|
86
104
|
Filtered list
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
--------
|
|
109
|
+
>>> ListExt(range(10)).tail(2)
|
|
110
|
+
[8, 9]
|
|
87
111
|
"""
|
|
88
|
-
number_of_items = int(
|
|
89
|
-
set_min_max(number_of_items, min_value=0, max_value=len(self))
|
|
90
|
-
)
|
|
112
|
+
number_of_items = int(set_min_max(number_of_items, min_value=0, max_value=len(self)))
|
|
91
113
|
return self[::-1][:number_of_items][::-1]
|
|
92
114
|
|
|
115
|
+
# Misc
|
|
116
|
+
def apply(self, func: Callable[[Any], Any]) -> Self:
|
|
117
|
+
"""
|
|
118
|
+
Apply function to each entry
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
func : Callable
|
|
123
|
+
Callable function
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
Self
|
|
128
|
+
ListExt
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
--------
|
|
133
|
+
>>> test = ListExt([1, 2, 3])
|
|
134
|
+
>>> test.apply(str)
|
|
135
|
+
['1', '2', '3']
|
|
136
|
+
"""
|
|
137
|
+
# return self.__class__(map(func, self))
|
|
138
|
+
return self.__class__(func(x) for x in self)
|
|
139
|
+
|
|
93
140
|
def sorts(self, reverse: bool = False) -> Self:
|
|
94
141
|
"""
|
|
95
142
|
Sort all items (with different type) in ``list``
|
|
@@ -97,13 +144,12 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
97
144
|
Parameters
|
|
98
145
|
----------
|
|
99
146
|
reverse : bool
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
| (Default: ``False``)
|
|
147
|
+
- ``True`` then sort in descending order
|
|
148
|
+
- ``False`` then sort in ascending order (default value)
|
|
103
149
|
|
|
104
150
|
Returns
|
|
105
151
|
-------
|
|
106
|
-
|
|
152
|
+
Self
|
|
107
153
|
A sorted list
|
|
108
154
|
|
|
109
155
|
|
|
@@ -120,13 +166,29 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
120
166
|
type_weights[type(x)] = len(type_weights)
|
|
121
167
|
# logger.debug(f"Type weight: {type_weights}")
|
|
122
168
|
|
|
123
|
-
output = sorted(
|
|
124
|
-
lst, key=lambda x: (type_weights[type(x)], str(x)), reverse=reverse
|
|
125
|
-
)
|
|
169
|
+
output = sorted(lst, key=lambda x: (type_weights[type(x)], str(x)), reverse=reverse)
|
|
126
170
|
|
|
127
171
|
# logger.debug(output)
|
|
128
172
|
return self.__class__(output)
|
|
129
173
|
|
|
174
|
+
@overload
|
|
175
|
+
def freq(self) -> dict: ... # type: ignore
|
|
176
|
+
|
|
177
|
+
@overload
|
|
178
|
+
def freq(
|
|
179
|
+
self,
|
|
180
|
+
sort: bool = False,
|
|
181
|
+
num_of_first_char: int | None = None,
|
|
182
|
+
) -> dict: ...
|
|
183
|
+
|
|
184
|
+
@overload
|
|
185
|
+
def freq(
|
|
186
|
+
self,
|
|
187
|
+
sort: bool = False,
|
|
188
|
+
num_of_first_char: int | None = None,
|
|
189
|
+
appear_increment: Literal[True] = ...,
|
|
190
|
+
) -> list[int]: ...
|
|
191
|
+
|
|
130
192
|
def freq(
|
|
131
193
|
self,
|
|
132
194
|
sort: bool = False,
|
|
@@ -138,18 +200,17 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
138
200
|
|
|
139
201
|
Parameters
|
|
140
202
|
----------
|
|
141
|
-
sort : bool
|
|
142
|
-
|
|
143
|
-
|
|
203
|
+
sort : bool, optional
|
|
204
|
+
- ``True``: Sorts the output in ascending order
|
|
205
|
+
- ``False``: No sort (default value)
|
|
144
206
|
|
|
145
|
-
num_of_first_char : int | None
|
|
146
|
-
| Number of first character taken into account to sort
|
|
147
|
-
|
|
|
148
|
-
| (num_of_first_char = ``1``: first character in each item)
|
|
207
|
+
num_of_first_char : int | None, optional
|
|
208
|
+
| Number of first character taken into account to sort, by default ``None``
|
|
209
|
+
| Eg: ``num_of_first_char = 1``: first character of each item
|
|
149
210
|
|
|
150
|
-
appear_increment : bool
|
|
151
|
-
|
|
152
|
-
|
|
211
|
+
appear_increment : bool, optional
|
|
212
|
+
Returns incremental index list of each item when sort,
|
|
213
|
+
by default ``False``
|
|
153
214
|
|
|
154
215
|
Returns
|
|
155
216
|
-------
|
|
@@ -170,7 +231,6 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
170
231
|
>>> test.freq(appear_increment=True)
|
|
171
232
|
[2, 3, 5, 6, 7, 8]
|
|
172
233
|
"""
|
|
173
|
-
|
|
174
234
|
if sort:
|
|
175
235
|
data = self.sorts().copy()
|
|
176
236
|
else:
|
|
@@ -190,57 +250,172 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
190
250
|
|
|
191
251
|
try:
|
|
192
252
|
times_appear = dict(sorted(temp.items()))
|
|
193
|
-
except
|
|
253
|
+
except TypeError:
|
|
194
254
|
times_appear = dict(self.__class__(temp.items()).sorts())
|
|
195
255
|
# logger.debug(times_appear)
|
|
196
256
|
|
|
197
257
|
if appear_increment:
|
|
198
|
-
times_appear_increment: list[int] = list(
|
|
199
|
-
accumulate(times_appear.values(), operator.add)
|
|
200
|
-
)
|
|
258
|
+
times_appear_increment: list[int] = list(accumulate(times_appear.values(), operator.add))
|
|
201
259
|
# logger.debug(times_appear_increment)
|
|
202
260
|
return times_appear_increment # incremental index list
|
|
203
261
|
else:
|
|
204
262
|
return times_appear # character frequency
|
|
205
263
|
|
|
206
|
-
|
|
264
|
+
@versionchanged("5.2.0", reason="New ``recursive`` parameter")
|
|
265
|
+
def flatten(self, recursive: bool = False) -> Self:
|
|
207
266
|
"""
|
|
208
|
-
|
|
267
|
+
Flatten the list
|
|
209
268
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
269
|
+
Parameters
|
|
270
|
+
----------
|
|
271
|
+
recursive : bool
|
|
272
|
+
Recursively flatten the list, by default ``False``
|
|
273
|
+
|
|
274
|
+
Returns
|
|
275
|
+
-------
|
|
276
|
+
Self
|
|
277
|
+
Flattened list
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
Example:
|
|
281
|
+
--------
|
|
282
|
+
>>> test = ListExt([["test"], ["test", "test"], ["test"]])
|
|
283
|
+
>>> test.flatten()
|
|
284
|
+
['test', 'test', 'test', 'test']
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
def instance_checking(item):
|
|
288
|
+
if isinstance(item, str):
|
|
289
|
+
return [item]
|
|
290
|
+
if isinstance(item, Iterable):
|
|
291
|
+
return item
|
|
292
|
+
return [item]
|
|
293
|
+
|
|
294
|
+
# Flatten
|
|
295
|
+
list_of_list = (instance_checking(x) for x in self)
|
|
296
|
+
flattened = self.__class__(chain(*list_of_list))
|
|
297
|
+
|
|
298
|
+
# Recursive
|
|
299
|
+
if recursive:
|
|
300
|
+
|
|
301
|
+
def _condition(item) -> bool:
|
|
302
|
+
if isinstance(item, str):
|
|
303
|
+
return False
|
|
304
|
+
return isinstance(item, Iterable)
|
|
305
|
+
|
|
306
|
+
while any(flattened.apply(_condition)):
|
|
307
|
+
flattened = flattened.flatten()
|
|
308
|
+
|
|
309
|
+
# Return
|
|
310
|
+
return flattened
|
|
311
|
+
|
|
312
|
+
@versionadded("5.2.0")
|
|
313
|
+
def join(self, sep: str = " ", /) -> str:
|
|
314
|
+
"""
|
|
315
|
+
Join every element in list to str (``str.join()`` wrapper).
|
|
214
316
|
|
|
215
317
|
Parameters
|
|
216
318
|
----------
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
the list. These indices are *exclusive* of the starting sublist
|
|
220
|
-
but *inclusive* of the ending sublist.
|
|
319
|
+
sep : str, optional
|
|
320
|
+
Separator between each element, by default ``" "``
|
|
221
321
|
|
|
222
322
|
Returns
|
|
223
323
|
-------
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
defined by the provided split points.
|
|
324
|
+
str
|
|
325
|
+
Joined list
|
|
227
326
|
|
|
228
327
|
|
|
229
328
|
Example:
|
|
230
329
|
--------
|
|
231
|
-
>>>
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
>>>
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
[[1, 1, 2, 3, 3, 4, 5, 6]]
|
|
330
|
+
>>> ListExt(["a", "b", "c"]).join()
|
|
331
|
+
a b c
|
|
332
|
+
|
|
333
|
+
>>> # Also work with non-str type
|
|
334
|
+
>>> ListExt([1, 2, 3]).join()
|
|
335
|
+
1 2 3
|
|
238
336
|
"""
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
337
|
+
try:
|
|
338
|
+
return sep.join(self)
|
|
339
|
+
except TypeError:
|
|
340
|
+
return sep.join(self.apply(str))
|
|
341
|
+
|
|
342
|
+
@overload
|
|
343
|
+
def numbering(self) -> Self: ...
|
|
344
|
+
|
|
345
|
+
@overload
|
|
346
|
+
def numbering(self, start: int | float = 0) -> Self: ...
|
|
347
|
+
|
|
348
|
+
@overload
|
|
349
|
+
def numbering(self, *, step: int | float = 1) -> Self: ...
|
|
350
|
+
|
|
351
|
+
@overload
|
|
352
|
+
def numbering(self, start: int | float = 0, step: int | float = 1) -> Self: ...
|
|
353
|
+
|
|
354
|
+
@versionchanged("5.5.0", "Use itertools.count to wrap")
|
|
355
|
+
def numbering(self, start: int | float = 0, step: int | float = 1) -> Self:
|
|
356
|
+
"""
|
|
357
|
+
Number the item in list
|
|
358
|
+
(``itertools.count`` wrapper)
|
|
359
|
+
|
|
360
|
+
Parameters
|
|
361
|
+
----------
|
|
362
|
+
start : int | float, optional
|
|
363
|
+
Start from which number, by default ``0``
|
|
364
|
+
|
|
365
|
+
step : int | float, optional
|
|
366
|
+
Step, by default ``1``
|
|
367
|
+
|
|
368
|
+
Returns
|
|
369
|
+
-------
|
|
370
|
+
Self
|
|
371
|
+
Counted list
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
Example:
|
|
375
|
+
--------
|
|
376
|
+
>>> test = ListExt([9, 9, 9])
|
|
377
|
+
>>> test.numbering()
|
|
378
|
+
[(0, 9), (1, 9), (2, 9)]
|
|
379
|
+
"""
|
|
380
|
+
nums = count(start=start, step=step)
|
|
381
|
+
# return self.__class__(enumerate(self, start=start))
|
|
382
|
+
return self.__class__(zip(nums, self))
|
|
383
|
+
|
|
384
|
+
@versionadded("5.3.0") # no test case yet
|
|
385
|
+
def transpose(self, fillvalue: Any | None = None, /) -> Self:
|
|
386
|
+
"""
|
|
387
|
+
Transpose a list of iterable.
|
|
388
|
+
|
|
389
|
+
Parameters
|
|
390
|
+
----------
|
|
391
|
+
fillvalue : Any, optional
|
|
392
|
+
A fill value, by default ``None``
|
|
393
|
+
|
|
394
|
+
Returns
|
|
395
|
+
-------
|
|
396
|
+
Self | list[list[T]]
|
|
397
|
+
Transposed list.
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
Example:
|
|
401
|
+
--------
|
|
402
|
+
>>> ListExt([1, 1, 1, 1]).transpose()
|
|
403
|
+
[(1, 1, 1, 1)]
|
|
404
|
+
|
|
405
|
+
>>> ListExt([[1, 1, 1, 1], [1, 1, 1, 1]]).transpose()
|
|
406
|
+
[(1, 1), (1, 1), (1, 1), (1, 1)]
|
|
407
|
+
|
|
408
|
+
>>> ListExt([[1, 1, 1, 1], [1, 1, 1, 1], [1]]).transpose()
|
|
409
|
+
[(1, 1, 1), (1, 1, None), (1, 1, None), (1, 1, None)]
|
|
410
|
+
"""
|
|
411
|
+
try:
|
|
412
|
+
return self.__class__(zip_longest(*self, fillvalue=fillvalue)).apply(list)
|
|
413
|
+
except TypeError: # Dimension of 1
|
|
414
|
+
mod_dat = self.apply(lambda x: [x])
|
|
415
|
+
# return self.__class__(zip_longest(*mod_dat, fillvalue=fillvalue)).apply(list)
|
|
416
|
+
return mod_dat
|
|
243
417
|
|
|
418
|
+
# Random
|
|
244
419
|
def pick_one(self) -> Any:
|
|
245
420
|
"""
|
|
246
421
|
Pick one random items from ``list``
|
|
@@ -272,8 +447,7 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
272
447
|
Parameters
|
|
273
448
|
----------
|
|
274
449
|
number_of_items : int
|
|
275
|
-
|
|
276
|
-
| (Default: ``5``)
|
|
450
|
+
Number random of items, by default ``5``
|
|
277
451
|
|
|
278
452
|
Returns
|
|
279
453
|
-------
|
|
@@ -282,13 +456,20 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
282
456
|
"""
|
|
283
457
|
return [self.pick_one() for _ in range(number_of_items)]
|
|
284
458
|
|
|
459
|
+
@versionadded("5.10.0")
|
|
460
|
+
def shuffle(self) -> Self:
|
|
461
|
+
random.shuffle(self)
|
|
462
|
+
return self
|
|
463
|
+
|
|
464
|
+
# Len
|
|
465
|
+
@versionchanged("5.2.0", reason="Handle more type")
|
|
285
466
|
def len_items(self) -> Self:
|
|
286
467
|
"""
|
|
287
|
-
``len()`` for every item in ``
|
|
468
|
+
``len()`` for every item in ``self``
|
|
288
469
|
|
|
289
470
|
Returns
|
|
290
471
|
-------
|
|
291
|
-
|
|
472
|
+
Self
|
|
292
473
|
List of ``len()``'ed value
|
|
293
474
|
|
|
294
475
|
|
|
@@ -298,14 +479,24 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
298
479
|
>>> test.len_items()
|
|
299
480
|
[3, 3, 5]
|
|
300
481
|
"""
|
|
301
|
-
out = self.__class__([len(str(x)) for x in self])
|
|
302
|
-
# out = ListExt(map(lambda x: len(str(x)), self))
|
|
303
|
-
# logger.debug(out)
|
|
304
|
-
return out
|
|
305
482
|
|
|
306
|
-
|
|
483
|
+
def _len(item: Any) -> int:
|
|
484
|
+
try:
|
|
485
|
+
return len(item)
|
|
486
|
+
except TypeError:
|
|
487
|
+
return len(str(item))
|
|
488
|
+
|
|
489
|
+
return self.__class__([_len(x) for x in self])
|
|
490
|
+
|
|
491
|
+
@versionchanged("5.2.0", reason="New ``recursive`` parameter")
|
|
492
|
+
def mean_len(self, recursive: bool = False) -> float:
|
|
307
493
|
"""
|
|
308
|
-
Average length of every item
|
|
494
|
+
Average length of every item. Returns zero if failed (empty list, ZeroDivisionError).
|
|
495
|
+
|
|
496
|
+
Parameters
|
|
497
|
+
----------
|
|
498
|
+
recursive : bool
|
|
499
|
+
Recursively find the average length of items in nested lists, by default ``False``
|
|
309
500
|
|
|
310
501
|
Returns
|
|
311
502
|
-------
|
|
@@ -319,41 +510,55 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
319
510
|
>>> test.mean_len()
|
|
320
511
|
3.6666666666666665
|
|
321
512
|
"""
|
|
322
|
-
out = sum(self.len_items()) / len(self)
|
|
323
|
-
# logger.debug(out)
|
|
324
|
-
return out
|
|
325
513
|
|
|
326
|
-
|
|
514
|
+
if recursive:
|
|
515
|
+
dat = self.flatten(recursive=recursive)
|
|
516
|
+
else:
|
|
517
|
+
dat = self
|
|
518
|
+
|
|
519
|
+
try:
|
|
520
|
+
return sum(dat.len_items()) / len(dat)
|
|
521
|
+
except ZeroDivisionError:
|
|
522
|
+
return 0.0
|
|
523
|
+
|
|
524
|
+
@versionadded("5.2.0")
|
|
525
|
+
def max_item_len(self, recursive: bool = False) -> int:
|
|
327
526
|
"""
|
|
328
|
-
|
|
527
|
+
Find the maximum length of items in the list.
|
|
329
528
|
|
|
330
529
|
Parameters
|
|
331
530
|
----------
|
|
332
|
-
|
|
333
|
-
|
|
531
|
+
recursive : bool
|
|
532
|
+
Recursively find the maximum length of items in nested lists, by default ``False``
|
|
334
533
|
|
|
335
534
|
Returns
|
|
336
535
|
-------
|
|
337
|
-
|
|
338
|
-
|
|
536
|
+
int
|
|
537
|
+
Maximum length of items
|
|
339
538
|
|
|
340
539
|
|
|
341
540
|
Example:
|
|
342
541
|
--------
|
|
343
|
-
>>> test = ListExt([
|
|
344
|
-
>>> test.
|
|
345
|
-
|
|
542
|
+
>>> test = ListExt(["test", "longer_test"])
|
|
543
|
+
>>> test.max_item_len()
|
|
544
|
+
11
|
|
545
|
+
|
|
546
|
+
>>> test = ListExt([["short"], ["longer_test"]])
|
|
547
|
+
>>> test.max_item_len(recursive=True)
|
|
548
|
+
11
|
|
346
549
|
"""
|
|
347
|
-
|
|
348
|
-
|
|
550
|
+
if recursive:
|
|
551
|
+
return cast(int, max(self.flatten(recursive=True).len_items()))
|
|
552
|
+
return cast(int, max(self.len_items()))
|
|
349
553
|
|
|
554
|
+
# Group/Unique
|
|
350
555
|
def unique(self) -> Self:
|
|
351
556
|
"""
|
|
352
557
|
Remove duplicates
|
|
353
558
|
|
|
354
559
|
Returns
|
|
355
560
|
-------
|
|
356
|
-
|
|
561
|
+
Self
|
|
357
562
|
Duplicates removed list
|
|
358
563
|
|
|
359
564
|
|
|
@@ -371,7 +576,7 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
371
576
|
|
|
372
577
|
Returns
|
|
373
578
|
-------
|
|
374
|
-
|
|
579
|
+
Self
|
|
375
580
|
Grouped value
|
|
376
581
|
|
|
377
582
|
|
|
@@ -423,7 +628,7 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
423
628
|
iter = self.copy()
|
|
424
629
|
|
|
425
630
|
# Init loop
|
|
426
|
-
for _ in range(
|
|
631
|
+
for _ in range(max(max_loop, 3)):
|
|
427
632
|
temp: dict[Any, list] = {}
|
|
428
633
|
# Make dict{key: all `item` that contains `key`}
|
|
429
634
|
for item in iter:
|
|
@@ -434,75 +639,712 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
434
639
|
temp[x].append(item)
|
|
435
640
|
|
|
436
641
|
# Flatten dict.values
|
|
437
|
-
for k, v in temp.items()
|
|
438
|
-
temp[k] = list(set(chain(*v)))
|
|
642
|
+
temp = {k: list(set(chain(*v))) for k, v in temp.items()}
|
|
439
643
|
|
|
440
644
|
iter = list(temp.values())
|
|
441
645
|
|
|
442
646
|
return list(x for x, _ in groupby(iter))
|
|
443
647
|
|
|
444
|
-
|
|
648
|
+
@versionadded("5.5.0")
|
|
649
|
+
def split_equal(self, n: int, sort: bool = True) -> Self:
|
|
445
650
|
"""
|
|
446
|
-
|
|
651
|
+
Try to equally split a list of number into ``n`` equal sum parts.
|
|
652
|
+
|
|
653
|
+
**Note:** Element in list must be a number.
|
|
654
|
+
|
|
655
|
+
Parameters
|
|
656
|
+
----------
|
|
657
|
+
n : int
|
|
658
|
+
Split into how many parts. Must be >= 1.
|
|
659
|
+
|
|
660
|
+
sort : bool, optional
|
|
661
|
+
Sort the instance before split, by default ``True``
|
|
447
662
|
|
|
448
663
|
Returns
|
|
449
664
|
-------
|
|
450
|
-
|
|
451
|
-
|
|
665
|
+
Self
|
|
666
|
+
Splitted (list[list])
|
|
452
667
|
|
|
453
668
|
|
|
454
669
|
Example:
|
|
455
670
|
--------
|
|
456
|
-
>>>
|
|
457
|
-
|
|
458
|
-
['test', 'test', 'test', 'test']
|
|
671
|
+
>>> ListExt(range(1, 11)).split_equal(2)
|
|
672
|
+
[[10, 7, 5, 4, 1], [9, 8, 6, 3, 2]]
|
|
459
673
|
"""
|
|
460
|
-
try:
|
|
461
|
-
# return self.__class__(sum(self, start=[]))
|
|
462
|
-
return self.__class__(chain(*self))
|
|
463
|
-
except Exception:
|
|
464
|
-
temp = list(map(lambda x: x if isinstance(x, list) else [x], self))
|
|
465
|
-
return self.__class__(chain(*temp))
|
|
466
674
|
|
|
467
|
-
|
|
675
|
+
# https://stackoverflow.com/a/61649667
|
|
676
|
+
bins: list[list[int]] = [[0] for _ in range(max(n, 1))]
|
|
677
|
+
if sort:
|
|
678
|
+
# self = self.sorts(reverse=True)
|
|
679
|
+
self = self.__class__(sorted(self, reverse=True))
|
|
680
|
+
for x in self:
|
|
681
|
+
least = bins[0]
|
|
682
|
+
least[0] += x
|
|
683
|
+
least.append(x)
|
|
684
|
+
heapreplace(bins, least)
|
|
685
|
+
return self.__class__(x[1:] for x in bins)
|
|
686
|
+
|
|
687
|
+
# Slicing
|
|
688
|
+
def slice_points(self, points: list[int]) -> Self:
|
|
468
689
|
"""
|
|
469
|
-
|
|
470
|
-
|
|
690
|
+
Splits a list into sublists based on specified split points (indices).
|
|
691
|
+
|
|
692
|
+
This method divides the original list into multiple sublists. The ``points``
|
|
693
|
+
argument provides the indices at which the list should be split. The resulting
|
|
694
|
+
list of lists contains the sublists created by these splits. The original
|
|
695
|
+
list is not modified.
|
|
471
696
|
|
|
472
697
|
Parameters
|
|
473
698
|
----------
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
699
|
+
points : list
|
|
700
|
+
A list of integer indices representing the points at which to split
|
|
701
|
+
the list. These indices are *exclusive* of the starting sublist
|
|
702
|
+
but *inclusive* of the ending sublist.
|
|
477
703
|
|
|
478
704
|
Returns
|
|
479
705
|
-------
|
|
480
|
-
|
|
481
|
-
|
|
706
|
+
Self | list[list]
|
|
707
|
+
A list of lists, where each inner list is a slice of the original list
|
|
708
|
+
defined by the provided split points.
|
|
482
709
|
|
|
483
710
|
|
|
484
711
|
Example:
|
|
485
712
|
--------
|
|
486
|
-
>>> test = ListExt([
|
|
487
|
-
>>> test.
|
|
488
|
-
[
|
|
713
|
+
>>> test = ListExt([1, 1, 2, 3, 3, 4, 5, 6])
|
|
714
|
+
>>> test.slice_points([2, 5])
|
|
715
|
+
[[1, 1], [2, 3, 3], [4, 5, 6]]
|
|
716
|
+
>>> test.slice_points([0, 1, 2, 3, 4, 5, 6, 7, 8])
|
|
717
|
+
[[], [1], [1], [2], [3], [3], [4], [5], [6]]
|
|
718
|
+
>>> test.slice_points([])
|
|
719
|
+
[[1, 1, 2, 3, 3, 4, 5, 6]]
|
|
720
|
+
"""
|
|
721
|
+
points.append(len(self))
|
|
722
|
+
data = self.copy()
|
|
723
|
+
# return [data[points[i]:points[i+1]] for i in range(len(points)-1)]
|
|
724
|
+
return self.__class__(data[i1:i2] for i1, i2 in zip([0] + points[:-1], points))
|
|
725
|
+
|
|
726
|
+
@versionadded("5.1.0")
|
|
727
|
+
def split_chunk(self, chunk_size: int, /) -> Self:
|
|
728
|
+
"""
|
|
729
|
+
Split list into smaller chunks
|
|
730
|
+
|
|
731
|
+
Parameters
|
|
732
|
+
----------
|
|
733
|
+
chunk_size : int
|
|
734
|
+
Chunk size, minimum: ``1``
|
|
735
|
+
|
|
736
|
+
Returns
|
|
737
|
+
-------
|
|
738
|
+
Self | list[list]
|
|
739
|
+
List of chunk
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
Example:
|
|
743
|
+
--------
|
|
744
|
+
>>> ListExt([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]).split_chunk(5)
|
|
745
|
+
[[1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1]]
|
|
489
746
|
"""
|
|
490
|
-
|
|
491
|
-
return self.
|
|
747
|
+
slice_points = list(range(0, len(self), max(chunk_size, 1)))[1:]
|
|
748
|
+
return self.slice_points(slice_points)
|
|
749
|
+
|
|
750
|
+
@versionadded("5.3.0") # no test case yet
|
|
751
|
+
def to_column(self, ncols: int, fillvalue: Any | None = None) -> Self:
|
|
752
|
+
"""
|
|
753
|
+
Smart convert 1 dimension list to 2 dimension list,
|
|
754
|
+
in which, number of columns = ``ncols``.
|
|
755
|
+
|
|
756
|
+
Parameters
|
|
757
|
+
----------
|
|
758
|
+
ncols : int
|
|
759
|
+
Number of columns
|
|
760
|
+
|
|
761
|
+
fillvalue : Any | None, optional
|
|
762
|
+
Fill value, by default ``None``
|
|
763
|
+
|
|
764
|
+
Returns
|
|
765
|
+
-------
|
|
766
|
+
Self
|
|
767
|
+
Coulumned list.
|
|
768
|
+
|
|
492
769
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
770
|
+
Example:
|
|
771
|
+
--------
|
|
772
|
+
>>> ins = ListExt(range(1, 20))
|
|
773
|
+
|
|
774
|
+
>>> # Normal split chunk
|
|
775
|
+
>>> ins.split_chunk(10)
|
|
776
|
+
[
|
|
777
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
|
778
|
+
[11, 12, 13, 14, 15, 16, 17, 18, 19]
|
|
779
|
+
]
|
|
780
|
+
|
|
781
|
+
>>> # Column split chunk
|
|
782
|
+
>>> ins.to_column(10)
|
|
783
|
+
[
|
|
784
|
+
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19],
|
|
785
|
+
[2, 4, 6, 8, 10, 12, 14, 16, 18, None]
|
|
786
|
+
]
|
|
496
787
|
"""
|
|
497
|
-
|
|
788
|
+
num_of_col = max(ncols, 1)
|
|
789
|
+
len_cols = len(self.split_chunk(num_of_col))
|
|
790
|
+
return self.split_chunk(len_cols).transpose(fillvalue)
|
|
791
|
+
|
|
792
|
+
@overload
|
|
793
|
+
def wrap_to_column(self, width: int, /) -> Self: ...
|
|
794
|
+
|
|
795
|
+
@overload
|
|
796
|
+
def wrap_to_column(
|
|
797
|
+
self,
|
|
798
|
+
width: int,
|
|
799
|
+
/,
|
|
800
|
+
*,
|
|
801
|
+
margin: int = 4,
|
|
802
|
+
sep: str = "",
|
|
803
|
+
fill: str = " ",
|
|
804
|
+
transpose: bool = False,
|
|
805
|
+
) -> Self: ...
|
|
806
|
+
|
|
807
|
+
@versionchanged("5.3.0", reason="New `sep`, `fill`, `transpose` parameters")
|
|
808
|
+
@versionadded("5.2.0") # no test case yet
|
|
809
|
+
def wrap_to_column(
|
|
810
|
+
self,
|
|
811
|
+
width: int,
|
|
812
|
+
/,
|
|
813
|
+
*,
|
|
814
|
+
margin: int = 4,
|
|
815
|
+
sep: str = "",
|
|
816
|
+
fill: str = " ",
|
|
817
|
+
transpose: bool = False,
|
|
818
|
+
) -> Self:
|
|
498
819
|
"""
|
|
499
|
-
|
|
820
|
+
Arrange list[str] items into aligned text columns (for printing)
|
|
821
|
+
with automatic column count calculation.
|
|
822
|
+
|
|
823
|
+
Parameters
|
|
824
|
+
----------
|
|
825
|
+
width : int
|
|
826
|
+
Total available display width (must be >= ``margin``)
|
|
827
|
+
|
|
828
|
+
margin : int, optional
|
|
829
|
+
Reserved space for borders/padding, should be an even number, by default ``4``
|
|
830
|
+
|
|
831
|
+
sep : str, optional
|
|
832
|
+
Separator between each element, by default ``""``
|
|
833
|
+
|
|
834
|
+
fill : str, optional
|
|
835
|
+
Fill character for spacing, must have the length of 1, by default ``" "``
|
|
836
|
+
|
|
837
|
+
transpose : bool, optional
|
|
838
|
+
Smart transpose the columns, by default ``False``
|
|
839
|
+
|
|
840
|
+
Returns
|
|
841
|
+
-------
|
|
842
|
+
Self
|
|
843
|
+
New instance type[list[str]] with formatted column strings.
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
Example:
|
|
847
|
+
--------
|
|
848
|
+
>>> items = ListExt(["apple", "banana", "cherry", "date"])
|
|
849
|
+
>>> print(items.wrap_to_column(30))
|
|
850
|
+
['apple banana cherry ', 'date ']
|
|
500
851
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
def _numbering(iterable: list, start: int = 0) -> list[tuple[int, Any]]:
|
|
852
|
+
>>> items.wrap_to_column(15)
|
|
853
|
+
['apple ', 'banana ', 'cherry ', 'date ']
|
|
504
854
|
"""
|
|
505
|
-
|
|
855
|
+
|
|
856
|
+
max_item_length = self.max_item_len() + max(len(sep), 1)
|
|
857
|
+
available_width = max(width, 4) - max(margin, 0) # Set boundary
|
|
858
|
+
if len(fill) != 1:
|
|
859
|
+
fill = " "
|
|
860
|
+
|
|
861
|
+
# Calculate how many columns of text
|
|
862
|
+
column_count = max(1, available_width // max_item_length) if max_item_length > 0 else 1
|
|
863
|
+
|
|
864
|
+
# splitted_chunk: list[list[str]] = self.split_chunk(cols)
|
|
865
|
+
# mod_chunk = self.__class__(
|
|
866
|
+
# [[x.ljust(max_name_len, " ") for x in chunk] for chunk in splitted_chunk]
|
|
867
|
+
# ).apply(lambda x: "".join(x))
|
|
868
|
+
|
|
869
|
+
def mod_item(item: list[str]) -> str:
|
|
870
|
+
# Set width for str item and join them together
|
|
871
|
+
return sep.join(x.ljust(max_item_length, fill) for x in item)
|
|
872
|
+
|
|
873
|
+
if transpose:
|
|
874
|
+
mod_chunk = self.to_column(column_count, fillvalue="").apply(mod_item)
|
|
875
|
+
else:
|
|
876
|
+
mod_chunk = self.split_chunk(column_count).apply(mod_item)
|
|
877
|
+
|
|
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]:
|
|
506
891
|
"""
|
|
507
|
-
|
|
508
|
-
|
|
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))
|