Linq排序扭曲

时间:2015-10-11 15:59:31

标签: c# performance linq

我有以下记录

enter image description here

最后2个是记录3和4的孩子,我希望能够按数量对记录进行排序,但是应该先将兴趣(父母)的兴趣排序,然后再排序孩子应该出现在这之后,例如它就像这样

2000
2000
20001
99.84 (child of the above)
50000
249.58 (child of the above)

基本上我希望我的数量可以忽略那个“IsInterest”设置为true的那个,但是让它们显示在父母之后。

我可以通过首先将所有父母带入一个新的集合来做到这一点..然后通过父母看看是否有任何孩子然后在新集合中的父母之后插入它们但我觉得这不是有效和脏代码,所以我想我会问可能有人知道黑魔法。 排序还应该知道数量上的asc / desc。

我可以发布我的代码,将该系列拆开并将它放在一起,如果它有帮助但我尽量不使用该代码。

如果有帮助,我的排序方法会使用“升序”或“降序”字符串

谢谢

UPDATE2 我会指出,只有两个级别,孩子们只有一个父母(没有祖父母),每个父母最多只有一个孩子

根据请求

更新代码(字段名称可能与数据库字段不同..)

 switch (sortMember.ToUpper())
            {
                case "AMOUNT":
                    {
                        //check to see if any imputed interests exist
                        if (contributions.Any(x => x.IsImputedInterest))
                        {
                            var children = contributions.Where(x => x.IsImputedInterest);
                            var sortedColl = contributions.Where(x => x.IsImputedInterest == false).OrderByWithDirection(x => x.ContributionAmount, sortDirection.ToUpper() == "DESCENDING").ToList();
                            foreach (var child  in children )
                            {
                                //find the parent
                                var parentIndex = sortedColl.FindIndex(x => x.ContributionId == child.ParentContirbutionId);
                                sortedColl.Insert(parentIndex+1, child);

                            }

                        }
                        else
                        {
                            contributions = contributions.OrderByWithDirection(x => x.ContributionAmount, sortDirection.ToUpper() == "DESCENDING");
                        }

                        break;
                    }
            }

.................

 public static IOrderedEnumerable<TSource> OrderByWithDirection<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, bool descending)
    {
        return descending ? source.OrderByDescending(keySelector)
                          : source.OrderBy(keySelector);
    }

    public static IOrderedQueryable<TSource> OrderByWithDirection<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector, bool descending)
    {
        return descending ? source.OrderByDescending(keySelector)
                          : source.OrderBy(keySelector);
    }

2 个答案:

答案 0 :(得分:2)

这是Linq解决方案的单一陈述:

var desc = order == "descending";
var result = list
    //group parents with it's children
    .GroupBy(x => x.ParentId ?? x.Id)
    //move the parent to the first position in each group
    .Select(g => g.OrderBy(x => x.ParentId.HasValue).ThenBy(x => desc ? -x.Amount : x.Amount))
    //sort the groups by parents' amounts
    .OrderBy(g => desc ? -g.First().Amount : g.First().Amount)
    //retrieve the items from each group
    .SelectMany(g => g);

一些表现提示:

  • 如果您最多只有一个孩子,或者您不关心孩子的订单,您可以放弃ThenBy(...)
  • 使用if语句检查订单并使用两个版本的语句 - 第二个使用OrderByDescending / ThenByDescending,然后删除三元运算符(desc ? ... : ... ) - 否则将对每个项目进行评估

对于您当前的解决方案,我没有对性能提供任何保证 - 它可能会变得更慢。

答案 1 :(得分:1)

您可以使用以下通用方法(不受父级/子级的级别或数量限制):

public static class Extensions
{
    public static IEnumerable<T> ThenByHierarchy<T, TKey>(this IOrderedEnumerable<T> source, Func<T, TKey> keySelector, Func<T, TKey> parentKeySelector)
    {
        var itemByKey = source.ToDictionary(keySelector);
        var processSet = new HashSet<T>();
        var stack = new Stack<T>();
        foreach (var item in itemByKey.Values)
        {
            for (var next = item; processSet.Add(next); )
            {
                stack.Push(next);
                if (!itemByKey.TryGetValue(parentKeySelector(next), out next)) break;
            }
            while (stack.Count != 0)
                yield return stack.Pop();
        }
    }
}

只需将其附加到OrderBy序列的末尾,就像这样

var result = contributions
    .OrderByWithDirection(x => x.ContributionAmount, sortDirection.ToUpper() == "DESCENDING")
    .ThenByHierarchy(x => x.ContributionId, x => x.ParentContirbutionId);

更新:事实证明,事情并非如此简单。虽然上面的方法为叶元素以及元素与其父元素提供了正确的顺序,但它没有正确地对父元素进行排序。正确的一个如下(使用另一个可重复使用的方法How to flatten tree via LINQ?,所以如果我们不计算它并不比前一个大得多):

public static class Extensions
{
    public static IEnumerable<T> HierarchicalOrder<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector, Func<T, TKey> parentKeySelector, Func<IEnumerable<T>, IOrderedEnumerable<T>> order)
    {
        // Collect parent/child relation info
        var itemById = source.ToDictionary(keySelector);
        var childListById = new Dictionary<TKey, List<T>>();
        var rootList = new List<T>();
        foreach (var item in itemById.Values)
        {
            var parentKey = parentKeySelector(item);
            List<T> childList;
            if (parentKey == null || !itemById.ContainsKey(parentKey))
                childList = rootList;
            else if (!childListById.TryGetValue(parentKey, out childList))
                childListById.Add(parentKey, childList = new List<T>());
            childList.Add(item);
        }
        // Traverse the tree using in-order DFT and applying the sort on each sublist
        return order(rootList).Expand(item =>
        {
            List<T> childList;
            return childListById.TryGetValue(keySelector(item), out childList) ? order(childList) : null;
        });
    }
    public static IEnumerable<T> Expand<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> elementSelector)
    {
        var stack = new Stack<IEnumerator<T>>();
        var e = source.GetEnumerator();
        try
        {
            while (true)
            {
                while (e.MoveNext())
                {
                    var item = e.Current;
                    yield return item;
                    var elements = elementSelector(item);
                    if (elements == null) continue;
                    stack.Push(e);
                    e = elements.GetEnumerator();
                }
                if (stack.Count == 0) break;
                e.Dispose();
                e = stack.Pop();
            }
        }
        finally
        {
            e.Dispose();
            while (stack.Count != 0) stack.Pop().Dispose();
        }
    }
}

并且您的案例用法很简单

var result = contributions
    .HierarchicalOrder(x => x.ContributionId, x => x.ParentContirbutionId, c =>
    .OrderByWithDirection(x => x.ContributionAmount, sortDirection.ToUpper() == "DESCENDING"));