zoukankan      html  css  js  c++  java
  • Core源码(七)GroupBy

      如果大数据量的分组,而且是从数据库读取,那么分组尽可能在数据库端进行,因为大数据量的读取传输就会消耗一定量的带宽和时间,而在网页应用中,响应时间越短越好,时间越长,用户体验越糟。而在数据库中分组,读取分组后的统计数据,消耗的传输带宽会明显降低。

    这里我们介绍的是C#集合的分组,关于数据库的分组可以参考下文

    SQL夯实基础(三):聚合函数详解

    GroupBy使用

    基本使用如下

    List<IGrouping<long,StatisEmployeeLog>> group=list.GroupBy(c => c.DefeatReasonId).ToList();
    var groupList =callList.GroupBy(q => new { q.AgentId, q.CallStartTime.Date }).ToList() ;

    分组后的查询

    var thisGroup = group.FirstOrDefault(c => c.Key == item.DefeatReasonId);
    var listThis = thisGroup.ToList();

    基本原理

    GroupBy在没有传comparer的时候,会创建一个基于当前TSource类型的默认的comparer。

    但不管是默认的comparer还是我们自己传的comparer,都会调用Equals和GetHashCode两个方法,所以我们需要重载这两个方法。不论如何,一定要重载GetHashCode。匿名类型会自动生成对应的GetHashCode方法,而我们定义的类,要自己实现。

    比如如下

    class StudentKey : IEquatable<StudentKey>
    {
        public int Age { get; set; }
        public string Class { get; set; }
    
        public override int GetHashCode()
        {
            return Age.GetHashCode() ^ Class.GetHashCode();
        }
        
        public bool Equals(StudentKey other)
        {
            return Age == other.Age && Class == other.Class;
        }
    }

      IGrouping<TKey,TElement>对象序列,请注意,是对象序列,而不是单个对象。由于group查询产生的IGrouping<TKey,TElement>实质上是列表的列表。因此必须使用嵌套的foreach循环来访问每一组的各个子项。外部循环可以访问每个组的Key,内部循环可以访问每个组的子项。每个组的Key可以是任何类型,如字符串、用户自定义的对象或其他

    程序通过Enumerable类中如下方法进行分组

    public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);

    内部调用的逻辑如下

    public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) =>

                new GroupedEnumerable<TSource, TKey>(source, keySelector, null);

    代码位置为corefxsrcSystem.Linq

    GroupedEnumerable类

    我们之前使用的tolist方法就是调用的这个类型下的实现

    public List<IGrouping<TKey, TSource>> ToList()

    {

        IIListProvider<IGrouping<TKey, TSource>> lookup = Lookup<TKey, TSource>.Create(_source, _keySelector, _comparer);

        return lookup.ToList();

    }

    Lookup类

      Lookup<TKey,TElement>也是一种字典,不过它是一对多,不像Dictionary<TKey,TElement>一样是一对一的。Lookup<int,int>和Dictionary<int,List<int>>是一样的。

      Lookup<TKey,TElement>的对象可以存储ToLookup方法的结果。代码如下

      ToDictionary 和ToLookUp 对对象集合的操作带来极大的方便,特别是对索引的提供。方便通过 key 来找到相应的键值,ToDictionary 转换成是键值对 关系是一 一 对应的关系且key 值是唯一的不能重复。ToLookUp 是ToDictionary 的扩展版本

    [DebuggerDisplay("Count = {Count}")]
    [DebuggerTypeProxy(typeof(SystemLinq_LookupDebugView<,>))]
    public class Lookup<TKey, TElement> : ILookup<TKey, TElement>, IIListProvider<IGrouping<TKey, TElement>>
    {
        private readonly IEqualityComparer<TKey> _comparer;
        private Grouping<TKey, TElement>[] _groupings;
        private Grouping<TKey, TElement> _lastGrouping;
        private int _count;
    
        internal static Lookup<TKey, TElement> Create<TSource>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer)
        {
            Debug.Assert(source != null);
            Debug.Assert(keySelector != null);
            Debug.Assert(elementSelector != null);
    
            Lookup<TKey, TElement> lookup = new Lookup<TKey, TElement>(comparer);
            foreach (TSource item in source)
            {
                lookup.GetGrouping(keySelector(item), create: true).Add(elementSelector(item));
            }
        
            return lookup;
        }
    
        internal Grouping<TKey, TElement> GetGrouping(TKey key, bool create)
        {
            int hashCode = InternalGetHashCode(key);
            for (Grouping<TKey, TElement> g = _groupings[hashCode % _groupings.Length]; g != null; g = g._hashNext)
            {
                if (g._hashCode == hashCode && _comparer.Equals(g._key, key))
                {
                    return g;
                }
            }
    
            if (create)
            {
                if (_count == _groupings.Length)
                {
                    Resize();
                }
    
                int index = hashCode % _groupings.Length;
                Grouping<TKey, TElement> g = new Grouping<TKey, TElement>();
                g._key = key;
                g._hashCode = hashCode;
                g._elements = new TElement[1];
                g._hashNext = _groupings[index];
                _groupings[index] = g;
                if (_lastGrouping == null)
                {
                    g._next = g;
                }
                else
                {
                    g._next = _lastGrouping._next;
                    _lastGrouping._next = g;
                }
    
                _lastGrouping = g;
                _count++;
                return g;
            }
            return null;
        }
    
        IGrouping<TKey, TElement>[] IIListProvider<IGrouping<TKey, TElement>>.ToArray()
        {
            IGrouping<TKey, TElement>[] array = new IGrouping<TKey, TElement>[_count];
            int index = 0;
            Grouping<TKey, TElement> g = _lastGrouping;
            if (g != null)
            {
                do
                {
                    g = g._next;
                    array[index] = g;
                    ++index;
                }
                while (g != _lastGrouping);
            }
    
            return array;
        }
    }
    Lookup源码

    延伸阅读

    分组失效

  • 相关阅读:
    前台线程与后台线程,AfxGetApp>GetMainWnd()与AfxGetMainWnd的不同 (转)
    详细解说 STL 排序(Sort) (转)
    (转)怎样从一个DLL中导出一个C++类
    HTTP协议之状态码详解(转)
    关于列表(ListCtrl)控件的界面基础知识
    C++序列化(转)
    vs2008修改Menu(菜单)资源ID的方法。
    怎样解决VC中滚动条最大滚动值不能超过32767的问题
    < Photoshop CS 专栏 >颜色(二)
    C#使用BinaryReader类读取二进制文件
  • 原文地址:https://www.cnblogs.com/qixinbo/p/12703471.html
Copyright © 2011-2022 走看看