使用linq分层分组数据 - 删除递归

时间:2016-02-25 11:05:04

标签: c# linq recursion refactoring hierarchy

我在这个stackoverflow中发现了一个很好的分层分组解决方案。

How can I hierarchically group data using LINQ?

它给了我很多帮助,但我想问一下如何才能获得相同的结果,但没有递归。老实说,我有一个转换它的问题,因为递归对我来说是自然的方法。任何人都可以将此方法转换为不使用递归?

用法:

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

编辑:

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;
    }
}

提前致谢

2 个答案:

答案 0 :(得分:1)

有点挑战,但是你走了:

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);
    }
}

诀窍是使用前一步结果以相反的顺序构建分组lambda表达式,并使用闭包来捕获执行lambda所需的必要信息。

答案 1 :(得分:1)

主要问题应该是为什么要删除实现中的递归?您提供的代码具有最大递归深度= groupSelectors.Length。在这种情况下,我不认为堆栈使用应该是您的关注。

Ivan提供的解决方案是正确的,但我认为它也有间接递归方法,并提供相同的堆栈消耗级别。它不是命名声明的方法调用,而是构建嵌套委托(Func)调用链。

如果你的目标是欺骗一些静态代码分析工具(他们通常不喜欢递归调用),你可以将GroupByMany的一部分提取到单独的方法中并从另一个方法中调用。