patterncsharpMinor
Grouping by sequence in LINQ
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
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
"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
complexity you added. Can't you just write a simple loop through the
items which yields a result every time a group is passed?
If
{1,2},{3,4},{5} is your desired outcome, I don't understand all thecomplexity 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.