zoukankan      html  css  js  c++  java
  • 败给了IEqualityComparer

    LINQ中的Distinct方法能够帮助我们轻松地剔除集合里面相同的元素。 它提供了2个重载函数,其中一个允许我们传入IEqualityComparer<T> 接口, 给我们充分的自由来决定2个元素是否相同。

    为了实现剔除一堆string集合中相同的string,我写下了如下的代码。 注:不区分大小写

    source.Distinct(new StringComparer());

    internal class StringComparer : IEqualityComparer<string>
    {
        public bool Equals(string x, string y)
        {
            return string.Compare(x, y, true) == 0;
        }

        public int GetHashCode(string obj)
        {
            return obj.GetHashCode();
        }
    }

    结果调试的时候始终不对,结果始终不能剔除那些只是大小写有区别的字符串,如"good” 跟"Good”.

    当时因为时间紧张,就先提交了,事后发现原来还是自己不严谨,败给了GetHashCode()方法。

    印象中自己没怎么实现过IEqualityComparer, 到实现过多次的IComparer接口。在上面的例子中虽然我意识到在比较的时候忽略大小写,但是再实现GetHashCode(), 随手地用了上面的方式。

    问题就在这,既然我们认为"good” 跟"Good”是一样的,那么我们必须给他俩返回一样的hash code,但显然以我上面实现方式它俩返回的hash code是不一样的。

    在鄙人的《避免陷阱,重现Equals方法您需要注意的其中2个原则》与《Just Reflect 》 中,其实我是多次提到了这个问题,想不到这次还是马失前蹄了,罪过啊。

    所以正确的写法应该是:

    public int GetHashCode(string obj)
        {
            return obj.ToUpper().GetHashCode();
        }


    扩充话题: 既然第一次写法返回的结果不正确,这就能说明Distinct扩展方法是先调用GetHashCode(), 再调用Equals(). 因为如果是先调用Equals()方面,理论上"good” 跟"Good” 是能剔除一个的。 问题是为什么要先调用GetHashCode(), 或则换种说法是为什么Distinct方法接受IEqualityComparer接口, 而不是IComparer接口。 因为判定2个对象是否相同,只需调用Equals() 方法就好。或者说因为 ICompare接口返回的是int,而不是直接的bool类型吗?那为啥不是IEquatable呢?

    调试Framework的源代码,可以发现如下踪迹:

    static IEnumerable<TSource> DistinctIterator<TSource>(IEnumerable<TSource> source, IEqualityComparer<TSource> comparer) {
        Set<TSource> set = new Set<TSource>(comparer);
        foreach (TSource element in source)
            if (set.Add(element)) yield return element;
    }

    也就是说Distinct执行时在遍历集合的时候,是把那些非重复的元素放在了Set容器里,而非普通的List,这样的好处是因为Set是一个HashTable结构的容器类,可以提高执行效率。我们知道在HashTable里找一个元素的间复杂度为O(1). 而在List里查找的效率却是O(n)。

    因此将Distinct方法接受IEqualityComparer接口,是很有必要的, 目前按照它的实现方式时间复杂度为O(n)。 而如果改为传IEquatable接口,那么时间复杂度就得变为O(n2).

  • 相关阅读:
    servlet中getWriter和getOutputStream的区别
    一个页面访问错误的问题
    sendRedirect实现原理分析
    servlet开发细节
    tomcat 目录分析
    servlet杂谈
    SQL 查询中的like子句的另一种实现方法,速度比like快
    让复合控件的子控件获得设计时支持
    bug管理工具——Gemini
    HtmlAgilityPack获取#开头节点的XPath
  • 原文地址:https://www.cnblogs.com/anders06/p/1883778.html
Copyright © 2011-2022 走看看