Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] GroupSequentialBy #2075

Open
voroninp opened this issue Jan 3, 2024 · 0 comments
Open

[Feature] GroupSequentialBy #2075

voroninp opened this issue Jan 3, 2024 · 0 comments

Comments

@voroninp
Copy link

voroninp commented Jan 3, 2024

I believe for async sequences this can be a good addition to GroupBy method.
This method would have the same behavior of python's itertools.groupby

It's also convenient for asynchronous client-side processing of database query when it's guaranteed that elements of the same group are sequential:

return dbContext.Set<DemographicsInPeriod>()
    .OrderBy(d => d.DemographicsId)
    .ThenBy(d => d.Period.SequenceNumber)
    .AsAsyncEnumerable()
    .GroupSequentialBy(d => d.Demographics, DemographicsByIdEquality, d => d.Period);

Our implementation is the following:

/// <summary>
/// Puts consecutive items of the sequence into groups.
/// </summary>
/// <remarks>
/// For example, <c>[1,1,2,1]</c> grouped by value will turn into <strong>three</strong> groups 
/// with keys <c>[1,2,1]</c>.
/// </remarks>
public static async IAsyncEnumerable<IGrouping<TKey, TOut>> GroupSequentialBy<TIn, TKey, TOut>(
    this IAsyncEnumerable<TIn> seq,
    Func<TIn, TKey> keySelector, IEqualityComparer<TKey> keyEqualityComparer,
    Func<TIn, TOut> itemSelector)
{
    seq.NotNull();
    keySelector.NotNull();
    keyEqualityComparer.NotNull();
    itemSelector.NotNull();

    TKey key;
    var items = new List<TOut>();

    await using var enumerator = seq.GetAsyncEnumerator();
    if (!(await enumerator.MoveNextAsync()))
    {
        yield break;
    }

    key = keySelector(enumerator.Current);
    items.Add(itemSelector(enumerator.Current));

    while (await enumerator.MoveNextAsync())
    {
        var newKey = keySelector(enumerator.Current);
        var newGroupStarted = !keyEqualityComparer.Equals(key, newKey);
        var item = itemSelector(enumerator.Current);
        if (newGroupStarted)
        {
            yield return new Grouping<TKey, TOut>(key, items);

            items = new List<TOut>();
        }

        key = newKey;
        items.Add(item);
    }

    if (items.Count > 0)
    {
        yield return new Grouping<TKey, TOut>(key, items);
    }
}

private sealed class Grouping<TKey, TElement> : IGrouping<TKey, TElement>
{
    private readonly TKey _key;
    private readonly IEnumerable<TElement> _elements;

    public Grouping(TKey key, IEnumerable<TElement> elements)
        {
            _key = key;
            _elements = elements.NotNull();
        }

    public TKey Key => _key;

    public IEnumerator<TElement> GetEnumerator() => _elements.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => _elements.GetEnumerator();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant