absfuyu 5.1.0__py3-none-any.whl → 5.3.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.
- absfuyu/__init__.py +1 -1
- absfuyu/__main__.py +3 -3
- absfuyu/cli/__init__.py +1 -1
- absfuyu/cli/color.py +3 -3
- absfuyu/cli/config_group.py +2 -2
- absfuyu/cli/do_group.py +12 -2
- absfuyu/cli/game_group.py +2 -2
- absfuyu/cli/tool_group.py +2 -3
- absfuyu/config/__init__.py +1 -1
- absfuyu/core/__init__.py +1 -1
- absfuyu/core/baseclass.py +32 -2
- absfuyu/core/baseclass2.py +1 -1
- absfuyu/core/decorator.py +4 -4
- absfuyu/core/docstring.py +43 -25
- absfuyu/core/dummy_cli.py +1 -1
- absfuyu/core/dummy_func.py +4 -4
- absfuyu/dxt/__init__.py +1 -1
- absfuyu/dxt/dictext.py +5 -2
- absfuyu/dxt/dxt_support.py +1 -1
- absfuyu/dxt/intext.py +5 -2
- absfuyu/dxt/listext.py +405 -127
- absfuyu/dxt/strext.py +75 -15
- absfuyu/extra/__init__.py +1 -1
- absfuyu/extra/beautiful.py +1 -1
- absfuyu/extra/da/__init__.py +1 -1
- absfuyu/extra/da/dadf.py +56 -4
- absfuyu/extra/da/dadf_base.py +1 -1
- absfuyu/extra/da/df_func.py +1 -1
- absfuyu/extra/da/mplt.py +1 -1
- absfuyu/extra/data_analysis.py +3 -3
- absfuyu/fun/__init__.py +1 -1
- absfuyu/fun/tarot.py +1 -1
- absfuyu/game/__init__.py +1 -1
- absfuyu/game/game_stat.py +1 -1
- absfuyu/game/sudoku.py +1 -1
- absfuyu/game/tictactoe.py +2 -3
- absfuyu/game/wordle.py +1 -1
- absfuyu/general/__init__.py +1 -1
- absfuyu/general/content.py +2 -2
- absfuyu/general/human.py +1 -1
- absfuyu/general/shape.py +1 -1
- absfuyu/logger.py +1 -1
- absfuyu/pkg_data/__init__.py +1 -1
- absfuyu/pkg_data/deprecated.py +1 -1
- absfuyu/sort.py +1 -1
- absfuyu/tools/__init__.py +16 -13
- absfuyu/tools/checksum.py +2 -2
- absfuyu/tools/converter.py +29 -8
- absfuyu/tools/generator.py +251 -110
- absfuyu/tools/inspector.py +84 -40
- absfuyu/tools/keygen.py +1 -1
- absfuyu/tools/obfuscator.py +2 -2
- absfuyu/tools/passwordlib.py +3 -4
- absfuyu/tools/shutdownizer.py +1 -1
- absfuyu/tools/web.py +1 -1
- absfuyu/typings.py +136 -0
- absfuyu/util/__init__.py +18 -4
- absfuyu/util/api.py +36 -16
- absfuyu/util/json_method.py +43 -14
- absfuyu/util/lunar.py +1 -1
- absfuyu/util/path.py +158 -4
- absfuyu/util/performance.py +120 -5
- absfuyu/util/shorten_number.py +1 -1
- absfuyu/util/text_table.py +235 -45
- absfuyu/util/zipped.py +4 -3
- absfuyu/version.py +2 -2
- {absfuyu-5.1.0.dist-info → absfuyu-5.3.0.dist-info}/METADATA +1 -1
- absfuyu-5.3.0.dist-info/RECORD +76 -0
- absfuyu/core/typings.py +0 -40
- absfuyu-5.1.0.dist-info/RECORD +0 -76
- {absfuyu-5.1.0.dist-info → absfuyu-5.3.0.dist-info}/WHEEL +0 -0
- {absfuyu-5.1.0.dist-info → absfuyu-5.3.0.dist-info}/entry_points.txt +0 -0
- {absfuyu-5.1.0.dist-info → absfuyu-5.3.0.dist-info}/licenses/LICENSE +0 -0
absfuyu/dxt/listext.py
CHANGED
|
@@ -3,8 +3,8 @@ Absfuyu: Data Extension
|
|
|
3
3
|
-----------------------
|
|
4
4
|
list extension
|
|
5
5
|
|
|
6
|
-
Version: 5.
|
|
7
|
-
Date updated:
|
|
6
|
+
Version: 5.2.0
|
|
7
|
+
Date updated: 15/03/2025 (dd/mm/yyyy)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
# Module Package
|
|
@@ -17,13 +17,14 @@ __all__ = ["ListExt"]
|
|
|
17
17
|
import operator
|
|
18
18
|
import random
|
|
19
19
|
from collections import Counter
|
|
20
|
-
from collections.abc import Callable
|
|
21
|
-
from itertools import accumulate, chain, groupby
|
|
22
|
-
from typing import Any, Self
|
|
20
|
+
from collections.abc import Callable, Iterable
|
|
21
|
+
from itertools import accumulate, chain, groupby, zip_longest
|
|
22
|
+
from typing import Any, Literal, Self, cast, overload
|
|
23
23
|
|
|
24
|
-
from absfuyu.core import ShowAllMethodsMixin
|
|
25
|
-
from absfuyu.core.docstring import versionadded
|
|
26
|
-
from absfuyu.
|
|
24
|
+
from absfuyu.core.baseclass import ShowAllMethodsMixin
|
|
25
|
+
from absfuyu.core.docstring import deprecated, versionadded, versionchanged
|
|
26
|
+
from absfuyu.typings import T as _T
|
|
27
|
+
from absfuyu.util import set_min_max
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
# Class
|
|
@@ -31,15 +32,20 @@ from absfuyu.util import set_min, set_min_max
|
|
|
31
32
|
class ListExt(ShowAllMethodsMixin, list):
|
|
32
33
|
"""
|
|
33
34
|
``list`` extension
|
|
35
|
+
|
|
36
|
+
>>> # For a list of new methods
|
|
37
|
+
>>> ListExt.show_all_methods()
|
|
34
38
|
"""
|
|
35
39
|
|
|
40
|
+
# Deprecated
|
|
41
|
+
@deprecated("5.2.0", reason="Use ListExt.apply(str) instead")
|
|
36
42
|
def stringify(self) -> Self:
|
|
37
43
|
"""
|
|
38
44
|
Convert all item in ``list`` into string
|
|
39
45
|
|
|
40
46
|
Returns
|
|
41
47
|
-------
|
|
42
|
-
|
|
48
|
+
Self
|
|
43
49
|
A list with all items with type <str`>
|
|
44
50
|
|
|
45
51
|
|
|
@@ -51,6 +57,7 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
51
57
|
"""
|
|
52
58
|
return self.__class__(map(str, self))
|
|
53
59
|
|
|
60
|
+
# Info
|
|
54
61
|
def head(self, number_of_items: int = 5) -> list:
|
|
55
62
|
"""
|
|
56
63
|
Show first ``number_of_items`` items in ``ListExt``
|
|
@@ -91,6 +98,31 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
91
98
|
)
|
|
92
99
|
return self[::-1][:number_of_items][::-1]
|
|
93
100
|
|
|
101
|
+
# Misc
|
|
102
|
+
def apply(self, func: Callable[[Any], Any]) -> Self:
|
|
103
|
+
"""
|
|
104
|
+
Apply function to each entry
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
func : Callable
|
|
109
|
+
Callable function
|
|
110
|
+
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
Self
|
|
114
|
+
ListExt
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
--------
|
|
119
|
+
>>> test = ListExt([1, 2, 3])
|
|
120
|
+
>>> test.apply(str)
|
|
121
|
+
['1', '2', '3']
|
|
122
|
+
"""
|
|
123
|
+
# return self.__class__(map(func, self))
|
|
124
|
+
return self.__class__(func(x) for x in self)
|
|
125
|
+
|
|
94
126
|
def sorts(self, reverse: bool = False) -> Self:
|
|
95
127
|
"""
|
|
96
128
|
Sort all items (with different type) in ``list``
|
|
@@ -98,13 +130,12 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
98
130
|
Parameters
|
|
99
131
|
----------
|
|
100
132
|
reverse : bool
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
| (Default: ``False``)
|
|
133
|
+
- ``True`` then sort in descending order
|
|
134
|
+
- ``False`` then sort in ascending order (default value)
|
|
104
135
|
|
|
105
136
|
Returns
|
|
106
137
|
-------
|
|
107
|
-
|
|
138
|
+
Self
|
|
108
139
|
A sorted list
|
|
109
140
|
|
|
110
141
|
|
|
@@ -128,6 +159,25 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
128
159
|
# logger.debug(output)
|
|
129
160
|
return self.__class__(output)
|
|
130
161
|
|
|
162
|
+
@overload
|
|
163
|
+
def freq(self) -> dict: ...
|
|
164
|
+
|
|
165
|
+
@overload
|
|
166
|
+
def freq(
|
|
167
|
+
self,
|
|
168
|
+
sort: bool = False,
|
|
169
|
+
num_of_first_char: int | None = None,
|
|
170
|
+
appear_increment: Literal[False] = ...,
|
|
171
|
+
) -> dict: ...
|
|
172
|
+
|
|
173
|
+
@overload
|
|
174
|
+
def freq(
|
|
175
|
+
self,
|
|
176
|
+
sort: bool = False,
|
|
177
|
+
num_of_first_char: int | None = None,
|
|
178
|
+
appear_increment: Literal[True] = ...,
|
|
179
|
+
) -> list[int]: ...
|
|
180
|
+
|
|
131
181
|
def freq(
|
|
132
182
|
self,
|
|
133
183
|
sort: bool = False,
|
|
@@ -139,18 +189,17 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
139
189
|
|
|
140
190
|
Parameters
|
|
141
191
|
----------
|
|
142
|
-
sort : bool
|
|
143
|
-
|
|
144
|
-
|
|
192
|
+
sort : bool, optional
|
|
193
|
+
- ``True``: Sorts the output in ascending order
|
|
194
|
+
- ``False``: No sort (default value)
|
|
145
195
|
|
|
146
|
-
num_of_first_char : int | None
|
|
147
|
-
| Number of first character taken into account to sort
|
|
148
|
-
|
|
|
149
|
-
| (num_of_first_char = ``1``: first character in each item)
|
|
196
|
+
num_of_first_char : int | None, optional
|
|
197
|
+
| Number of first character taken into account to sort, by default ``None``
|
|
198
|
+
| Eg: ``num_of_first_char = 1``: first character of each item
|
|
150
199
|
|
|
151
|
-
appear_increment : bool
|
|
152
|
-
|
|
153
|
-
|
|
200
|
+
appear_increment : bool, optional
|
|
201
|
+
Returns incremental index list of each item when sort,
|
|
202
|
+
by default ``False``
|
|
154
203
|
|
|
155
204
|
Returns
|
|
156
205
|
-------
|
|
@@ -171,7 +220,6 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
171
220
|
>>> test.freq(appear_increment=True)
|
|
172
221
|
[2, 3, 5, 6, 7, 8]
|
|
173
222
|
"""
|
|
174
|
-
|
|
175
223
|
if sort:
|
|
176
224
|
data = self.sorts().copy()
|
|
177
225
|
else:
|
|
@@ -191,7 +239,7 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
191
239
|
|
|
192
240
|
try:
|
|
193
241
|
times_appear = dict(sorted(temp.items()))
|
|
194
|
-
except
|
|
242
|
+
except TypeError:
|
|
195
243
|
times_appear = dict(self.__class__(temp.items()).sorts())
|
|
196
244
|
# logger.debug(times_appear)
|
|
197
245
|
|
|
@@ -204,44 +252,144 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
204
252
|
else:
|
|
205
253
|
return times_appear # character frequency
|
|
206
254
|
|
|
207
|
-
|
|
255
|
+
@versionchanged("5.2.0", reason="New ``recursive`` parameter")
|
|
256
|
+
def flatten(self, recursive: bool = False) -> Self:
|
|
208
257
|
"""
|
|
209
|
-
|
|
258
|
+
Flatten the list
|
|
210
259
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
260
|
+
Parameters
|
|
261
|
+
----------
|
|
262
|
+
recursive : bool
|
|
263
|
+
Recursively flatten the list, by default ``False``
|
|
264
|
+
|
|
265
|
+
Returns
|
|
266
|
+
-------
|
|
267
|
+
Self
|
|
268
|
+
Flattened list
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
Example:
|
|
272
|
+
--------
|
|
273
|
+
>>> test = ListExt([["test"], ["test", "test"], ["test"]])
|
|
274
|
+
>>> test.flatten()
|
|
275
|
+
['test', 'test', 'test', 'test']
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
def instance_checking(item):
|
|
279
|
+
if isinstance(item, str):
|
|
280
|
+
return [item]
|
|
281
|
+
if isinstance(item, Iterable):
|
|
282
|
+
return item
|
|
283
|
+
return [item]
|
|
284
|
+
|
|
285
|
+
# Flatten
|
|
286
|
+
list_of_list = (instance_checking(x) for x in self)
|
|
287
|
+
flattened = self.__class__(chain(*list_of_list))
|
|
288
|
+
|
|
289
|
+
# Recursive
|
|
290
|
+
if recursive:
|
|
291
|
+
|
|
292
|
+
def _condition(item) -> bool:
|
|
293
|
+
if isinstance(item, str):
|
|
294
|
+
return False
|
|
295
|
+
return isinstance(item, Iterable)
|
|
296
|
+
|
|
297
|
+
while any(flattened.apply(_condition)):
|
|
298
|
+
flattened = flattened.flatten()
|
|
299
|
+
|
|
300
|
+
# Return
|
|
301
|
+
return flattened
|
|
302
|
+
|
|
303
|
+
@versionadded("5.2.0")
|
|
304
|
+
def join(self, sep: str = " ", /) -> str:
|
|
305
|
+
"""
|
|
306
|
+
Join every element in list to str (``str.join()`` wrapper).
|
|
215
307
|
|
|
216
308
|
Parameters
|
|
217
309
|
----------
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
the list. These indices are *exclusive* of the starting sublist
|
|
221
|
-
but *inclusive* of the ending sublist.
|
|
310
|
+
sep : str, optional
|
|
311
|
+
Separator between each element, by default ``" "``
|
|
222
312
|
|
|
223
313
|
Returns
|
|
224
314
|
-------
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
defined by the provided split points.
|
|
315
|
+
str
|
|
316
|
+
Joined list
|
|
228
317
|
|
|
229
318
|
|
|
230
319
|
Example:
|
|
231
320
|
--------
|
|
232
|
-
>>>
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
>>>
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
[[1, 1, 2, 3, 3, 4, 5, 6]]
|
|
321
|
+
>>> ListExt(["a", "b", "c"]).join()
|
|
322
|
+
a b c
|
|
323
|
+
|
|
324
|
+
>>> # Also work with non-str type
|
|
325
|
+
>>> ListExt([1, 2, 3]).join()
|
|
326
|
+
1 2 3
|
|
239
327
|
"""
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
328
|
+
try:
|
|
329
|
+
return sep.join(self)
|
|
330
|
+
except TypeError:
|
|
331
|
+
return sep.join(self.apply(str))
|
|
332
|
+
|
|
333
|
+
def numbering(self, start: int = 0) -> Self:
|
|
334
|
+
"""
|
|
335
|
+
Number the item in list
|
|
336
|
+
(``enumerate`` wrapper)
|
|
337
|
+
|
|
338
|
+
Parameters
|
|
339
|
+
----------
|
|
340
|
+
start : int
|
|
341
|
+
Start from which number, by default ``0``
|
|
342
|
+
|
|
343
|
+
Returns
|
|
344
|
+
-------
|
|
345
|
+
Self
|
|
346
|
+
Counted list
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
Example:
|
|
350
|
+
--------
|
|
351
|
+
>>> test = ListExt([9, 9, 9])
|
|
352
|
+
>>> test.numbering()
|
|
353
|
+
[(0, 9), (1, 9), (2, 9)]
|
|
354
|
+
"""
|
|
355
|
+
start = max(start, 0)
|
|
356
|
+
return self.__class__(enumerate(self, start=start))
|
|
357
|
+
|
|
358
|
+
@versionadded("5.3.0") # no test case yet
|
|
359
|
+
def transpose(self, fillvalue: _T | None = None, /) -> Self:
|
|
360
|
+
"""
|
|
361
|
+
Transpose a list of iterable.
|
|
362
|
+
|
|
363
|
+
Parameters
|
|
364
|
+
----------
|
|
365
|
+
fillvalue : Any, optional
|
|
366
|
+
A fill value, by default ``None``
|
|
367
|
+
|
|
368
|
+
Returns
|
|
369
|
+
-------
|
|
370
|
+
Self | list[list[T]]
|
|
371
|
+
Transposed list.
|
|
244
372
|
|
|
373
|
+
|
|
374
|
+
Example:
|
|
375
|
+
--------
|
|
376
|
+
>>> ListExt([1, 1, 1, 1]).transpose()
|
|
377
|
+
[(1, 1, 1, 1)]
|
|
378
|
+
|
|
379
|
+
>>> ListExt([[1, 1, 1, 1], [1, 1, 1, 1]]).transpose()
|
|
380
|
+
[(1, 1), (1, 1), (1, 1), (1, 1)]
|
|
381
|
+
|
|
382
|
+
>>> ListExt([[1, 1, 1, 1], [1, 1, 1, 1], [1]]).transpose()
|
|
383
|
+
[(1, 1, 1), (1, 1, None), (1, 1, None), (1, 1, None)]
|
|
384
|
+
"""
|
|
385
|
+
try:
|
|
386
|
+
return self.__class__(zip_longest(*self, fillvalue=fillvalue)).apply(list)
|
|
387
|
+
except TypeError: # Dimension of 1
|
|
388
|
+
mod_dat = self.apply(lambda x: [x])
|
|
389
|
+
# return self.__class__(zip_longest(*mod_dat, fillvalue=fillvalue)).apply(list)
|
|
390
|
+
return mod_dat
|
|
391
|
+
|
|
392
|
+
# Random
|
|
245
393
|
def pick_one(self) -> Any:
|
|
246
394
|
"""
|
|
247
395
|
Pick one random items from ``list``
|
|
@@ -273,8 +421,7 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
273
421
|
Parameters
|
|
274
422
|
----------
|
|
275
423
|
number_of_items : int
|
|
276
|
-
|
|
277
|
-
| (Default: ``5``)
|
|
424
|
+
Number random of items, by default ``5``
|
|
278
425
|
|
|
279
426
|
Returns
|
|
280
427
|
-------
|
|
@@ -283,13 +430,15 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
283
430
|
"""
|
|
284
431
|
return [self.pick_one() for _ in range(number_of_items)]
|
|
285
432
|
|
|
433
|
+
# Len
|
|
434
|
+
@versionchanged("5.2.0", reason="Handle more type")
|
|
286
435
|
def len_items(self) -> Self:
|
|
287
436
|
"""
|
|
288
|
-
``len()`` for every item in ``
|
|
437
|
+
``len()`` for every item in ``self``
|
|
289
438
|
|
|
290
439
|
Returns
|
|
291
440
|
-------
|
|
292
|
-
|
|
441
|
+
Self
|
|
293
442
|
List of ``len()``'ed value
|
|
294
443
|
|
|
295
444
|
|
|
@@ -299,14 +448,24 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
299
448
|
>>> test.len_items()
|
|
300
449
|
[3, 3, 5]
|
|
301
450
|
"""
|
|
302
|
-
out = self.__class__([len(str(x)) for x in self])
|
|
303
|
-
# out = ListExt(map(lambda x: len(str(x)), self))
|
|
304
|
-
# logger.debug(out)
|
|
305
|
-
return out
|
|
306
451
|
|
|
307
|
-
|
|
452
|
+
def _len(item: Any) -> int:
|
|
453
|
+
try:
|
|
454
|
+
return len(item)
|
|
455
|
+
except TypeError:
|
|
456
|
+
return len(str(item))
|
|
457
|
+
|
|
458
|
+
return self.__class__([_len(x) for x in self])
|
|
459
|
+
|
|
460
|
+
@versionchanged("5.2.0", reason="New ``recursive`` parameter")
|
|
461
|
+
def mean_len(self, recursive: bool = False) -> float:
|
|
308
462
|
"""
|
|
309
|
-
Average length of every item
|
|
463
|
+
Average length of every item. Returns zero if failed (empty list, ZeroDivisionError).
|
|
464
|
+
|
|
465
|
+
Parameters
|
|
466
|
+
----------
|
|
467
|
+
recursive : bool
|
|
468
|
+
Recursively find the average length of items in nested lists, by default ``False``
|
|
310
469
|
|
|
311
470
|
Returns
|
|
312
471
|
-------
|
|
@@ -320,41 +479,55 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
320
479
|
>>> test.mean_len()
|
|
321
480
|
3.6666666666666665
|
|
322
481
|
"""
|
|
323
|
-
out = sum(self.len_items()) / len(self)
|
|
324
|
-
# logger.debug(out)
|
|
325
|
-
return out
|
|
326
482
|
|
|
327
|
-
|
|
483
|
+
if recursive:
|
|
484
|
+
dat = self.flatten(recursive=recursive)
|
|
485
|
+
else:
|
|
486
|
+
dat = self
|
|
487
|
+
|
|
488
|
+
try:
|
|
489
|
+
return sum(dat.len_items()) / len(dat)
|
|
490
|
+
except ZeroDivisionError:
|
|
491
|
+
return 0.0
|
|
492
|
+
|
|
493
|
+
@versionadded("5.2.0")
|
|
494
|
+
def max_item_len(self, recursive: bool = False) -> int:
|
|
328
495
|
"""
|
|
329
|
-
|
|
496
|
+
Find the maximum length of items in the list.
|
|
330
497
|
|
|
331
498
|
Parameters
|
|
332
499
|
----------
|
|
333
|
-
|
|
334
|
-
|
|
500
|
+
recursive : bool
|
|
501
|
+
Recursively find the maximum length of items in nested lists, by default ``False``
|
|
335
502
|
|
|
336
503
|
Returns
|
|
337
504
|
-------
|
|
338
|
-
|
|
339
|
-
|
|
505
|
+
int
|
|
506
|
+
Maximum length of items
|
|
340
507
|
|
|
341
508
|
|
|
342
509
|
Example:
|
|
343
510
|
--------
|
|
344
|
-
>>> test = ListExt([
|
|
345
|
-
>>> test.
|
|
346
|
-
|
|
511
|
+
>>> test = ListExt(["test", "longer_test"])
|
|
512
|
+
>>> test.max_item_len()
|
|
513
|
+
11
|
|
514
|
+
|
|
515
|
+
>>> test = ListExt([["short"], ["longer_test"]])
|
|
516
|
+
>>> test.max_item_len(recursive=True)
|
|
517
|
+
11
|
|
347
518
|
"""
|
|
348
|
-
|
|
349
|
-
|
|
519
|
+
if recursive:
|
|
520
|
+
return cast(int, max(self.flatten(recursive=True).len_items()))
|
|
521
|
+
return cast(int, max(self.len_items()))
|
|
350
522
|
|
|
523
|
+
# Group/Unique
|
|
351
524
|
def unique(self) -> Self:
|
|
352
525
|
"""
|
|
353
526
|
Remove duplicates
|
|
354
527
|
|
|
355
528
|
Returns
|
|
356
529
|
-------
|
|
357
|
-
|
|
530
|
+
Self
|
|
358
531
|
Duplicates removed list
|
|
359
532
|
|
|
360
533
|
|
|
@@ -372,7 +545,7 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
372
545
|
|
|
373
546
|
Returns
|
|
374
547
|
-------
|
|
375
|
-
|
|
548
|
+
Self
|
|
376
549
|
Grouped value
|
|
377
550
|
|
|
378
551
|
|
|
@@ -424,7 +597,7 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
424
597
|
iter = self.copy()
|
|
425
598
|
|
|
426
599
|
# Init loop
|
|
427
|
-
for _ in range(
|
|
600
|
+
for _ in range(max(max_loop, 3)):
|
|
428
601
|
temp: dict[Any, list] = {}
|
|
429
602
|
# Make dict{key: all `item` that contains `key`}
|
|
430
603
|
for item in iter:
|
|
@@ -442,57 +615,47 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
442
615
|
|
|
443
616
|
return list(x for x, _ in groupby(iter))
|
|
444
617
|
|
|
445
|
-
|
|
618
|
+
# Slicing
|
|
619
|
+
def slice_points(self, points: list[int]) -> Self:
|
|
446
620
|
"""
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
Returns
|
|
450
|
-
-------
|
|
451
|
-
ListExt
|
|
452
|
-
Flattened list
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
Example:
|
|
456
|
-
--------
|
|
457
|
-
>>> test = ListExt([["test"], ["test", "test"], ["test"]])
|
|
458
|
-
>>> test.flatten()
|
|
459
|
-
['test', 'test', 'test', 'test']
|
|
460
|
-
"""
|
|
461
|
-
try:
|
|
462
|
-
# return self.__class__(sum(self, start=[]))
|
|
463
|
-
return self.__class__(chain(*self))
|
|
464
|
-
except Exception:
|
|
465
|
-
temp = list(map(lambda x: x if isinstance(x, list) else [x], self))
|
|
466
|
-
return self.__class__(chain(*temp))
|
|
621
|
+
Splits a list into sublists based on specified split points (indices).
|
|
467
622
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
623
|
+
This method divides the original list into multiple sublists. The ``points``
|
|
624
|
+
argument provides the indices at which the list should be split. The resulting
|
|
625
|
+
list of lists contains the sublists created by these splits. The original
|
|
626
|
+
list is not modified.
|
|
472
627
|
|
|
473
628
|
Parameters
|
|
474
629
|
----------
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
630
|
+
points : list
|
|
631
|
+
A list of integer indices representing the points at which to split
|
|
632
|
+
the list. These indices are *exclusive* of the starting sublist
|
|
633
|
+
but *inclusive* of the ending sublist.
|
|
478
634
|
|
|
479
635
|
Returns
|
|
480
636
|
-------
|
|
481
|
-
|
|
482
|
-
|
|
637
|
+
Self | list[list]
|
|
638
|
+
A list of lists, where each inner list is a slice of the original list
|
|
639
|
+
defined by the provided split points.
|
|
483
640
|
|
|
484
641
|
|
|
485
642
|
Example:
|
|
486
643
|
--------
|
|
487
|
-
>>> test = ListExt([
|
|
488
|
-
>>> test.
|
|
489
|
-
[
|
|
644
|
+
>>> test = ListExt([1, 1, 2, 3, 3, 4, 5, 6])
|
|
645
|
+
>>> test.slice_points([2, 5])
|
|
646
|
+
[[1, 1], [2, 3, 3], [4, 5, 6]]
|
|
647
|
+
>>> test.slice_points([0, 1, 2, 3, 4, 5, 6, 7, 8])
|
|
648
|
+
[[], [1], [1], [2], [3], [3], [4], [5], [6]]
|
|
649
|
+
>>> test.slice_points([])
|
|
650
|
+
[[1, 1, 2, 3, 3, 4, 5, 6]]
|
|
490
651
|
"""
|
|
491
|
-
|
|
492
|
-
|
|
652
|
+
points.append(len(self))
|
|
653
|
+
data = self.copy()
|
|
654
|
+
# return [data[points[i]:points[i+1]] for i in range(len(points)-1)]
|
|
655
|
+
return self.__class__(data[i1:i2] for i1, i2 in zip([0] + points[:-1], points))
|
|
493
656
|
|
|
494
|
-
@versionadded("5.1.0")
|
|
495
|
-
def split_chunk(self, chunk_size: int) ->
|
|
657
|
+
@versionadded("5.1.0")
|
|
658
|
+
def split_chunk(self, chunk_size: int, /) -> Self:
|
|
496
659
|
"""
|
|
497
660
|
Split list into smaller chunks
|
|
498
661
|
|
|
@@ -503,7 +666,7 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
503
666
|
|
|
504
667
|
Returns
|
|
505
668
|
-------
|
|
506
|
-
list[list]
|
|
669
|
+
Self | list[list]
|
|
507
670
|
List of chunk
|
|
508
671
|
|
|
509
672
|
|
|
@@ -515,19 +678,134 @@ class ListExt(ShowAllMethodsMixin, list):
|
|
|
515
678
|
slice_points = list(range(0, len(self), max(chunk_size, 1)))[1:]
|
|
516
679
|
return self.slice_points(slice_points)
|
|
517
680
|
|
|
518
|
-
@
|
|
519
|
-
|
|
520
|
-
def _group_by_unique(iterable: list) -> list[list]:
|
|
681
|
+
@versionadded("5.3.0") # no test case yet
|
|
682
|
+
def to_column(self, ncols: int, fillvalue: _T | None = None) -> Self:
|
|
521
683
|
"""
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
return list([list(g) for _, g in groupby(iterable)])
|
|
684
|
+
Smart convert 1 dimension list to 2 dimension list,
|
|
685
|
+
in which, number of columns = ``ncols``.
|
|
525
686
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
687
|
+
Parameters
|
|
688
|
+
----------
|
|
689
|
+
ncols : int
|
|
690
|
+
Number of columns
|
|
691
|
+
|
|
692
|
+
fillvalue : T | None, optional
|
|
693
|
+
Fill value, by default ``None``
|
|
694
|
+
|
|
695
|
+
Returns
|
|
696
|
+
-------
|
|
697
|
+
Self
|
|
698
|
+
Coulumned list.
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
Example:
|
|
702
|
+
--------
|
|
703
|
+
>>> ins = ListExt(range(1,20))
|
|
704
|
+
|
|
705
|
+
>>> # Normal split chunk
|
|
706
|
+
>>> ins.split_chunk(10)
|
|
707
|
+
[
|
|
708
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
|
709
|
+
[11, 12, 13, 14, 15, 16, 17, 18, 19]
|
|
710
|
+
]
|
|
711
|
+
|
|
712
|
+
>>> # Column split chunk
|
|
713
|
+
>>> ins.to_column(10)
|
|
714
|
+
[
|
|
715
|
+
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19],
|
|
716
|
+
[2, 4, 6, 8, 10, 12, 14, 16, 18, None]
|
|
717
|
+
]
|
|
718
|
+
"""
|
|
719
|
+
num_of_col = max(ncols, 1)
|
|
720
|
+
len_cols = len(self.split_chunk(num_of_col))
|
|
721
|
+
return self.split_chunk(len_cols).transpose(fillvalue)
|
|
722
|
+
|
|
723
|
+
@overload
|
|
724
|
+
def wrap_to_column(self, width: int, /) -> Self: ...
|
|
725
|
+
|
|
726
|
+
@overload
|
|
727
|
+
def wrap_to_column(
|
|
728
|
+
self,
|
|
729
|
+
width: int,
|
|
730
|
+
/,
|
|
731
|
+
*,
|
|
732
|
+
margin: int = 4,
|
|
733
|
+
sep: str = "",
|
|
734
|
+
fill: str = " ",
|
|
735
|
+
transpose: bool = False,
|
|
736
|
+
) -> Self: ...
|
|
737
|
+
|
|
738
|
+
@versionchanged("5.3.0", reason="New `sep`, `fill`, `transpose` parameters")
|
|
739
|
+
@versionadded("5.2.0") # no test case yet
|
|
740
|
+
def wrap_to_column(
|
|
741
|
+
self,
|
|
742
|
+
width: int,
|
|
743
|
+
/,
|
|
744
|
+
*,
|
|
745
|
+
margin: int = 4,
|
|
746
|
+
sep: str = "",
|
|
747
|
+
fill: str = " ",
|
|
748
|
+
transpose: bool = False,
|
|
749
|
+
) -> Self:
|
|
750
|
+
"""
|
|
751
|
+
Arrange list[str] items into aligned text columns (for printing)
|
|
752
|
+
with automatic column count calculation.
|
|
753
|
+
|
|
754
|
+
Parameters
|
|
755
|
+
----------
|
|
756
|
+
width : int
|
|
757
|
+
Total available display width (must be >= ``margin``)
|
|
758
|
+
|
|
759
|
+
margin : int, optional
|
|
760
|
+
Reserved space for borders/padding, should be an even number, by default ``4``
|
|
761
|
+
|
|
762
|
+
sep : str, optional
|
|
763
|
+
Separator between each element, by default ``""``
|
|
764
|
+
|
|
765
|
+
fill : str, optional
|
|
766
|
+
Fill character for spacing, must have the length of 1, by default ``" "``
|
|
767
|
+
|
|
768
|
+
transpose : bool, optional
|
|
769
|
+
Smart transpose the columns, by default ``False``
|
|
770
|
+
|
|
771
|
+
Returns
|
|
772
|
+
-------
|
|
773
|
+
Self
|
|
774
|
+
New instance type[list[str]] with formatted column strings.
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
Example:
|
|
778
|
+
--------
|
|
779
|
+
>>> items = ListExt(["apple", "banana", "cherry", "date"])
|
|
780
|
+
>>> print(items.wrap_to_column(30))
|
|
781
|
+
['apple banana cherry ', 'date ']
|
|
782
|
+
|
|
783
|
+
>>> items.wrap_to_column(15)
|
|
784
|
+
['apple ', 'banana ', 'cherry ', 'date ']
|
|
531
785
|
"""
|
|
532
|
-
|
|
533
|
-
|
|
786
|
+
|
|
787
|
+
max_item_length = self.max_item_len() + max(len(sep), 1)
|
|
788
|
+
available_width = max(width, 4) - max(margin, 0) # Set boundary
|
|
789
|
+
if len(fill) != 1:
|
|
790
|
+
fill = " "
|
|
791
|
+
|
|
792
|
+
# Calculate how many columns of text
|
|
793
|
+
column_count = (
|
|
794
|
+
max(1, available_width // max_item_length) if max_item_length > 0 else 1
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
# splitted_chunk: list[list[str]] = self.split_chunk(cols)
|
|
798
|
+
# mod_chunk = self.__class__(
|
|
799
|
+
# [[x.ljust(max_name_len, " ") for x in chunk] for chunk in splitted_chunk]
|
|
800
|
+
# ).apply(lambda x: "".join(x))
|
|
801
|
+
|
|
802
|
+
def mod_item(item: list[str]) -> str:
|
|
803
|
+
# Set width for str item and join them together
|
|
804
|
+
return sep.join(x.ljust(max_item_length, fill) for x in item)
|
|
805
|
+
|
|
806
|
+
if transpose:
|
|
807
|
+
mod_chunk = self.to_column(column_count, fillvalue="").apply(mod_item)
|
|
808
|
+
else:
|
|
809
|
+
mod_chunk = self.split_chunk(column_count).apply(mod_item)
|
|
810
|
+
|
|
811
|
+
return mod_chunk
|