Distinct源码分析

以前比较两个List数据,筛选出所需要的数据时候,一直套两层for循环来执行。用到去重(Distinct)的时候,这两个需求其实都是一样的,都是需要比较两个集合,查看了下它的源码,里面确实有值得借鉴的地方。

先附上源码一直我调试的代码,大部分地方加上了注释

  1 class Program
  2     {
  3         static void Main(string[] args)
  4         {
  5             //初始化集合
  6             List<Point> points = new List<Point>()
  7             {
  8                 new Point() {X=1,Y=2},
  9                 new Point() {X=7,Y=2},
 10                 new Point() {X=2,Y=2},
 11                 new Point() {X=2,Y=3},
 12                 new Point() {X=3,Y=2},
 13                 new Point() {X=4,Y=2},
 14                 new Point() {X=5,Y=2},
 15                 new Point() {X=6,Y=3},
 16                 new Point() {X=2,Y=3}
 17             };
 18             var distinctPoints = DistinctIterator(points, new PointCompare()).ToList();
 19             Console.Read();
 20         }
 21 
 22         //调用Distinct 方法 内部调的这个方法  抽取出来了
 23         static IEnumerable<TSource> DistinctIterator<TSource>(IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
 24         {
 25             Set<TSource> set = new Set<TSource>(comparer);
 26             foreach (TSource element in source)
 27                 if (set.Add(element))
 28                     //若返回true则添加到集合中
 29                     yield return element;
 30         }
 31     }
 32 
 33     struct Point
 34     {
 35         public int X { get; set; }
 36 
 37         public int Y { get; set; }
 38     }
 39 
 40     class PointCompare : IEqualityComparer<Point>
 41     {
 42 
 43 
 44         public bool Equals(Point x, Point y)
 45         {
 46             return x.X == y.X && x.Y == y.Y;
 47             //return false;
 48         }
 49 
 50         public int GetHashCode(Point obj)
 51         {
 52             //  return 1;
 53             return obj.X.GetHashCode() ^ obj.Y.GetHashCode();
 54         }
 55     }
 56 
 57     internal class Set<TElement>
 58     {
 59         int[] buckets;
 60         Slot[] slots;
 61         int count;
 62         int freeList;
 63         IEqualityComparer<TElement> comparer;
 64 
 65         public Set() : this(null) { }
 66 
 67         //初始化
 68         public Set(IEqualityComparer<TElement> comparer)
 69         {
 70             //若comparer为null则string、int...基元类型
 71             if (comparer == null) comparer = EqualityComparer<TElement>.Default;
 72             this.comparer = comparer;
 73             //大小为7
 74             buckets = new int[7];
 75             //大小为7
 76             slots = new Slot[7];
 77             freeList = -1;
 78         }
 79 
 80         // If value is not in set, add it and return true; otherwise return false
 81         public bool Add(TElement value)
 82         {
 83             //若Find返回true则代表已重复,则不会添加到集合中
 84             return !Find(value, true);
 85         }
 86 
 87         // Check whether value is in set 
 88         //没用到
 89         public bool Contains(TElement value)
 90         {
 91             return Find(value, false);
 92         }
 93 
 94         // If value is in set, remove it and return true; otherwise return false 
 95         //没用到
 96         public bool Remove(TElement value)
 97         {
 98             int hashCode = InternalGetHashCode(value);
 99             int bucket = hashCode % buckets.Length;
100             int last = -1;
101             for (int i = buckets[bucket] - 1; i >= 0; last = i, i = slots[i].next)
102             {
103                 if (slots[i].hashCode == hashCode && comparer.Equals(slots[i].value, value))
104                 {
105                     if (last < 0)
106                     {
107                         buckets[bucket] = slots[i].next + 1;
108                     }
109                     else
110                     {
111                         slots[last].next = slots[i].next;
112                     }
113                     slots[i].hashCode = -1;
114                     slots[i].value = default(TElement);
115                     slots[i].next = freeList;
116                     freeList = i;
117                     return true;
118                 }
119             }
120             return false;
121         }
122 
123         bool Find(TElement value, bool add)
124         {
125             //调用comparer的GetHashCode
126             int hashCode = InternalGetHashCode(value);
127             //根据hash值取余运算  查找相同结果的数据 比较hash值以及调用IEqualityComparer.Equals方法,若相同  则代表存在相同数据
128             for (int i = buckets[hashCode % buckets.Length] - 1; i >= 0; i = slots[i].next)
129             {
130                 if (slots[i].hashCode == hashCode && comparer.Equals(slots[i].value, value)) return true;
131             }
132             if (add)
133             {
134                 int index;
135                 //没用到。。。
136                 if (freeList >= 0)
137                 {
138                     index = freeList;
139                     freeList = slots[index].next;
140                 }
141                 else
142                 {
143                     //当循环数量大于slots.Length值(初始7)时则会扩充slots,buckets的数量,以及重置对应hash值的取余
144                     if (count == slots.Length) Resize();
145                     index = count;
146                     count++;
147                 }
148                 //hash取余
149                 int bucket = hashCode % buckets.Length;
150                 //设置hash值
151                 slots[index].hashCode = hashCode;
152                 //指向当前的元素
153                 slots[index].value = value;
154                 //指向相同hash取余的上一个元素索引*  有了这个才能够在判断是否重复的地方减少比较量
155                 slots[index].next = buckets[bucket] - 1;
156                 //指向最近的相同元素
157                 buckets[bucket] = index + 1;
158             }
159             return false;
160         }
161 
162         void Resize()
163         {
164             //不理解要+1  保证奇数?
165             int newSize = checked(count * 2 + 1);
166             int[] newBuckets = new int[newSize];
167             Slot[] newSlots = new Slot[newSize];
168             Array.Copy(slots, 0, newSlots, 0, count);
169             for (int i = 0; i < count; i++)
170             {
171                 int bucket = newSlots[i].hashCode % newSize;
172                 newSlots[i].next = newBuckets[bucket] - 1;
173                 newBuckets[bucket] = i + 1;
174             }
175             buckets = newBuckets;
176             slots = newSlots;
177         }
178 
179         internal int InternalGetHashCode(TElement value)
180         {
181             //[....] DevDivBugs 171937. work around comparer implementations that throw when passed null
182             return (value == null) ? 0 : comparer.GetHashCode(value) & 0x7FFFFFFF;
183         }
184 
185         internal struct Slot
186         {
187             //value的hash值
188             internal int hashCode;
189             //元素
190             internal TElement value;
191             //指向下一个元素索引
192             internal int next;
193         }
194     }

 

着重说说它去重的思路,代码可以拷出来调试一步一步走,会对下面的更能理解

他用到了hash环的思路,初始大小是7,图随便画的,丑陋还请海涵

1.第一个Point元素hash值取余为3
solts[0]=当前元素 next=-1 hash=3
buckets[3]=1 指向第一个元素索引+1    其实也是当前的count数

2.第二个Point元素hash值取余为5
solts[1]=当前元素 next=-1 hash=5
buckets[5]=2 指向第二个元素索引+1
....
3.假设第三个Point元素hash值为10,取余为3
会对相同取余结果值的元素比较,目前来说也就是第一步的Point元素 比较对应的hash值以及调用Equals方法 返回false(上面两部也会比较,但因为没有元素所以未强调)
则solts[2]=当前元素 next=0(这样子在第128行进行比较的时候就产生了一个类似链表性质) hash=10
buckets[3]=3 指向第三个元素索引+1

4.假设第四个Point元素跟第一个元素相同 也就是hash也为3 

同样也会执行第128行  根据 buckets[3] 得到对应的第一个元素为3-1=2  取solts[2] 得到第三步的元素,进行比较, 很显然两个元素不相同,根据对应的next也就是0得到第一个元素solts[0]进行比较  返回true也就是存在相同元素,则去掉当前重复项。

很显然你会发现第二个元素没有进行比较,这样子就会较少数据的比较次数。

当元素越来越多的时候 144行的时候就会执行他的Resize方法,会将大小*2+1(为啥要+1?),对应的hash取余结果重写来计算

你会发现Distinct里面对GetHashCode依赖特别大,我发现很多人实现IEqualityComparer<T>接口时不会关注他的GetHashCode方法(只是返回了一个特定的int值),而只是实现了里面的Equals方法,通过上面的解释你会发现这样子其实比我们用两次for循环开销很大,所以强烈建议重写他的GetHashCode方法。

强调一点,当两个元素的相同时,那他的hashcode肯定也是相同的

希望这样的思路能在大家的coding中有所帮助

转载于:https://www.cnblogs.com/sjr10/p/Distinct.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/460221.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java语言 编译原理_【Java学习】深入分析Java的编译原理

在《Java代码的编译与反编译》中&#xff0c;有过关于Java语言的编译和反编译的介绍。我们可以通过javac命令将Java程序的源代码编译成Java字节码&#xff0c;即我们常说的class文件。这是我们通常意义上理解的编译。但是&#xff0c;字节码并不是机器语言&#xff0c;要想让机…

实验 使用 vivado zedboard GPIO 开关 开控制 LED

前面我做了几个实验 都没有用过 开关&#xff0c;这一次用一用 发现 vivado 真的挺方便 所以 使用 vivado 开发 1.建工程 我使用 vivado 2013.4 创建新工程 –》 next –》next 勾选 Do not specify sources at this time //这样跳过后面两个添加文件页面 选择 board –》 ze…

java 最优化_java-多维度求最优解

拿出11条数据//条件每个位置(position)的人数限制每队(team)人数不能超过7人credits的总和在100分之内(包含100)总分(points)最高//位置人数限制position-1 : 1 position-2 : 3-5 position-3 : 1-3 position-4 : 3-5//模拟数据{points credits position team56 9.0 1 t154 9.1 …

必应(Bing)每日图片获取API

必应(Bing)每日图片获取API January 11, 2015 API http://lab.dobyi.com/api/bing.php 介绍 ValueDescriptiontitle标题desc描述url图片地址你们自由发挥……

java取模多位数_JAVA大数类—基础操作(加减乘除、取模、四舍五入、设置保留位数)...

当基础数据类型长度无法满足需求时可以使用大数类构造方法接受字符串为参数1 BigInteger bInt new BigInteger("123123");2 BigDecimal bDouble new BigDecimal("123123.123123124");基础操作(取模使用divideAndRemainder方法&#xff0c;返回的数组第二…

HDU 4902

数据太弱&#xff0c;直接让我小暴力一下就过了&#xff0c;一开始没注意到时间是15000MS&#xff0c;队友发现真是太给力了 #include <cstdio> #include <cstring> int n,q,a[100005],x[100005],p,l[100005],r[100005],t[100005]; int tree[1000005]; void build(…

tcp/udp高并发和高吐吞性能测试工具

在编写一个网络服务的时候都比较关心这个服务能达到多少并发连接,而在这连接的基础上又能达到一个怎样的交互能力.编写服务已经是一件很花力气的事情,而还要去编写一个能够体现结果的测试工具就更加消耗工作时间.下面介绍一个测试工具只需要简单地设置一下就能对tcp/udp服务进行…

几个数字的和

ctrl z 的使用 #include<iostream> using namespace std;main() {int num,sum0;while(cin>>num) {sumnum;}cout<<"和为"<<sum<<endl; } View Code#include<iostream> using namespace std; main() { int num,s…

网站在线压力测试工具Load Impact

关于Load ImpactLoad Impact是一个一个在线的网站压力测试服务及工具&#xff0c;模拟多用户同时访问你的站点&#xff0c;并出具报告以分析你的站点可以支撑的访问者数量&#xff0c;它能让你通过简单的几次点击就能测试出你的网站的性能。不过免费用户只能同时并发50个虚拟访…

sc.next在java什么意思_sc.next() 和 nextLine 的原理

对java的Scanner类的next开头的相关类有点纠结&#xff0c;看了一些博客大致懂了&#xff0c;整理下代码事例直接参考了这位大佬的https://blog.csdn.net/long71751380/article/details/94008351. 总的原理以一段代码为例,scanner类import java.util.Scanner;public class Next…

WPF RichTextBox相关总结

由于公司涉及到聊天对话框的功能&#xff0c;就想到了RichTextBox&#xff0c;查阅相关资料&#xff0c;总结下&#xff1a; 一、RichTextBox的内容相关的类 1.1RichTextBox的内容结构 RichTexBox是个可编辑控件&#xff0c;可编辑我们很容易想到word的可编辑&#xff0c;在wor…

python 内置方法赋值_Python内置数据结构之字符串str

1. 数据结构回顾所有标准序列操作(索引、切片、乘法、成员资格检查、长度、最小值和最大值)都适用于字符串&#xff0c;但是字符串是不可变序列&#xff0c;因此所有的元素赋值和切片赋值都是非法的。>>> website http://www.python.org>>> website[-3:] c…

以下是关于ASP.NET中保存各种信息的对象的比较,理解这些对象的原理,对制作完善的程序来说是相当有必要的(摘至互联网,并非原创--xukunping)...

在ASP.NET中&#xff0c;有很多种保存信息的对象.例如:APPlication,Session,Cookie,ViewState和Cache等,那么它们有什么区别呢?每一种对象应用的环境是什么? 为了更清楚的了解,我们总结出每一种对象应用的具体环境,如下表所示: 方法信息量大小保存时间应用范围保存位置App…

实现每个点赞用户点击的带属性的字符串

2019独角兽企业重金招聘Python工程师标准>>> #pragma mark - 点击各个点赞用户-(void)setClicked:(TweetCell *)cell andOSCTweet:(OSCTweet *)tweet {NSMutableAttributedString *attributedString [[NSMutableAttributedString alloc] initWithString:tweet.like…

xampp php5.6 7.1共存,New XAMPP with PHP 7.2.8, 7.1.20, 7.0.31 5.6.37

嗨&#xff0c;阿帕奇的朋友们&#xff01;We just released new versions of XAMPP for all platforms with the latest PHP versions: 7.2.8, 7.1.20, 7.0.31 & 5.6.37.您可以下载这些新版本http://www.apachefriends.org/download.html.7.2.8 / 7.1.20 / 7.0.31 / 5.6.3…

基于.net开发chrome核心浏览器【四】

原文:基于.net开发chrome核心浏览器【四】一&#xff1a; 上周去北京出差&#xff0c;给国家电网的项目做架构方案&#xff0c;每天都很晚睡&#xff0c;客户那边的副总也这样拼命工作。 累的不行了&#xff0c;直接导致第四篇文章没有按时发出来。 希望虚心学习1&#xff0c;小…

php5.6 pdo.dll 没有,php5.6没有pdo怎么办

php5.6没有pdo是因为在php5.6中php已经内置了pdo功能&#xff0c;只需要在php.ini文件中将“extensionphp_pdo_firebird.dll”等配置项打开即可。推荐&#xff1a;《PHP视频教程》php5.6中没有php.pdo.dll文件我下载的php 5.6要使用pdo模块&#xff0c;但是通过百度发现发现没有…

网页的背景图片代码

网页背景图片代码1.(最普遍类) <style>body{background-image:url(logo.gif);background-repeat:no-repeat;background-position:center}</style> 说明:以上代码为网页背景图片固定代码&#xff01;网页背景图片固定代码&#xff0c;这样&#xff0c;当向下拉网页时…

andriod 新建 Activity_ Form (详细设置)

参考&#xff1a; Starting Another Activity 去创建Activity New->Other->Android->Android Activity->BlankActivity&#xff1a; 输入对应的信息&#xff1a; 创建完毕后&#xff0c;可以看到新建了对应的 src下面的java文件 layout下面的xml 等等&#xff1a; …

php laypage,layui laypage组件常见用法总结

laypage 的使用非常简单&#xff0c;指向一个用于存放分页的容器&#xff0c;通过服务端得到一些初始值&#xff0c;即可完成分页渲染。核心方法&#xff1a; laypage.render(options) 来设置基础参数。一、laypage的常用基础参数layui.use([laypage], function () {laypage l…