GroupBy With Maximum Size

Monday, November 19, 2012

I recently needed to group some objects, which is easy with GroupBy, but I also needed to enforce a maximum group size, as demonstrated by the following test.

public void Splits_Group_When_GroupSize_Greater_Than_MaxSize()
{
var items = new[] { "A1", "A2", "A3", "B4", "B5" };

var result = items.GroupByWithMaxSize(i => i[0], maxSize: 2);

Assert.True(result.ElementAt(0).SequenceEqual(new[] { "A1", "A2" }));
Assert.True(result.ElementAt(1).SequenceEqual(new[] { "A3" }));
Assert.True(result.ElementAt(2).SequenceEqual(new[] { "B4", "B5" }));
}
The following code is not the fastest or cleverest solution, but it does make all the tests turn green.  
public static IEnumerable<IEnumerable<T>> GroupByWithMaxSize<T, TKey>(
this IEnumerable<T> source, Func<T, TKey> keySelector, int maxSize)
{
var originalGroups = source.GroupBy(keySelector);

foreach (var group in originalGroups)
{
if (group.Count() <= maxSize)
{
yield return group;
}
else
{
var regroups = group.Select((item, index) => new { item, index })
.GroupBy(g => g.index / maxSize);
foreach (var regroup in regroups)
{
yield return regroup.Select(g => g.item);
}
}
}
}
In this case I don’t need the Key property provided by IGrouping, so the return type is a generically beautiful IEnumerable<IEnumerable<T>>.

Comments
gravatar Fredi Machado Monday, November 19, 2012
That's a nice solution.

Thanks Scott!
gravatar Stas Monday, November 19, 2012
Nice!
BTW, also it's possible to group items using RX by using items.Buffer(count)
function. I know it isn't the same but results are close
gravatar Scott Allen Monday, November 19, 2012
@Stas: Ah, thanks! I should have looked at Rx first.
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!