1

I have a list of instances of my own class "Vertrag" ("Contract") and need to group on them.

A simple GroupBy does exactly what I need: Group by the last four fields, the result is an IEnumerable<IGrouping<...,Vertrag>>. I can work with this result type, it is easy enough to iterate on.
At the spot marked ... is nothing given as type definition. So this seems to be an anonymous type?

Now I got a further request: Make sure that there is a max. three elements in each group. Start new groups (still same key!) until all are allocated. I found a solution to that as well in StackOverflow, the LINQ is a bit more complex but working fine. Just the return type is somewhat more complex.
I'd prefer to transform this complex structure back into the simple IGrouping-form so it works with the already existing code.

I got it almost working, or at least simplified down to a version that is as easy to use as the simple GroupBy one, yet not exactly the same. Is there a way to create this IGrouping with anonymous type yourself so that there is no need to change the code following the original grouping?

The key would come up identical more than once, exactly opposite of what GroupBy means to do. So I am not sure if a hand-crafted IGrouping would even allow that to exist.

This is a working LINQPad example (with type definition):

void Main() 
{
    List<Vertrag> verträge = new List<Vertrag>();
    verträge.Add(new Vertrag("a1","b1","c","d","e"));
    verträge.Add(new Vertrag("a2","b1","c","d","e"));
    verträge.Add(new Vertrag("a3","b1","c","d","e"));
    verträge.Add(new Vertrag("a4","b1","c","d","e"));
    verträge.Add(new Vertrag("a5","b1","c","d","e"));
    verträge.Add(new Vertrag("a6","b1","c","d","e"));
    verträge.Add(new Vertrag("a7","b1","c","d","e"));
    verträge.Add(new Vertrag("a1","b2","c","d","e"));
    verträge.Add(new Vertrag("a2","b2","c","d","e"));
    verträge.Add(new Vertrag("a3","b2","c","d","e"));
    verträge.Add(new Vertrag("a4","b2","c","d","e"));

    // Very easy first group
    var verträgeGruppiert = verträge
        .GroupBy(v => new { v.Beginn, v.Ende, v.Vorlage, v.Ware });
    verträgeGruppiert.Dump();

    // Far more complex grouping
    var maxInGroup = 3;
    var verträgeGruppiertMax = verträge
        .GroupBy(v => new {v.Beginn, v.Ende, v.Vorlage, v.Ware})
        .Select(g => new 
            {
                Key = g.Key, 
                Teile = g.Select((v,i) => new {Value = v, Index = i})
                    .GroupBy(item => item.Index / maxInGroup)
            });
    verträgeGruppiertMax.Dump();

    // Transform back into simpler style
    // SelectMany used instead select, see comment in question
    var vereinfacht = verträgeGruppiertMax
        .SelectMany(vgm => vgm.Teile
            .Select(t => new 
                { 
                    vgm.Key, 
                    a = t.Select(x => x.Value.ID)
                }));
    vereinfacht.Dump();
}

public class Vertrag 
{
    public Vertrag( // Constructor
        string id,
        string beginn,
        string ende,
        string vorlage,
        string ware)
    {
        ID = id;
        Beginn = beginn;
        Ende = ende;
        Vorlage = vorlage;
        Ware = ware;
    }

    // Fields
    public string ID { get; private set; }
    public string Beginn { get; private set; }
    public string Ende { get; private set; }
    public string Vorlage { get; private set; }
    public string Ware { get; private set; }
}
Ralf
  • 538
  • 1
  • 6
  • 17
  • The final transformation is even better if the outer .Select is a .SelectMany. Instead of a IEnum> this gives IEnum<...>, closer to the original GroupBy result – Ralf Oct 23 '14 at 12:00
  • I think the only way to do what you want is to modify the key to include the (index/3) value, but I'm not sure exactly how the rest of your code is using the results of this query. Most likely you'll have to change your code to work with the new results either way. – juharr Oct 23 '14 at 12:10

1 Answers1

0

Just implement IGrouping with your own desired behaviour. That would make your code much simpler.

The "application":

var elements = new List<Tuple<string, string, string, string>>();
elements.Add(Tuple.Create("a", "one", "A1", "Banana"));
elements.Add(Tuple.Create("a", "two", "B3", "Orange"));
elements.Add(Tuple.Create("a", "three", "C5", "Kiwi"));
elements.Add(Tuple.Create("a", "four", "D7", "Coconut"));
elements.Add(Tuple.Create("a", "five", "E9", "Maracuja"));
elements.Add(Tuple.Create("b", "one", "A1", "Banana"));
elements.Add(Tuple.Create("b", "two", "B3", "Orange"));
elements.Add(Tuple.Create("b", "three", "C5", "Kiwi"));
elements.Add(Tuple.Create("b", "four", "D7", "Coconut"));
elements.Add(Tuple.Create("b", "five", "E9", "Maracuja"));

var groups = elements.GroupBy(element => element.Item1);
var cuttedGroups = groups.Select(group => CuttedGroup.Create(group, 3));

foreach (var group in cuttedGroups)
{
    foreach (var item in group)
    {
        Console.WriteLine(item);
    }

    Console.WriteLine();
}

The implementation of CuttedGroup:

public static class CuttedGroup
{
    public static IGrouping<TKey, TElement> Create<TKey, TElement>(IGrouping<TKey, TElement> source, int maximumElements)
    {
        return new CuttedGroup<TKey, TElement>(source, maximumElements);
    }
}

public class CuttedGroup<TKey, TElement> : IGrouping<TKey, TElement>
{
    private IGrouping<TKey, TElement> _Source;
    private int _MaximumElements;

    public CuttedGroup(IGrouping<TKey, TElement> source, int maximumElements)
    {
        // Parameter check omitted...

        _Source = source;
        _MaximumElements = maximumElements;
    }
    public TKey Key
    {
        get { return _Source.Key; }
    }

    public IEnumerator<TElement> GetEnumerator()
    {
        return _Source.Take(_MaximumElements).GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}
Oliver
  • 43,366
  • 8
  • 94
  • 151