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.

Files changed (73) hide show
  1. absfuyu/__init__.py +1 -1
  2. absfuyu/__main__.py +3 -3
  3. absfuyu/cli/__init__.py +1 -1
  4. absfuyu/cli/color.py +3 -3
  5. absfuyu/cli/config_group.py +2 -2
  6. absfuyu/cli/do_group.py +12 -2
  7. absfuyu/cli/game_group.py +2 -2
  8. absfuyu/cli/tool_group.py +2 -3
  9. absfuyu/config/__init__.py +1 -1
  10. absfuyu/core/__init__.py +1 -1
  11. absfuyu/core/baseclass.py +32 -2
  12. absfuyu/core/baseclass2.py +1 -1
  13. absfuyu/core/decorator.py +4 -4
  14. absfuyu/core/docstring.py +43 -25
  15. absfuyu/core/dummy_cli.py +1 -1
  16. absfuyu/core/dummy_func.py +4 -4
  17. absfuyu/dxt/__init__.py +1 -1
  18. absfuyu/dxt/dictext.py +5 -2
  19. absfuyu/dxt/dxt_support.py +1 -1
  20. absfuyu/dxt/intext.py +5 -2
  21. absfuyu/dxt/listext.py +405 -127
  22. absfuyu/dxt/strext.py +75 -15
  23. absfuyu/extra/__init__.py +1 -1
  24. absfuyu/extra/beautiful.py +1 -1
  25. absfuyu/extra/da/__init__.py +1 -1
  26. absfuyu/extra/da/dadf.py +56 -4
  27. absfuyu/extra/da/dadf_base.py +1 -1
  28. absfuyu/extra/da/df_func.py +1 -1
  29. absfuyu/extra/da/mplt.py +1 -1
  30. absfuyu/extra/data_analysis.py +3 -3
  31. absfuyu/fun/__init__.py +1 -1
  32. absfuyu/fun/tarot.py +1 -1
  33. absfuyu/game/__init__.py +1 -1
  34. absfuyu/game/game_stat.py +1 -1
  35. absfuyu/game/sudoku.py +1 -1
  36. absfuyu/game/tictactoe.py +2 -3
  37. absfuyu/game/wordle.py +1 -1
  38. absfuyu/general/__init__.py +1 -1
  39. absfuyu/general/content.py +2 -2
  40. absfuyu/general/human.py +1 -1
  41. absfuyu/general/shape.py +1 -1
  42. absfuyu/logger.py +1 -1
  43. absfuyu/pkg_data/__init__.py +1 -1
  44. absfuyu/pkg_data/deprecated.py +1 -1
  45. absfuyu/sort.py +1 -1
  46. absfuyu/tools/__init__.py +16 -13
  47. absfuyu/tools/checksum.py +2 -2
  48. absfuyu/tools/converter.py +29 -8
  49. absfuyu/tools/generator.py +251 -110
  50. absfuyu/tools/inspector.py +84 -40
  51. absfuyu/tools/keygen.py +1 -1
  52. absfuyu/tools/obfuscator.py +2 -2
  53. absfuyu/tools/passwordlib.py +3 -4
  54. absfuyu/tools/shutdownizer.py +1 -1
  55. absfuyu/tools/web.py +1 -1
  56. absfuyu/typings.py +136 -0
  57. absfuyu/util/__init__.py +18 -4
  58. absfuyu/util/api.py +36 -16
  59. absfuyu/util/json_method.py +43 -14
  60. absfuyu/util/lunar.py +1 -1
  61. absfuyu/util/path.py +158 -4
  62. absfuyu/util/performance.py +120 -5
  63. absfuyu/util/shorten_number.py +1 -1
  64. absfuyu/util/text_table.py +235 -45
  65. absfuyu/util/zipped.py +4 -3
  66. absfuyu/version.py +2 -2
  67. {absfuyu-5.1.0.dist-info → absfuyu-5.3.0.dist-info}/METADATA +1 -1
  68. absfuyu-5.3.0.dist-info/RECORD +76 -0
  69. absfuyu/core/typings.py +0 -40
  70. absfuyu-5.1.0.dist-info/RECORD +0 -76
  71. {absfuyu-5.1.0.dist-info → absfuyu-5.3.0.dist-info}/WHEEL +0 -0
  72. {absfuyu-5.1.0.dist-info → absfuyu-5.3.0.dist-info}/entry_points.txt +0 -0
  73. {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.1.0
7
- Date updated: 10/03/2025 (dd/mm/yyyy)
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, deprecated
25
- from absfuyu.core.docstring import versionadded
26
- from absfuyu.util import set_min, set_min_max
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
- ListExt
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
- | if ``True`` then sort in descending order
102
- | if ``False`` then sort in ascending order
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
- ListExt
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
- | if ``True``: Sorts the output in ascending order
144
- | if ``False``: No sort
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
- | (Default: ``None``)
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
- | return incremental index list of each item when sort
153
- | (Default: ``False``)
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 Exception:
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
- def slice_points(self, points: list[int]) -> list[list]:
255
+ @versionchanged("5.2.0", reason="New ``recursive`` parameter")
256
+ def flatten(self, recursive: bool = False) -> Self:
208
257
  """
209
- Splits a list into sublists based on specified split points (indices).
258
+ Flatten the list
210
259
 
211
- This method divides the original list into multiple sublists. The ``points``
212
- argument provides the indices at which the list should be split. The resulting
213
- list of lists contains the sublists created by these splits. The original
214
- list is not modified.
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
- points : list
219
- A list of integer indices representing the points at which to split
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
- list[list]
226
- A list of lists, where each inner list is a slice of the original list
227
- defined by the provided split points.
315
+ str
316
+ Joined list
228
317
 
229
318
 
230
319
  Example:
231
320
  --------
232
- >>> test = ListExt([1, 1, 2, 3, 3, 4, 5, 6])
233
- >>> test.slice_points([2, 5])
234
- [[1, 1], [2, 3, 3], [4, 5, 6]]
235
- >>> test.slice_points([0, 1, 2, 3, 4, 5, 6, 7, 8])
236
- [[], [1], [1], [2], [3], [3], [4], [5], [6]]
237
- >>> test.slice_points([])
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
- points.append(len(self))
241
- data = self.copy()
242
- # return [data[points[i]:points[i+1]] for i in range(len(points)-1)]
243
- return [data[i1:i2] for i1, i2 in zip([0] + points[:-1], points)]
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
- | Number random of items
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 ``list[str]``
437
+ ``len()`` for every item in ``self``
289
438
 
290
439
  Returns
291
440
  -------
292
- ListExt
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
- def mean_len(self) -> float:
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
- def apply(self, func: Callable[[Any], Any]) -> Self:
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
- Apply function to each entry
496
+ Find the maximum length of items in the list.
330
497
 
331
498
  Parameters
332
499
  ----------
333
- func : Callable
334
- Callable function
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
- ListExt
339
- ListExt
505
+ int
506
+ Maximum length of items
340
507
 
341
508
 
342
509
  Example:
343
510
  --------
344
- >>> test = ListExt([1, 2, 3])
345
- >>> test.apply(str)
346
- ['1', '2', '3']
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
- # return __class__(func(x) for x in self)
349
- return self.__class__(map(func, self))
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
- ListExt
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
- ListExt
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(int(set_min(max_loop, min_value=3))):
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
- def flatten(self) -> Self:
618
+ # Slicing
619
+ def slice_points(self, points: list[int]) -> Self:
446
620
  """
447
- Flatten the list
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
- def numbering(self, start: int = 0) -> Self:
469
- """
470
- Number the item in list
471
- (``enumerate`` wrapper)
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
- start : int
476
- Start from which number
477
- (Default: ``0``)
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
- ListExt
482
- Counted list
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([9, 9, 9])
488
- >>> test.numbering()
489
- [(0, 9), (1, 9), (2, 9)]
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
- start = int(set_min(start, min_value=0))
492
- return self.__class__(enumerate(self, start=start))
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") # no test case yet
495
- def split_chunk(self, chunk_size: int) -> list[list]:
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
- @staticmethod
519
- @deprecated("5.0.0")
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
- Static method for ``group_by_unique``
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
- @staticmethod
527
- @deprecated("5.0.0")
528
- def _numbering(iterable: list, start: int = 0) -> list[tuple[int, Any]]:
529
- """
530
- Static method for ``numbering``
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
- start = int(set_min(start, min_value=0))
533
- return list(enumerate(iterable, start=start))
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