-
-
Notifications
You must be signed in to change notification settings - Fork 9
/
CountBy.cs
103 lines (90 loc) · 3.93 KB
/
CountBy.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
namespace SuperLinq.Async;
public static partial class AsyncSuperEnumerable
{
/// <summary>
/// Applies a key-generating function to each element of a sequence and returns a sequence of
/// unique keys and their number of occurrences in the original sequence.
/// </summary>
/// <typeparam name="TSource">Type of the elements of the source sequence.</typeparam>
/// <typeparam name="TKey">Type of the projected element.</typeparam>
/// <param name="source">Source sequence.</param>
/// <param name="keySelector">Function that transforms each item of source sequence into a key to be compared against the others.</param>
/// <returns>A sequence of unique keys and their number of occurrences in the original sequence.</returns>
public static IAsyncEnumerable<KeyValuePair<TKey, int>> CountBy<TSource, TKey>(
this IAsyncEnumerable<TSource> source,
Func<TSource, TKey> keySelector
)
{
return source.CountBy(keySelector, comparer: null);
}
/// <summary>
/// Applies a key-generating function to each element of a sequence and returns a sequence of
/// unique keys and their number of occurrences in the original sequence.
/// An additional argument specifies a comparer to use for testing equivalence of keys.
/// </summary>
/// <typeparam name="TSource">Type of the elements of the source sequence.</typeparam>
/// <typeparam name="TKey">Type of the projected element.</typeparam>
/// <param name="source">Source sequence.</param>
/// <param name="keySelector">Function that transforms each item of source sequence into a key to be compared against the others.</param>
/// <param name="comparer">The equality comparer to use to determine whether or not keys are equal.
/// If null, the default equality comparer for <typeparamref name="TSource"/> is used.</param>
/// <returns>A sequence of unique keys and their number of occurrences in the original sequence.</returns>
public static IAsyncEnumerable<KeyValuePair<TKey, int>> CountBy<TSource, TKey>(
this IAsyncEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey>? comparer
)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(keySelector);
return Core(source, keySelector, comparer ?? EqualityComparer<TKey>.Default);
static async IAsyncEnumerable<KeyValuePair<TKey, int>> Core(
IAsyncEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> comparer,
[EnumeratorCancellation] CancellationToken cancellationToken = default
)
{
// Avoid the temptation to inline the Loop method, which
// exists solely to separate the scope & lifetimes of the
// locals needed for the actual looping of the source &
// production of the results (that happens once at the start
// of iteration) from those needed to simply yield the
// results. It is harder to reason about the lifetimes (if the
// code is inlined) with respect to how the compiler will
// rewrite the iterator code as a state machine. For
// background, see:
// http://blog.stephencleary.com/2010/02/q-should-i-set-variables-to-null-to.html
var (keys, counts) = await Loop(source, keySelector, comparer, cancellationToken).ConfigureAwait(false);
for (var i = 0; i < keys.Count; i++)
yield return new(keys[i], counts[i]);
}
static async ValueTask<(List<TKey>, List<int>)> Loop(
IAsyncEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> cmp,
CancellationToken cancellationToken
)
{
var dic = new Collections.NullKeyDictionary<TKey, int>(cmp);
var keys = new List<TKey>();
var counts = new List<int>();
await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false))
{
var key = keySelector(item);
if (dic.TryGetValue(key, out var index))
{
counts[index]++;
}
else
{
index = keys.Count;
dic[key] = index;
keys.Add(key);
counts.Add(1);
}
}
return (keys, counts);
}
}
}