Reputation: 575
Consider I have this object:
public class Person
{
public string Name { get; set; }
public string Family { get; set; }
public string Group { get; set; }
}
I want to groupBy a list of "Person" based on their group but I have limit in the number of groups.
Here is what I currently have:
var person1=new Person
{
Name = "rfy",
Family = "jhg",
Group = "A"
};
var person2=new Person
{
Name = "rjg",
Family = "fh",
Group = "B"
};
var list = new List<Person> {person1, person2};
var group = list.GroupBy(s => s.Group);
in this case I will have this:
"A":{person1,person2,person3}
"B":{person4,Person5,person6}
but I want each group have only two items, and I want it to be like this:
"A":{person1,person2}
"B":{person4,Person5}
"A":{person3}
"B":{person6}
Upvotes: 1
Views: 158
Reputation: 186668
Well, it seems that we should implement the routine manually:
public static partial class EnumerableExtensions {
internal sealed class MyGrouping<TKey, TElement> : IGrouping<TKey, TElement> {
private readonly IEnumerable<TElement> m_Values;
public MyGrouping(TKey key, IEnumerable<TElement> values) {
if (values == null)
throw new ArgumentNullException("values");
Key = key;
m_Values = values;
}
public TKey Key {
get;
}
public IEnumerator<TElement> GetEnumerator() {
return m_Values.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
return GetEnumerator();
}
}
public static IEnumerable<IGrouping<TKey, TSource>> GroupByRestricted<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
int size = int.MaxValue) {
if (null == source)
throw new ArgumentNullException("source");
else if (null == keySelector)
throw new ArgumentNullException("keySelector");
else if (size <= 0)
throw new ArgumentOutOfRangeException("size", "size must be positive");
Dictionary<TKey, List<TSource>> dict = new Dictionary<TKey, List<TSource>>();
foreach (var item in source) {
var key = keySelector(item);
List<TSource> list = null;
if (!dict.TryGetValue(key, out list)) {
list = new List<TSource>();
dict.Add(key, list);
}
list.Add(item);
if (list.Count >= size) {
yield return new MyGrouping<TKey, TSource>(key, list.ToArray());
list.Clear();
}
}
foreach (var item in dict.Where(pair => pair.Value.Any()))
yield return new MyGrouping<TKey, TSource>(item.Key, item.Value.ToArray());
}
}
Test
List<Person> test = new List<Person>() {
new Person() { Name = "Person1", Group = "A"},
new Person() { Name = "Person2", Group = "A"},
new Person() { Name = "Person3", Group = "A"},
new Person() { Name = "Person4", Group = "B"},
new Person() { Name = "Person5", Group = "B"},
new Person() { Name = "Person6", Group = "B"},
};
var result = test
.GroupByRestricted(item => item.Group, 2)
.Select(chunk => $"{chunk.Key}: {string.Join("; ", chunk.Select(item => item.Name))}");
Console.WriteLine(string.Join(Environment.NewLine, result));
Outcome:
A: Person1; Person2
B: Person4; Person5
A: Person3
B: Person6
Upvotes: 0
Reputation: 11730
First group entirely:
[
{ "A": [person1, person2, person3] },
{ "B": [person4, person5, person6] }
]
Then partition it into chunks in a second pass. In your case you want to create an entire group object for each element of the partition. This will require some SelectMany cleverness to recreate a group. GroupBy the person's group again will recover the key and create a new group.
[
[
{ "A": [person1, person2] },
{ "A": [person3] }
],
[
{ "B": [person4, person5] },
{ "B": [person6] }
]
]
And also in the second pass, you can use SelectMany to flatten it.
[
{ "A": [person1, person2] },
{ "A": [person3] },
{ "B": [person4, person5] },
{ "B": [person6] }
]
Here is the critical part. Chunk is included in the complete example below:
var query = people.GroupBy(person => person.Group)
.SelectMany(g => g.Chunk(2))
.SelectMany(g => g.GroupBy(person => person.Group));
Here is a complete example.
using System;
using System.Collections.Generic;
using System.Linq;
public class Person
{
public string Name { get; set; }
public string Group { get; set; }
public override string ToString()
{
return string.Format("{0} ({1})", Name, Group);
}
}
public static class Extensions
{
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
while (source.Any())
{
yield return source.Take(chunksize);
source = source.Skip(chunksize);
}
}
}
class Program
{
static void Main(string[] args)
{
Person[] people = new Person[] {
new Person() { Name = "person1", Group = "A" },
new Person() { Name = "person2", Group = "A" },
new Person() { Name = "person3", Group = "A" },
new Person() { Name = "person4", Group = "B" },
new Person() { Name = "person5", Group = "B" },
new Person() { Name = "person6", Group = "B" }
};
var query = people.GroupBy(person => person.Group)
.SelectMany(g => g.Chunk(2))
.SelectMany(g => g.GroupBy(person => person.Group));
foreach (var group in query)
{
Console.WriteLine(group.Key);
foreach (var item in group)
{
Console.WriteLine(" {0}", item);
}
}
}
}
Upvotes: 2