0

Suppose that I have an enumerable on which I have applied .GroupBy() twice to end up with nested IGroupings:

IEnumerable<IGrouping<TOuterKey, IGrouping<TInnerKey, TElement>>> source = enumerable.GroupBy(o => o.InnerKey).GroupBy(g => g.OuterKey);

For nested IEnumerables, it is easy to just apply .SelectMany() to get a flattened IEnumerable with all of the results. For IGroupings, however, this is not so simple. Suppose I wanted a generalised extension method to do this:

public static IEnumerable<IGrouping<TResultKey, TResultElement>> SelectMany<TOuterKey, TInnerKey, TInputElement, TResultKey, TResultElement>(
    this IEnumerable<IGrouping<TOuterKey, IGrouping<TInnerKey, TInputElement>>> source,
    Func<IGrouping<TOuterKey, IGrouping<TInnerKey, TInputElement>>, TResultKey> keySelector,
    Func<TOuterKey, TInnerKey, TInputElement, TResultElement> valueSelector)

How would I create that?

Philip Atz
  • 886
  • 1
  • 10
  • 26
  • 4
    Seems to me you've got the start of it; the signature of the method. Keep going! Try stuff! Fail! Try again! This is how we learn. We don't learn by asking other people (or AIs) to think for us. If you get hung up somewhere with an error you don't get, come back and someone will help you get past that. – Heretic Monkey Aug 04 '23 at 19:27
  • 1
    The starting point is impossible. `IGrouping` doesn't have a property `OuterKey`. You should supply a [mre] that clarifies your question and makes it answerable. – Gert Arnold Aug 04 '23 at 19:52
  • 1
    You'll also have to create your own class to implement `IGrouping` since sadly .Net doesn't make its implementation usable (unless you can figure out how to use `GroupBy` or `ToLookup` in your method). – NetMage Aug 04 '23 at 20:39
  • BTW, I would recommend using `LINQPad` and testing with a sample double grouped enumeration to consider what you need to do. `Dump` is very helpful as you build the result. It is a one liner :) – NetMage Aug 04 '23 at 21:02

1 Answers1

0

I left some time between posting the question and the answer, to give anyone wanting to give this a try a chance, but now I'll give you my answer:

public static IEnumerable<IGrouping<TResultKey, TResultElement>> GroupSelectMany<TOuterKey, TInnerKey, TSourceElement, TResultKey, TResultElement>(
    this IEnumerable<IGrouping<TOuterKey, IGrouping<TInnerKey, TSourceElement>>> source,
    Func<IGrouping<TOuterKey, IGrouping<TInnerKey, TSourceElement>>, TResultKey> keySelector,
    Func<TOuterKey, TInnerKey, TSourceElement, TResultElement> elementSelector)
{
    elementSelector = elementSelector ?? ((outerKey, innerKey, element) => (TResultElement)(object)element);
    
    return source.SelectMany(
        gg => new IGrouping<TResultKey, TResultElement>[]
        {
            new Grouping<TResultKey, TResultElement>(
                keySelector(gg),
                gg.SelectMany(
                    g => g.Select(
                        o => elementSelector(gg.Key, g.Key, o))))
        }); 
}

You will also need an implementation of IGrouping, which I have found here, but which I am only including as private for encapsulation purposes:

private class Grouping<TKey, TElement> : List<TElement>, IGrouping<TKey, TElement>
{
    public Grouping(TKey key) : base() => Key = key;
    public Grouping(TKey key, int capacity) : base(capacity) => Key = key;
    public Grouping(TKey key, IEnumerable<TElement> collection)
        : base(collection) => Key = key;
    public TKey Key { get; }
}
Philip Atz
  • 886
  • 1
  • 10
  • 26