Skip to content

Commit c412e25

Browse files
committed
+ DistinctUntilChanged
1 parent 9f92c3f commit c412e25

File tree

4 files changed

+219
-2
lines changed

4 files changed

+219
-2
lines changed

async-enumerable-dotnet-test/DistinctTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ await AsyncEnumerable.Range(1, 5)
6262
public async void Custom_Set()
6363
{
6464
await AsyncEnumerable.Range(1, 5)
65-
.Distinct(v => (v % 3), () => new HashSet<long>())
65+
.Distinct(v => v % 3, () => new HashSet<long>())
6666
.AssertResult(1, 2, 3);
6767
}
6868
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) David Karnok & Contributors.
2+
// Licensed under the Apache 2.0 License.
3+
// See LICENSE file in the project root for full license information.
4+
5+
using Xunit;
6+
using async_enumerable_dotnet;
7+
using System.Collections.Generic;
8+
9+
namespace async_enumerable_dotnet_test
10+
{
11+
public class DistinctUntilChangedTest
12+
{
13+
[Fact]
14+
public async void Empty()
15+
{
16+
await AsyncEnumerable.Empty<int>()
17+
.DistinctUntilChanged()
18+
.AssertResult();
19+
}
20+
21+
[Fact]
22+
public async void Just()
23+
{
24+
await AsyncEnumerable.Just(1)
25+
.DistinctUntilChanged()
26+
.AssertResult(1);
27+
}
28+
29+
[Fact]
30+
public async void Range()
31+
{
32+
await AsyncEnumerable.Range(1, 5)
33+
.DistinctUntilChanged()
34+
.AssertResult(1, 2, 3, 4, 5);
35+
}
36+
37+
[Fact]
38+
public async void Redundant()
39+
{
40+
await AsyncEnumerable.FromArray(1, 2, 3, 3, 2, 1, 1, 4, 5, 4, 4)
41+
.DistinctUntilChanged()
42+
.AssertResult(1, 2, 3, 2, 1, 4, 5, 4);
43+
}
44+
45+
[Fact]
46+
public async void Redundant_Comparer()
47+
{
48+
await AsyncEnumerable.FromArray(1, 2, 3, 3, 2, 1, 1, 4, 5, 4, 4)
49+
.DistinctUntilChanged(EqualityComparer<int>.Default)
50+
.AssertResult(1, 2, 3, 2, 1, 4, 5, 4);
51+
}
52+
53+
[Fact]
54+
public async void KeySelector()
55+
{
56+
await AsyncEnumerable.Range(1, 10)
57+
.DistinctUntilChanged(k => k / 3)
58+
.AssertResult(1, 3, 6, 9);
59+
}
60+
61+
[Fact]
62+
public async void KeySelector_KeyComparer()
63+
{
64+
await AsyncEnumerable.Range(1, 10)
65+
.DistinctUntilChanged(k => k / 3, EqualityComparer<long>.Default)
66+
.AssertResult(1, 3, 6, 9);
67+
}
68+
}
69+
}

