深入理解C# Unity List集合去除重复项 Distinct

18 篇文章 5 订阅

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查啊,唉,你这沙雕。
自定义类的集合直接Distinct()无效

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()
这是怎么回事?好像理解不了了。对了,不是还有个问题没解决得嘛。
为何要同时实现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了
下次再会。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值