Skip to content

Commit b5074c1

Browse files
committed
add run_length_encode()
1 parent e673d00 commit b5074c1

File tree

5 files changed

+134
-0
lines changed

5 files changed

+134
-0
lines changed

doc/api/more/types_linq.more.more_enumerable.rst

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,49 @@ Revisions:
459459

460460
----
461461

462+
instancemethod ``run_length_encode()``
463+
----------------------------------------
464+
465+
466+
Returns
467+
- ``MoreEnumerable[Tuple[TSource_co, int]]``
468+
469+
Run-length encodes the sequence into a sequence of tuples where each tuple contains an
470+
(the first) element and its number of contingent occurrences, where equality is based on
471+
`==`.
472+
473+
Example
474+
>>> MoreEnumerable('abbcaeeeaa').run_length_encode().to_list()
475+
[('a', 1), ('b', 2), ('c', 1), ('a', 1), ('e', 3), ('a', 2)]
476+
477+
Revisions:
478+
- main: New.
479+
480+
----
481+
482+
instancemethod ``run_length_encode(__comparer)``
483+
--------------------------------------------------
484+
485+
Parameters
486+
- `__comparer` (``Callable[[TSource_co, TSource_co], bool]``)
487+
488+
Returns
489+
- ``MoreEnumerable[Tuple[TSource_co, int]]``
490+
491+
Run-length encodes the sequence into a sequence of tuples where each tuple contains an
492+
(the first) element and its number of contingent occurrences, where equality is determined by
493+
the comparer.
494+
495+
Example
496+
>>> MoreEnumerable('abBBbcaEeeff') \
497+
>>> .run_length_encode(lambda x, y: x.lower() == y.lower()).to_list()
498+
[('a', 1), ('b', 4), ('c', 1), ('a', 1), ('E', 3), ('f', 2)]
499+
500+
Revisions:
501+
- main: New.
502+
503+
----
504+
462505
staticmethod ``traverse_breath_first[TSource](root, children_selector)``
463506
--------------------------------------------------------------------------
464507

doc/api_spec.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ class ClassSpec(TypedDict):
205205
'pipe',
206206
'rank',
207207
'rank_by',
208+
'run_length_encode',
208209
'traverse_breath_first',
209210
'traverse_depth_first',
210211
},

tests/test_more_usage.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,35 @@ def test_overload2_empty(self):
429429
.to_list() == []
430430

431431

432+
class TestRunLengthEncodeMethod:
433+
def test_overload1(self):
434+
en = MoreEnumerable('abbcaeeeaa')
435+
assert en.run_length_encode().to_list() \
436+
== [('a', 1), ('b', 2), ('c', 1), ('a', 1), ('e', 3), ('a', 2)]
437+
438+
def test_overload1_empty(self):
439+
en = MoreEnumerable(())
440+
assert en.run_length_encode().to_list() == []
441+
442+
def test_overload1_one_run(self):
443+
en = MoreEnumerable('AAAAA')
444+
assert en.run_length_encode().to_list() == [('A', 5)]
445+
446+
def test_overload1_one_elem(self):
447+
en = MoreEnumerable('A')
448+
assert en.run_length_encode().to_list() == [('A', 1)]
449+
450+
def test_overload1_no_run(self):
451+
en = MoreEnumerable('abcdefghijklmnopqrstuvwxyz')
452+
assert en.run_length_encode().to_list() == \
453+
en.select(lambda x: (x, 1)).to_list()
454+
455+
def test_overload2(self):
456+
en = MoreEnumerable('abBBbcaEeeff')
457+
assert en.run_length_encode(lambda x, y: x.lower() == y.lower()).to_list() \
458+
== [('a', 1), ('b', 4), ('c', 1), ('a', 1), ('E', 3), ('f', 2)]
459+
460+
432461
class TestCycleMethod:
433462
def test_repeat(self):
434463
en = MoreEnumerable([1, 2, 3])

types_linq/more/more_enumerable.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,34 @@ def inner():
192192
yield rank_map[key]
193193
return MoreEnumerable(inner)
194194

195+
def run_length_encode(self,
196+
*args: Callable[[TSource_co, TSource_co], bool],
197+
) -> MoreEnumerable[Tuple[TSource_co, int]]:
198+
if len(args) == 0:
199+
comparer = lambda x, y: x == y
200+
else:
201+
comparer = args[0]
202+
def inner():
203+
iterator = iter(self)
204+
try:
205+
prev_elem = next(iterator)
206+
except StopIteration:
207+
return
208+
count = 1
209+
while True:
210+
try:
211+
elem = next(iterator)
212+
except StopIteration:
213+
break
214+
if comparer(prev_elem, elem):
215+
count += 1
216+
else:
217+
yield (prev_elem, count)
218+
prev_elem = elem
219+
count = 1
220+
yield (prev_elem, count)
221+
return MoreEnumerable(inner)
222+
195223
@staticmethod
196224
def traverse_breath_first(
197225
root: TSource,

types_linq/more/more_enumerable.pyi

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,39 @@ class MoreEnumerable(Enumerable[TSource_co]):
317317
- v1.0.0: New.
318318
'''
319319

320+
@overload
321+
def run_length_encode(self) -> MoreEnumerable[Tuple[TSource_co, int]]:
322+
'''
323+
Run-length encodes the sequence into a sequence of tuples where each tuple contains an
324+
(the first) element and its number of contingent occurrences, where equality is based on
325+
`==`.
326+
327+
Example
328+
>>> MoreEnumerable('abbcaeeeaa').run_length_encode().to_list()
329+
[('a', 1), ('b', 2), ('c', 1), ('a', 1), ('e', 3), ('a', 2)]
330+
331+
Revisions:
332+
- main: New.
333+
'''
334+
335+
@overload
336+
def run_length_encode(self,
337+
__comparer: Callable[[TSource_co, TSource_co], bool],
338+
) -> MoreEnumerable[Tuple[TSource_co, int]]:
339+
'''
340+
Run-length encodes the sequence into a sequence of tuples where each tuple contains an
341+
(the first) element and its number of contingent occurrences, where equality is determined by
342+
the comparer.
343+
344+
Example
345+
>>> MoreEnumerable('abBBbcaEeeff') \\
346+
>>> .run_length_encode(lambda x, y: x.lower() == y.lower()).to_list()
347+
[('a', 1), ('b', 4), ('c', 1), ('a', 1), ('E', 3), ('f', 2)]
348+
349+
Revisions:
350+
- main: New.
351+
'''
352+
320353
@staticmethod
321354
def traverse_breath_first(
322355
root: TSource,

0 commit comments

Comments
 (0)