0

I found in this stackoverflow nice solution for hierarchical grouping.

How can I hierarchically group data using LINQ?

It helped me a lot, but I would like to ask how I can achieve the same result, but without recursion. Honestly I got a problem to convert it, because recursion is here natural approach for me. Anyone can convert this method to not use recursion?

Usage:

var result = customers.GroupByMany(c => c.Country, c => c.City);

Edited:

public class GroupResult
{
    public object Key { get; set; }
    public int Count { get; set; }
    public IEnumerable<GroupResult> SubGroups { get; set; }
    public override string ToString()
    { return string.Format("{0} ({1})", Key, Count); }
}

public static class MyEnumerableExtensions
{
    public static IEnumerable<GroupResult> GroupByMany<TElement>(
        this IEnumerable<TElement> elements,
        params Func<TElement, object>[] groupSelectors)
    {
        if (groupSelectors.Length > 0)
        {
            var selector = groupSelectors.First();

            //reduce the list recursively until zero
            var nextSelectors = groupSelectors.Skip(1).ToArray();
            return
                elements.GroupBy(selector).Select(
                    g => new GroupResult
                    {
                        Key = g.Key,
                        Count = g.Count(),
                        SubGroups = g.GroupByMany(nextSelectors)
                    });
        }
        return null;
    }
}

Thanks in advance

Community
  • 1
  • 1
Piotr Czarnecki
  • 1,688
  • 3
  • 14
  • 22

2 Answers2

1

A bit challenging, but here you go:

public class GroupResult<T>
{
    public object Key { get; set; }
    public int Count { get; set; }
    public IEnumerable<T> Items { get; set; }
    public IEnumerable<GroupResult<T>> SubGroups { get; set; }
    public override string ToString() { return string.Format("{0} ({1})", Key, Count); }
}

public static class MyEnumerableExtensions
{
    public static IEnumerable<GroupResult<TElement>> GroupByMany<TElement>(
        this IEnumerable<TElement> elements,
        params Func<TElement, object>[] groupSelectors)
    {
        Func<IEnumerable<TElement>, IEnumerable<GroupResult<TElement>>> groupBy = source => null;
        for (int i = groupSelectors.Length - 1; i >= 0; i--)
        {
            var keySelector = groupSelectors[i]; // Capture
            var subGroupsSelector = groupBy; // Capture
            groupBy = source => source.GroupBy(keySelector).Select(g => new GroupResult<TElement>
            {
                Key = g.Key,
                Count = g.Count(),
                Items = g,
                SubGroups = subGroupsSelector(g)
            });
        }
        return groupBy(elements);
    }
}

The trick is to build the grouping lambda expressions in reverse order, using the previous step result and also using closure to capture the necessary information needed to execute the lambda.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
1

The main question should be why do you want to get rid of the recursion from your implementation? The code you provided has max depth of recursion = groupSelectors.Length. I do not think stack usage should be your concern in that case.

The solution provided by Ivan is correct but I think it also has indirect recursive approach, and offers same stack consumption level. Instead of named declared method call, it builds the chain of nested delegate (Func) calls.

If your goal is to cheat some static code analysis tool (they usually do not like recursive calls) you can extract part of the GroupByMany into separate method and call one from another.