Skip to content

Commit abbe61c

Browse files
committed
add distinct_by(), except_by() to Enumerable class; removed distinct_by() from MoreEnumerable; renamed except_by() to except_by2() for MoreEnumerable
an essential breaking change
1 parent c13b55f commit abbe61c

File tree

9 files changed

+117
-59
lines changed

9 files changed

+117
-59
lines changed

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

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ MoreEnumerable provides more query methods. Instances of this class can be creat
1212
constructing, using as_more(), or invoking MoreEnumerable methods that return MoreEnumerable
1313
instead of Enumerable.
1414

15-
Some APIs are subject to change as updates to .NET (6) are happening - they will move to Enumerable
16-
class if one day they appear in the official .NET doc.
15+
These APIs may have breaking changes more frequently than those in Enumerable class because updates
16+
in .NET are happening and sometimes ones of these APIs could be moved to Enumerable with modification,
17+
or changed to accommodate changes to Enumerable.
1718

1819
Bases
1920
======
@@ -82,25 +83,6 @@ Returns the original MoreEnumerable reference.
8283

8384
----
8485

85-
instancemethod ``distinct_by(key_selector)``
86-
----------------------------------------------
87-
88-
Parameters
89-
- `key_selector` (``Callable[[TSource_co], object]``)
90-
91-
Returns
92-
- ``MoreEnumerable[TSource_co]``
93-
94-
Returns distinct elements from the sequence where "distinctness" is determined by the value
95-
returned by the selector.
96-
97-
Example
98-
>>> ints = [1, 4, 5, 6, 4, 3, 1, 99]
99-
>>> MoreEnumerable(ints).distinct_by(lambda x: x // 2).to_list()
100-
[1, 4, 6, 3, 99]
101-
102-
----
103-
10486
instancemethod ``enumerate(start=0)``
10587
---------------------------------------
10688

@@ -120,8 +102,8 @@ Example
120102

121103
----
122104

123-
instancemethod ``except_by(second, key_selector)``
124-
----------------------------------------------------
105+
instancemethod ``except_by2(second, key_selector)``
106+
-----------------------------------------------------
125107

126108
Parameters
127109
- `second` (``Iterable[TSource_co]``)
@@ -136,7 +118,7 @@ determines "distinctness". Note the second iterable is homogenous to self.
136118
Example
137119
>>> first = [(16, 'x'), (9, 'y'), (12, 'd'), (16, 't')]
138120
>>> second = [(24, 'd'), (77, 'y')]
139-
>>> MoreEnumerable(first).except_by(second, lambda x: x[1]).to_list()
121+
>>> MoreEnumerable(first).except_by2(second, lambda x: x[1]).to_list()
140122
[(16, 'x'), (16, 't')]
141123

142124
----

doc/api/types_linq.enumerable.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,25 @@ Example
680680

681681
----
682682

683+
instancemethod ``distinct_by(key_selector)``
684+
----------------------------------------------
685+
686+
Parameters
687+
- `key_selector` (``Callable[[TSource_co], object]``)
688+
689+
Returns
690+
- ``Enumerable[TSource_co]``
691+
692+
Returns distinct elements from the sequence where "distinctness" is determined by the value
693+
returned by the selector.
694+
695+
Example
696+
>>> ints = [1, 4, 5, 6, 4, 3, 1, 99]
697+
>>> Enumerable(ints).distinct_by(lambda x: x // 2).to_list()
698+
[1, 4, 6, 3, 99]
699+
700+
----
701+
683702
instancemethod ``element_at(index)``
684703
--------------------------------------
685704

@@ -777,6 +796,27 @@ Example
777796

778797
----
779798

799+
instancemethod ``except_by[TKey](second, key_selector)``
800+
----------------------------------------------------------
801+
802+
Parameters
803+
- `second` (``Iterable[TKey]``)
804+
- `key_selector` (``Callable[[TSource_co], TKey]``)
805+
806+
Returns
807+
- ``Enumerable[TSource_co]``
808+
809+
Produces the set difference of two sequences: self - second, according to a key selector that
810+
determines "distinctness".
811+
812+
Example
813+
>>> first = [(16, 'x'), (9, 'y'), (12, 'd'), (16, 't')]
814+
>>> second = ['y', 'd']
815+
>>> Enumerable(first).except_by(second, lambda x: x[1]).to_list()
816+
[(16, 'x'), (16, 't')]
817+
818+
----
819+
780820
instancemethod ``first()``
781821
----------------------------
782822

doc/api_spec.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,11 @@ class ClassSpec(TypedDict):
5858
'count',
5959
'default_if_empty',
6060
'distinct',
61+
'distinct_by',
6162
'element_at',
6263
'empty',
6364
'except1',
65+
'except_by',
6466
'first',
6567
'first2',
6668
'group_by',
@@ -188,9 +190,8 @@ class ClassSpec(TypedDict):
188190
'methods': {
189191
'aggregate_right',
190192
'as_more',
191-
'distinct_by',
192193
'enumerate',
193-
'except_by',
194+
'except_by2',
194195
'flatten',
195196
'flatten2',
196197
'for_each',

tests/test_more_usage.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,25 +71,19 @@ def test_enumerate_start(self):
7171
assert en.enumerate(1).to_list() == [(1, '2'), (2, '4'), (3, '6')]
7272

7373

74-
class TestDistinctByMethod:
75-
def test_distinct_by(self):
76-
en = MoreEnumerable([1, 4, 5, 6, 4, 3, 1, 99])
77-
assert en.distinct_by(lambda x: x // 2).to_list() == [1, 4, 6, 3, 99]
78-
79-
80-
class TestExceptByMethod:
81-
def test_except_by(self):
74+
class TestExceptBy2Method:
75+
def test_except_by2(self):
8276
en = MoreEnumerable(['aaa', 'bb', 'c', 'dddd'])
83-
assert en.except_by(['xx', 'y'], len).to_list() == ['aaa', 'dddd']
77+
assert en.except_by2(['xx', 'y'], len).to_list() == ['aaa', 'dddd']
8478

8579
def test_unhashable(self):
8680
en = MoreEnumerable([['aaa'], ['bb'], ['c'], ['dddd']])
87-
q = en.except_by([['xx'], ['y']], lambda x: len(x[0]))
81+
q = en.except_by2([['xx'], ['y']], lambda x: len(x[0]))
8882
assert q.to_list() == [['aaa'], ['dddd']]
8983

9084
def test_no_repeat(self):
9185
en = MoreEnumerable(['aaa', 'bb', 'c', 'dddd', 'aaa'])
92-
assert en.except_by(['xx', 'y'], len).to_list() == ['aaa', 'dddd']
86+
assert en.except_by2(['xx', 'y'], len).to_list() == ['aaa', 'dddd']
9387

9488
def test_remove_nothing(self):
9589
i = -1
@@ -98,7 +92,7 @@ def selector(_):
9892
return i
9993
strs = ['aaa', 'bb', 'c', 'dddd', 'dddd']
10094
en = MoreEnumerable(strs)
101-
assert en.except_by((), selector).to_list() == strs
95+
assert en.except_by2((), selector).to_list() == strs
10296

10397

10498
class TestFlattenMethod:

tests/test_usage.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,12 @@ def test_distinct(self):
344344
assert distinct == [1, 4, 5, 6, 3, 99]
345345

346346

347+
class TestDistinctByMethod:
348+
def test_distinct_by(self):
349+
en = Enumerable([1, 4, 5, 6, 4, 3, 1, 99])
350+
assert en.distinct_by(lambda x: x // 2).to_list() == [1, 4, 6, 3, 99]
351+
352+
347353
class TestReverseMethod:
348354
def test_delayed(self):
349355
gen = (i for i in range(5))
@@ -452,6 +458,18 @@ def test_remove_nothing(self):
452458
assert exc.to_list() == ints
453459

454460

461+
class TestExceptByMethod:
462+
def test_except_by1(self):
463+
first = [(16, 'x'), (9, 'y'), (12, 'd'), (16, 't')]
464+
en = Enumerable(first)
465+
exc = en.except_by(['y', 'd'], lambda x: x[1])
466+
assert exc.to_list() == [(16, 'x'), (16, 't')]
467+
468+
def test_no_repeat(self):
469+
en = Enumerable(['aaa', 'bb', 'c', 'dddd', 'aaa'])
470+
assert en.except_by([2, 1], len).to_list() == ['aaa', 'dddd']
471+
472+
455473
class TestFirstMethod:
456474
def test_first_overload1_yes(self):
457475
lst = ('a', 'b', 5, 'c')

types_linq/enumerable.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,9 @@ def inner():
319319
def distinct(self) -> Enumerable[TSource_co]:
320320
return self.except1(())
321321

322+
def distinct_by(self, key_selector: Callable[[TSource_co], object]) -> Enumerable[TSource_co]:
323+
return self.except_by((), key_selector)
324+
322325
def element_at(self, index: int, *args: TDefault) -> Union[TSource_co, TDefault]:
323326
if len(args) == 0:
324327
return self._getitem_impl(index, fallback=True) # type: ignore
@@ -334,12 +337,19 @@ def empty() -> Enumerable[TSource_co]:
334337
return Enumerable(())
335338

336339
def except1(self, second: Iterable[TSource_co]) -> Enumerable[TSource_co]:
340+
return self.except_by(second, lambda x: x)
341+
342+
def except_by(self,
343+
second: Iterable[TKey],
344+
key_selector: Callable[[TSource_co], TKey],
345+
) -> Enumerable[TSource_co]:
337346
def inner():
338347
s = ComposeSet(second)
339348
for elem in self:
340-
if elem in s:
349+
key = key_selector(elem)
350+
if key in s:
341351
continue
342-
s.add(elem)
352+
s.add(key)
343353
yield elem
344354
return Enumerable(inner)
345355

types_linq/enumerable.pyi

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,17 @@ class Enumerable(Sequence[TSource_co], Generic[TSource_co]):
509509
[1, 4, 5, 6, 3, 99]
510510
'''
511511

512+
def distinct_by(self, key_selector: Callable[[TSource_co], object]) -> Enumerable[TSource_co]:
513+
'''
514+
Returns distinct elements from the sequence where "distinctness" is determined by the value
515+
returned by the selector.
516+
517+
Example
518+
>>> ints = [1, 4, 5, 6, 4, 3, 1, 99]
519+
>>> Enumerable(ints).distinct_by(lambda x: x // 2).to_list()
520+
[1, 4, 6, 3, 99]
521+
'''
522+
512523
@overload
513524
def element_at(self, index: int) -> TSource_co:
514525
'''
@@ -578,6 +589,21 @@ class Enumerable(Sequence[TSource_co], Generic[TSource_co]):
578589
[2, 4]
579590
'''
580591

592+
def except_by(self,
593+
second: Iterable[TKey],
594+
key_selector: Callable[[TSource_co], TKey],
595+
) -> Enumerable[TSource_co]:
596+
'''
597+
Produces the set difference of two sequences: self - second, according to a key selector that
598+
determines "distinctness".
599+
600+
Example
601+
>>> first = [(16, 'x'), (9, 'y'), (12, 'd'), (16, 't')]
602+
>>> second = ['y', 'd']
603+
>>> Enumerable(first).except_by(second, lambda x: x[1]).to_list()
604+
[(16, 'x'), (16, 't')]
605+
'''
606+
581607
@overload
582608
def first(self) -> TSource_co:
583609
'''

types_linq/more/more_enumerable.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,10 @@ def aggregate_right(self, *args) -> Any:
3737
def as_more(self) -> MoreEnumerable[TSource_co]: # pyright: reportIncompatibleMethodOverride=false
3838
return self
3939

40-
def distinct_by(self, key_selector: Callable[[TSource_co], object]) -> MoreEnumerable[TSource_co]:
41-
return self.except_by((), key_selector)
42-
4340
def enumerate(self, start: int = 0) -> MoreEnumerable[Tuple[int, TSource_co]]:
4441
return MoreEnumerable(lambda: enumerate(self, start))
4542

46-
def except_by(self,
43+
def except_by2(self,
4744
second: Iterable[TSource_co],
4845
key_selector: Callable[[TSource_co], object],
4946
) -> MoreEnumerable[TSource_co]:

types_linq/more/more_enumerable.pyi

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ class MoreEnumerable(Enumerable[TSource_co]):
2222
constructing, using as_more(), or invoking MoreEnumerable methods that return MoreEnumerable
2323
instead of Enumerable.
2424
25-
Some APIs are subject to change as updates to .NET (6) are happening - they will move to Enumerable
26-
class if one day they appear in the official .NET doc.
25+
These APIs may have breaking changes more frequently than those in Enumerable class because updates
26+
in .NET are happening and sometimes ones of these APIs could be moved to Enumerable with modification,
27+
or changed to accommodate changes to Enumerable.
2728
'''
2829

2930
@overload
@@ -66,17 +67,6 @@ class MoreEnumerable(Enumerable[TSource_co]):
6667
Returns the original MoreEnumerable reference.
6768
'''
6869

69-
def distinct_by(self, key_selector: Callable[[TSource_co], object]) -> MoreEnumerable[TSource_co]:
70-
'''
71-
Returns distinct elements from the sequence where "distinctness" is determined by the value
72-
returned by the selector.
73-
74-
Example
75-
>>> ints = [1, 4, 5, 6, 4, 3, 1, 99]
76-
>>> MoreEnumerable(ints).distinct_by(lambda x: x // 2).to_list()
77-
[1, 4, 6, 3, 99]
78-
'''
79-
8070
def enumerate(self, start: int = 0) -> MoreEnumerable[Tuple[int, TSource_co]]:
8171
'''
8272
Returns a sequence of tuples containing the index and the value from the source sequence. `start`
@@ -88,7 +78,7 @@ class MoreEnumerable(Enumerable[TSource_co]):
8878
[(0, 2), (1, 4), (2, 6)]
8979
'''
9080

91-
def except_by(self,
81+
def except_by2(self,
9282
second: Iterable[TSource_co],
9383
key_selector: Callable[[TSource_co], object],
9484
) -> MoreEnumerable[TSource_co]:
@@ -99,7 +89,7 @@ class MoreEnumerable(Enumerable[TSource_co]):
9989
Example
10090
>>> first = [(16, 'x'), (9, 'y'), (12, 'd'), (16, 't')]
10191
>>> second = [(24, 'd'), (77, 'y')]
102-
>>> MoreEnumerable(first).except_by(second, lambda x: x[1]).to_list()
92+
>>> MoreEnumerable(first).except_by2(second, lambda x: x[1]).to_list()
10393
[(16, 'x'), (16, 't')]
10494
'''
10595

0 commit comments

Comments
 (0)