HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Grouping by sequence in LINQ

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
linqsequencegrouping

Problem

Suppose a series of objects (presented here as tuples):

"a" | 1
"a" | 2
"b" | 3
"b" | 4
"a" | 5

There is no built in function (that I know of) to group by the first columns's sequence, that is, all the "a"'s in a row, then the "b"'s, then the one "a" alone. So that the groups become: {1,2},{3,4},{5} and not {1,2,5},{3,4}.

So I wrote this, which I'm submitting for review. I emulate all 8 variants of GroupBy which I present here as the two main variants (with and without result selector):

public static IEnumerable> GroupBySequence
    (this TSource[] source,
     Func keySelector,
     Func elementSelector,
     IEqualityComparer comparer)
{
    var newElement = source.Select(keySelector).ToArray().MakeSequentialKey(comparer).Zip(
        source.Select(elementSelector),
        (x, y) => new Tuple(x, y));

    var groupElement = newElement.GroupBy(t => t.Item1, t => t.Item2);

    var newKey = source.Select(keySelector).ToArray().MakeSequentialKey(comparer).Zip(
        source.Select(keySelector),
        (x, y) => new Tuple(x, y));

    var groupKey = newKey.GroupBy(t => t.Item1, t => t.Item2);

    return groupKey.Zip(groupElement, 
        (key,element) => new Grouping(key.First(),element));
}

public static IEnumerable GroupBySequence
    (this TSource[] source,
     Func keySelector,
     Func elementSelector,
     Func, TResult> resultSelector,
     IEqualityComparer comparer)
{
    return source.GroupBySequence(keySelector, 
        elementSelector, comparer).Select(x => resultSelector(x.Key, x));
}


Helper methods:

```
//Performs an operation over each consecutive item. Here used for determining equality.
public static IEnumerable WithNext
(this T[] source, Func operation)
{
return source.Zip(source.Skip(1), operation);
}

//Makes the unique key
public static IEnumerable MakeSequentialKey
(this T[] source, IEqualityComparer comparer)
{
if (source.Length == 0)
return Enumerable.Empty();

return (new[] { 0 })
.C

Solution

As in my comment:


If {1,2},{3,4},{5} is your desired outcome, I don't understand all the
complexity you added. Can't you just write a simple loop through the
items which yields a result every time a group is passed?

public static IEnumerable> GroupBySequence(
    this TSource[] source,
    Func keySelector,
    Func elementSelector,
    IEqualityComparer comparer)
{
    if (source.Length == 0)
    {
        yield break;
    }

    TKey currentKey = keySelector(source.First());
    var foundItems = new List();
    foreach (var item in source)
    {
        TKey key = keySelector(item);

        if (!comparer.Equals(currentKey, key))
        {                    
            yield return new Grouping(currentKey, foundItems);
            currentKey = key;
            foundItems = new List();
        }

        foundItems.Add(elementSelector(item));
    }

    if (foundItems.Count > 0)
    {
        yield return new Grouping(currentKey, foundItems);
    }
}

Code Snippets

public static IEnumerable<IGrouping<TKey, TElement>> GroupBySequence<TSource, TKey, TElement>(
    this TSource[] source,
    Func<TSource, TKey> keySelector,
    Func<TSource, TElement> elementSelector,
    IEqualityComparer<TKey> comparer)
{
    if (source.Length == 0)
    {
        yield break;
    }

    TKey currentKey = keySelector(source.First());
    var foundItems = new List<TElement>();
    foreach (var item in source)
    {
        TKey key = keySelector(item);

        if (!comparer.Equals(currentKey, key))
        {                    
            yield return new Grouping<TKey, TElement>(currentKey, foundItems);
            currentKey = key;
            foundItems = new List<TElement>();
        }

        foundItems.Add(elementSelector(item));
    }

    if (foundItems.Count > 0)
    {
        yield return new Grouping<TKey, TElement>(currentKey, foundItems);
    }
}

Context

StackExchange Code Review Q#6512, answer score: 6

Revisions (0)

No revisions yet.