C#集合中如何去除重复项?
于是你百度了一波,找到以下解决方案。
第一种:两次循环遍历
List<int> repeatList= new List<int>() { 1, 2, 3, 4, 5, 3, 3, 2, 1};
for (int i = 0; i < repeatList.Count; i++) //外循环是循环的次数
{
for (int j = repeatList.Count - 1 ; j > i; j--) //内循环是 外循环一次比较的次数
{
if (repeatList[i] == repeatList[j])
{
repeatList.RemoveAt(j);
}
}
}
第二种:叫你引入Linq,然后Distinct
List<int> repeatList= new List<int>() { 1, 2, 3, 4, 5, 3, 3, 2, 1};
var removeRepeatList = repeatList.Distinct();
foreach (var item in removeRepeatList)
{
//去除重复项后
}
第二种好像很高端,代码也少。就用第二种啦。唉呀,但是案例里面集合存的int值,现在项目里面集合存的是类怎么弄?应该是一样的吧,然后一整狂撸代码。
//需去重复的类
public class Person
{
private string firstName;
private string lastName;
public string FirstName { get { return firstName; } set { firstName = value; } }
public string LastName { get { return lastName; } set { lastName = value; } }
public Person() { }
public Person(string firstName, string lastName)
{
this.firstName = firstName;
this.lastName = lastName;
}
public override string ToString()
{
return string.Format(firstName + lastName);
}
}
using System.Collections.Generic;
namespace Utils
{
//方便打印集合的工具类
public class LogHelper
{
public static string List2Str<T>(IEnumerable<T> list)
{
string str = "";
string suffix = "-";
foreach (var item in list)
{
str += item + suffix;
}
//移除掉最后末尾那个"-"
str = str.Remove(str.Length-1, 1);
return str;
}
}
}
List<Person> persons = new List<Person>();
persons.Add(new Person("张", "三"));
persons.Add(new Person("李", "四"));
persons.Add(new Person("王", "五"));
persons.Add(new Person("张", "三"));
persons.Add(new Person("张", "三"));
Debug.Log("去掉重复前: " + LogHelper.List2Str(persons));
var persionsRemoveRepeatList = persons.Distinct();
Debug.Log("去掉重复后: " + LogHelper.List2Str(persionsRemoveRepeatList));
然后噼里啪啦运行,纳尼?竟然没得啥子卵用。代码写错啦,然后一阵检查,没错啊。方式不对?然后一阵百度,找到的都是些去除int/string类型集合的,心里骂了一句娘,垃圾百度。然后想起可以去MSDN查啊,唉,你这沙雕。
MSDN怎么说?
唉呀,我的乖乖,看不懂看不懂。o_o …
然后又是一阵搜索,找到了方案。
方案是单独再写一个比较类,其继承自IEqualityComparer,然后实现Equals()和GetHashCode()方法。
这简单。
using System.Collections.Generic;
class PersonCompare : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if((x.FirstName == y.FirstName) && (x.LastName == y.LastName))
{
return true;
}
return false;
}
public int GetHashCode(Person obj)
{
return obj.GetHashCode();
}
}
噫,好像哪里不对,为何要同时实现Equals()和GetHashCode(),GetHashCode()内部如何实现?先不管,先跑起来看一下到。激动人心的时刻来了!
Debug.Log("去掉重复前: " + LogHelper.List2Str(persons));
var persionsRemoveRepeatList = persons.Distinct(new PersonCompare());
Debug.Log("去掉重复后: " + LogHelper.List2Str(persionsRemoveRepeatList));
纳尼!还是不行,一定是哪里有错了!绝壁不是我代码的问题!(哈哈,不是你傻逼代码的问题就是你傻逼的问题)
然后你机智的想到了去比较类里面答应输出一波。
using System.Collections.Generic;
class PersonCompare : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
UnityEngine.Debug.Log("比较相等 x:" + x + " y:" + y);
if ((x.FirstName == y.FirstName) && (x.LastName == y.LastName))
{
UnityEngine.Debug.Log("比较相等....");
return true;
}
return false;
}
public int GetHashCode(Person obj)
{
UnityEngine.Debug.Log("GetHashCode: " + obj.ToString());
return obj.GetHashCode();
}
}
然后惊奇的发现只执行了PersonCompare里的GetHashCode(),压根没执行Equals()方法。
这是怎么回事?好像理解不了了。对了,不是还有个问题没解决得嘛。
为何要同时实现Equals()和GetHashCode(),GetHashCode()内部如何实现?
然后你如饥似渴的查找原因。
直到你发现了这儿。
原来Distinct()内部会去先检查HashCode是否相等,若不相等压根儿就不会去执行Equals()判断两个元素相等。
那么问题来了,什么是HashCode?其实就是根据对象的地址算出来的一个int数字,即对象的哈希码值,代表了该对象在内存中的存储位置。因为代表内存地址,也就是说每个对象的HashCode都不一样。也就是说我们重写的GetHashCode()不对,不能这样写?
public int GetHashCode(Person obj)
{
return obj.GetHashCode();
}
那该如何实现GetHashCode()呢?
具体原则如下,特别是第四条,是我查了好多文章才看到一大佬说的。
第一,如果两个对象相等,它们必须产生相同的散列码,即GetHashCode()返回的值必须一样;
第二,对于任意对象obj,obj.GetHashCode()必须是一个实例不变式,无论在obj上调用什么方法,obj.GetHashCode必须返回同样的值;
第三,散列函数应该在所有整数中产生一个随机的分布,这样才能获得效率的提升
前三点是基本原则,第四点是实战原则。
第四,对象中用作Equals()方法比较标准的Filed(成员变量/类属性)都应该用来计算HashCode();
综上,我们修改HaseCode()方法。
public int GetHashCode(Person obj)
{
//UnityEngine.Debug.Log("GetHashCode: " + obj.ToString());
return obj.FirstName.GetHashCode() ^ obj.LastName.GetHashCode();
}
然后运行!
成功啦!怎么没得好鸡动呢,嘻嘻。
要解决一个看似简单的问题,可能需要各个方面的知识,如果某一方面知识欠缺,可能就会卡在那儿。所以要不断地积累知识,知道得越多,解决问题的能力就越强,看待问题的眼光也会比之前高出一大截。不断地去记录和总结自己所学的所想的,假以时日,会怎么样?我们拭目以待。
项目上传到GitHub了。
下次再会。