async-enumerable-dotnet/AsyncEnumerable.cs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1678,7 +1678,7 @@ public static IAsyncEnumerable<TSource> Distinct<TSource, TKey>(this IAsyncEnume
16781678

16791679
/// <summary>
16801680
/// Checks if the source items have been seen before by checking
1681-
/// a key extracted from such items agains a set of keys and
1681+
/// a key extracted from such items against a set of keys and
16821682
/// drops such items, ensuring that only distinct source items pass
16831683
/// through.
16841684
/// </summary>
@@ -1696,5 +1696,66 @@ public static IAsyncEnumerable<TSource> Distinct<TSource, TKey>(this IAsyncEnume
16961696
RequireNonNull(setSupplier, nameof(setSupplier));
16971697
return new Distinct<TSource, TKey>(source, keySelector, setSupplier);
16981698
}
1699+
1700+
/// <summary>
1701+
/// Checks if the current item is distinct from the previous item,
1702+
/// and if so it is no it is relayed.
1703+
/// </summary>
1704+
/// <typeparam name="TSource">The element type of the source and result sequences.</typeparam>
1705+
/// <param name="source">The source sequence to filter for distinct subsequent items.</param>
1706+
/// <returns>The new IAsyncEnumerable sequence.</returns>
1707+
public static IAsyncEnumerable<TSource> DistinctUntilChanged<TSource>(this IAsyncEnumerable<TSource> source)
1708+
{
1709+
return DistinctUntilChanged(source, v => v, EqualityComparer<TSource>.Default);
1710+
}
1711+
1712+
/// <summary>
1713+
/// Checks if the current item is distinct from the previous item,
1714+
/// and if so it is no it is relayed,
1715+
/// based on comparing the items via a custom equality comparer.
1716+
/// </summary>
1717+
/// <typeparam name="TSource">The element type of the source and result sequences.</typeparam>
1718+
/// <param name="source">The source sequence to filter for distinct subsequent items.</param>
1719+
/// <param name="comparer">The comparer for comparing the source items.</param>
1720+
/// <returns>The new IAsyncEnumerable sequence.</returns>
1721+
public static IAsyncEnumerable<TSource> DistinctUntilChanged<TSource>(this IAsyncEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
1722+
{
1723+
return DistinctUntilChanged(source, v => v, comparer);
1724+
}
1725+
1726+
/// <summary>
1727+
/// Checks if the current item is distinct from the previous item,
1728+
/// and if so it is no it is relayed,
1729+
/// based on comparing keys extracted via a function.
1730+
/// </summary>
1731+
/// <typeparam name="TSource">The element type of the source and result sequences.</typeparam>
1732+
/// <typeparam name="TKey">The type of the keys extracted for comparison</typeparam>
1733+
/// <param name="source">The source sequence to filter for distinct subsequent items.</param>
1734+
/// <param name="keySelector">The function receiving the current source item and should return a key value for comparison.</param>
1735+
/// <returns>The new IAsyncEnumerable sequence.</returns>
1736+
public static IAsyncEnumerable<TSource> DistinctUntilChanged<TSource, TKey>(this IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector)
1737+
{
1738+
return DistinctUntilChanged(source, keySelector, EqualityComparer<TKey>.Default);
1739+
}
1740+
1741+
/// <summary>
1742+
/// Checks if the current item is distinct from the previous item,
1743+
/// and if so it is no it is relayed,
1744+
/// based on comparing keys extracted via a function and using
1745+
/// a custom equality comparer.
1746+
/// </summary>
1747+
/// <typeparam name="TSource">The element type of the source and result sequences.</typeparam>
1748+
/// <typeparam name="TKey">The type of the keys extracted for comparison</typeparam>
1749+
/// <param name="source">The source sequence to filter for distinct subsequent items.</param>
1750+
/// <param name="keySelector">The function receiving the current source item and should return a key value for comparison.</param>
1751+
/// <param name="keyComparer">The comparer for comparing the key values extracted via <paramref name="keySelector"/>.</param>
1752+
/// <returns>The new IAsyncEnumerable sequence.</returns>
1753+
public static IAsyncEnumerable<TSource> DistinctUntilChanged<TSource, TKey>(this IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> keyComparer)
1754+
{
1755+
RequireNonNull(source, nameof(source));
1756+
RequireNonNull(keySelector, nameof(keySelector));
1757+
RequireNonNull(keyComparer, nameof(keyComparer));
1758+
return new DistinctUntilChanged<TSource, TKey>(source, keySelector, keyComparer);
1759+
}
16991760
}
17001761
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright (c) David Karnok & Contributors.
2+
// Licensed under the Apache 2.0 License.
3+
// See LICENSE file in the project root for full license information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Threading.Tasks;
8+
9+
namespace async_enumerable_dotnet.impl
10+
{
11+
internal sealed class DistinctUntilChanged<TSource, TKey> : IAsyncEnumerable<TSource>
12+
{
13+
private readonly IAsyncEnumerable<TSource> _source;
14+
15+
private readonly Func<TSource, TKey> _keySelector;
16+
17+
private readonly IEqualityComparer<TKey> _keyComparer;
18+
19+
public DistinctUntilChanged(IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> keyComparer)
20+
{
21+
_source = source;
22+
_keySelector = keySelector;
23+
_keyComparer = keyComparer;
24+
}
25+
26+
public IAsyncEnumerator<TSource> GetAsyncEnumerator()
27+
{
28+
return new DistinctUntilChangedEnumerator(_source.GetAsyncEnumerator(), _keySelector, _keyComparer);
29+
}
30+
31+
private sealed class DistinctUntilChangedEnumerator : IAsyncEnumerator<TSource>
32+
{
33+
private readonly IAsyncEnumerator<TSource> _source;
34+
35+
private readonly Func<TSource, TKey> _keySelector;
36+
37+
private readonly IEqualityComparer<TKey> _keyComparer;
38+
39+
private TKey _prevKey;
40+
41+
public TSource Current => _source.Current;
42+
43+
private bool _once;
44+
45+
public DistinctUntilChangedEnumerator(IAsyncEnumerator<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> keyComparer)
46+
{
47+
_source = source;
48+
_keySelector = keySelector;
49+
_keyComparer = keyComparer;
50+
}
51+
52+
public ValueTask DisposeAsync()
53+
{
54+
_prevKey = default;
55+
return _source.DisposeAsync();
56+
}
57+
58+
public async ValueTask<bool> MoveNextAsync()
59+
{
60+
for (; ;)
61+
{
62+
if (await _source.MoveNextAsync())
63+
{
64+
var key = _keySelector(_source.Current);
65+
if (!_once)
66+
{
67+
_once = true;
68+
_prevKey = key;
69+
return true;
70+
}
71+
if (!_keyComparer.Equals(_prevKey, key))
72+
{
73+
_prevKey = key;
74+
return true;
75+
}
76+
77+
_prevKey = key;
78+
}
79+
else
80+
{
81+
return false;
82+
}
83+
}
84+
}
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)