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.

Files changed (103) hide show
  1. absfuyu/__init__.py +5 -3
  2. absfuyu/__main__.py +3 -3
  3. absfuyu/cli/__init__.py +13 -2
  4. absfuyu/cli/audio_group.py +98 -0
  5. absfuyu/cli/color.py +30 -14
  6. absfuyu/cli/config_group.py +9 -2
  7. absfuyu/cli/do_group.py +23 -6
  8. absfuyu/cli/game_group.py +27 -2
  9. absfuyu/cli/tool_group.py +81 -11
  10. absfuyu/config/__init__.py +3 -3
  11. absfuyu/core/__init__.py +12 -8
  12. absfuyu/core/baseclass.py +929 -96
  13. absfuyu/core/baseclass2.py +44 -3
  14. absfuyu/core/decorator.py +70 -4
  15. absfuyu/core/docstring.py +64 -41
  16. absfuyu/core/dummy_cli.py +3 -3
  17. absfuyu/core/dummy_func.py +19 -6
  18. absfuyu/dxt/__init__.py +2 -2
  19. absfuyu/dxt/base_type.py +93 -0
  20. absfuyu/dxt/dictext.py +204 -16
  21. absfuyu/dxt/dxt_support.py +2 -2
  22. absfuyu/dxt/intext.py +151 -34
  23. absfuyu/dxt/listext.py +969 -127
  24. absfuyu/dxt/strext.py +77 -17
  25. absfuyu/extra/__init__.py +2 -2
  26. absfuyu/extra/audio/__init__.py +8 -0
  27. absfuyu/extra/audio/_util.py +57 -0
  28. absfuyu/extra/audio/convert.py +192 -0
  29. absfuyu/extra/audio/lossless.py +281 -0
  30. absfuyu/extra/beautiful.py +3 -2
  31. absfuyu/extra/da/__init__.py +72 -0
  32. absfuyu/extra/da/dadf.py +1600 -0
  33. absfuyu/extra/da/dadf_base.py +186 -0
  34. absfuyu/extra/da/df_func.py +181 -0
  35. absfuyu/extra/da/mplt.py +219 -0
  36. absfuyu/extra/ggapi/__init__.py +8 -0
  37. absfuyu/extra/ggapi/gdrive.py +223 -0
  38. absfuyu/extra/ggapi/glicense.py +148 -0
  39. absfuyu/extra/ggapi/glicense_df.py +186 -0
  40. absfuyu/extra/ggapi/gsheet.py +88 -0
  41. absfuyu/extra/img/__init__.py +30 -0
  42. absfuyu/extra/img/converter.py +402 -0
  43. absfuyu/extra/img/dup_check.py +291 -0
  44. absfuyu/extra/pdf.py +87 -0
  45. absfuyu/extra/rclone.py +253 -0
  46. absfuyu/extra/xml.py +90 -0
  47. absfuyu/fun/__init__.py +7 -20
  48. absfuyu/fun/rubik.py +442 -0
  49. absfuyu/fun/tarot.py +2 -2
  50. absfuyu/game/__init__.py +2 -2
  51. absfuyu/game/game_stat.py +2 -2
  52. absfuyu/game/schulte.py +78 -0
  53. absfuyu/game/sudoku.py +2 -2
  54. absfuyu/game/tictactoe.py +2 -3
  55. absfuyu/game/wordle.py +6 -4
  56. absfuyu/general/__init__.py +4 -4
  57. absfuyu/general/content.py +4 -4
  58. absfuyu/general/human.py +2 -2
  59. absfuyu/general/resrel.py +213 -0
  60. absfuyu/general/shape.py +3 -8
  61. absfuyu/general/tax.py +344 -0
  62. absfuyu/logger.py +806 -59
  63. absfuyu/numbers/__init__.py +13 -0
  64. absfuyu/numbers/number_to_word.py +321 -0
  65. absfuyu/numbers/shorten_number.py +303 -0
  66. absfuyu/numbers/time_duration.py +217 -0
  67. absfuyu/pkg_data/__init__.py +2 -2
  68. absfuyu/pkg_data/deprecated.py +2 -2
  69. absfuyu/pkg_data/logo.py +1462 -0
  70. absfuyu/sort.py +4 -4
  71. absfuyu/tools/__init__.py +28 -2
  72. absfuyu/tools/checksum.py +144 -9
  73. absfuyu/tools/converter.py +120 -34
  74. absfuyu/tools/generator.py +461 -0
  75. absfuyu/tools/inspector.py +752 -0
  76. absfuyu/tools/keygen.py +2 -2
  77. absfuyu/tools/obfuscator.py +47 -9
  78. absfuyu/tools/passwordlib.py +89 -25
  79. absfuyu/tools/shutdownizer.py +3 -8
  80. absfuyu/tools/sw.py +718 -0
  81. absfuyu/tools/web.py +10 -13
  82. absfuyu/typings.py +138 -0
  83. absfuyu/util/__init__.py +114 -6
  84. absfuyu/util/api.py +41 -18
  85. absfuyu/util/cli.py +119 -0
  86. absfuyu/util/gui.py +91 -0
  87. absfuyu/util/json_method.py +43 -14
  88. absfuyu/util/lunar.py +2 -2
  89. absfuyu/util/package.py +124 -0
  90. absfuyu/util/path.py +702 -82
  91. absfuyu/util/performance.py +122 -7
  92. absfuyu/util/shorten_number.py +244 -21
  93. absfuyu/util/text_table.py +481 -0
  94. absfuyu/util/zipped.py +8 -7
  95. absfuyu/version.py +79 -59
  96. {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/METADATA +52 -11
  97. absfuyu-6.1.2.dist-info/RECORD +105 -0
  98. {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/WHEEL +1 -1
  99. absfuyu/extra/data_analysis.py +0 -1078
  100. absfuyu/general/generator.py +0 -303
  101. absfuyu-5.0.0.dist-info/RECORD +0 -68
  102. {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/entry_points.txt +0 -0
  103. {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: 5.0.0
7
- Date updated: 11/02/2025 (dd/mm/yyyy)
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 itertools import accumulate, chain, groupby
22
- from typing import Any, Self
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 ShowAllMethodsMixin, deprecated
25
- from absfuyu.util import set_min, set_min_max
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(ShowAllMethodsMixin, list):
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
- ListExt
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
- | if ``True`` then sort in descending order
101
- | if ``False`` then sort in ascending order
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
- ListExt
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
- | if ``True``: Sorts the output in ascending order
143
- | if ``False``: No sort
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
- | (Default: ``None``)
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
- | return incremental index list of each item when sort
152
- | (Default: ``False``)
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 Exception:
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
- def slice_points(self, points: list[int]) -> list[list]:
264
+ @versionchanged("5.2.0", reason="New ``recursive`` parameter")
265
+ def flatten(self, recursive: bool = False) -> Self:
207
266
  """
208
- Splits a list into sublists based on specified split points (indices).
267
+ Flatten the list
209
268
 
210
- This method divides the original list into multiple sublists. The ``points``
211
- argument provides the indices at which the list should be split. The resulting
212
- list of lists contains the sublists created by these splits. The original
213
- list is not modified.
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
- points : list
218
- A list of integer indices representing the points at which to split
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
- list[list]
225
- A list of lists, where each inner list is a slice of the original list
226
- defined by the provided split points.
324
+ str
325
+ Joined list
227
326
 
228
327
 
229
328
  Example:
230
329
  --------
231
- >>> test = ListExt([1, 1, 2, 3, 3, 4, 5, 6])
232
- >>> test.slice_points([2, 5])
233
- [[1, 1], [2, 3, 3], [4, 5, 6]]
234
- >>> test.slice_points([0, 1, 2, 3, 4, 5, 6, 7, 8])
235
- [[], [1], [1], [2], [3], [3], [4], [5], [6]]
236
- >>> test.slice_points([])
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
- points.append(len(self))
240
- data = self.copy()
241
- # return [data[points[i]:points[i+1]] for i in range(len(points)-1)]
242
- return [data[i1:i2] for i1, i2 in zip([0] + points[:-1], points)]
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
- | Number random of items
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 ``list[str]``
468
+ ``len()`` for every item in ``self``
288
469
 
289
470
  Returns
290
471
  -------
291
- ListExt
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
- def mean_len(self) -> float:
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
- def apply(self, func: Callable) -> Self:
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
- Apply function to each entry
527
+ Find the maximum length of items in the list.
329
528
 
330
529
  Parameters
331
530
  ----------
332
- func : Callable
333
- Callable function
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
- ListExt
338
- ListExt
536
+ int
537
+ Maximum length of items
339
538
 
340
539
 
341
540
  Example:
342
541
  --------
343
- >>> test = ListExt([1, 2, 3])
344
- >>> test.apply(str)
345
- ['1', '2', '3']
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
- # return __class__(func(x) for x in self)
348
- return self.__class__(map(func, self))
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
- ListExt
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
- ListExt
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(int(set_min(max_loop, min_value=3))):
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
- def flatten(self) -> Self:
648
+ @versionadded("5.5.0")
649
+ def split_equal(self, n: int, sort: bool = True) -> Self:
445
650
  """
446
- Flatten the list
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
- ListExt
451
- Flattened list
665
+ Self
666
+ Splitted (list[list])
452
667
 
453
668
 
454
669
  Example:
455
670
  --------
456
- >>> test = ListExt([["test"], ["test", "test"], ["test"]])
457
- >>> test.flatten()
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
- def numbering(self, start: int = 0) -> Self:
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
- Number the item in list
470
- (``enumerate`` wrapper)
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
- start : int
475
- Start from which number
476
- (Default: ``0``)
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
- ListExt
481
- Counted list
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([9, 9, 9])
487
- >>> test.numbering()
488
- [(0, 9), (1, 9), (2, 9)]
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
- start = int(set_min(start, min_value=0))
491
- return self.__class__(enumerate(self, start=start))
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
- @staticmethod
494
- @deprecated("5.0.0")
495
- def _group_by_unique(iterable: list) -> list[list]:
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
- Static method for ``group_by_unique``
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
- return list([list(g) for _, g in groupby(iterable)])
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
- @staticmethod
502
- @deprecated("5.0.0")
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
- Static method for ``numbering``
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
- start = int(set_min(start, min_value=0))
508
- return list(enumerate(iterable, start=start))
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))