最近在工作中遇到了一个小的功能,就是需要向一个服务发送请求命令,需要判断请求是否发生变化,如果发生变化了,则重新请求。该问题实际上就是判断两个集合是否相等,只需要记录最后一次请求的元素的集合,然后将其和最新一次进行比较是否相等。需要说明的是这里定义的集合相等是指:两个集合如果元素值一样并且出现的次数也一样,即使顺序不一样也认为是相等,比如集合A={1,2,3,4,4,5} 集合B={1,4,4,2,3,5} 这两个集合也认为是相等的。后面讨论的集合相等都是基于这一假设的。

    就这么个简单的问题,也有不同种解决方法,这里和大家分享一下。

方法一 使用Dictionary计数来实现

    这种方法思路很简单,创建一个Dictionary对象,将第一个集合中的元素作为key添加到Dictionary中,value即为出现的次数。然后遍历第二个集合,如果包含相同的key,则value减1,如果不好含,则直接返回false,表示两个集合不同。最后,如果Dictionary中所有key对应的value都为0即表示两个集合相等,否则不相等。

/// <summary>
/// 判断两个集合是否相等,相等 表示元素值及出现的次数一样即可,顺序可以不一样。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list1"></param>
/// <param name="list2"></param>
/// <returns></returns>
public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2)
{
    //如果集合个数不相等,则集合不同
    if (list1.Count() != list2.Count()) return false;

    var cnt = new Dictionary<T, int>();
    foreach (T s in list1)
    {
        if (cnt.ContainsKey(s))
        {
            cnt[s]++;
        }
        else
        {
            cnt.Add(s, 1);
        }
    }
    foreach (T s in list2)
    {
        if (cnt.ContainsKey(s))
        {
            cnt[s]--;
        }
        else //如果第二个集合中有第一个集合中未包含的元素,表示两个集合不同
        {
            return false;
        }
    }
    return cnt.Values.All(c => c == 0);
}

    算法需要对象实现IEquatable接口,而该接口一般对象均默认实现。

    以上算法的效率是很高的,时间复杂度为O(N),因为对Dictionary的查找时间复杂度为O(1),所以主要时间都花在遍历集合上。

      以上方法对.NET不同的版本都兼容,如果是.NET 2.0版本也非常容易改造,只需要把最后一句代码改为遍历即可。由于项目原因,本人开发环境为2.0所以才用的是该方法。

方法二 使用IEnumerable的SequenceEqual 扩展方法

    在.NET 3.5 中,比较集合元素的相等性有了新的方法,IEnumerable接口提供了名为SequenceEqual的方法,该方法用于判断两个序列是否顺序相等,即两个源序列的长度相等,且其相应元素相等。

    我们可以先对两个集合进行排序,然后直接调用Enumerable.SequenceEqual 方法即可,这大概是最简单的实现方法了。

public static bool ScrambledEqualsUsingSequenceEqual<T>(IEnumerable<T> list1, IEnumerable<T> list2)
{
    return Enumerable.SequenceEqual(list1.OrderBy(t => t), list2.OrderBy(t => t));
}

    需要注意的是,如果要自定义相等特性,需要实现IEquatable<T>接口,并提供自定义的Equal和GetHashCode实现。

方法三 使用CollectionAssert.AreEquivalent方法

    在Visual Studio的单元测试框架中,位于Microsoft.VisualStudio.TestTools.UnitTesting命名空间下,以及在NUnit中都存在有CollectionAssert.AreEquivalent方法,该方法的解释是:

Two collections are equivalent if they have the same elements in the same quantity, but in any order. Elements are equal if their values are equal, not if they refer to the same object.

    从定义可以看出 这正是我们定义的集合相等性。所以可以直接使用该方法。需要注意的是该命名空间位于Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll这个dll中。

    使用Reflector工具,可以查看其具体实现,对其代码进行简单修改可以看到其原理如下:

public class MultiSetComparer<T> : IEqualityComparer<IEnumerable<T>>
{
    public bool Equals(IEnumerable<T> first, IEnumerable<T> second)
    {
        if (first == null)
            return second == null;

        if (second == null)
            return false;

        if (ReferenceEquals(first, second))
            return true;

        var firstCollection = first as ICollection<T>;
        var secondCollection = second as ICollection<T>;
        if (firstCollection != null && secondCollection != null)
        {
            if (firstCollection.Count != secondCollection.Count)
                return false;

            if (firstCollection.Count == 0)
                return true;
        }

        return !HaveMismatchedElement(first, second);
    }

    private static bool HaveMismatchedElement(IEnumerable<T> first, IEnumerable<T> second)
    {
        int firstCount;
        int secondCount;

        var firstElementCounts = GetElementCounts(first, out firstCount);
        var secondElementCounts = GetElementCounts(second, out secondCount);

        if (firstCount != secondCount)
            return true;

        foreach (var kvp in firstElementCounts)
        {
            firstCount = kvp.Value;
            secondElementCounts.TryGetValue(kvp.Key, out secondCount);

            if (firstCount != secondCount)
                return true;
        }

        return false;
    }

    private static Dictionary<T, int> GetElementCounts(IEnumerable<T> enumerable, out int nullCount)
    {
        var dictionary = new Dictionary<T, int>();
        nullCount = 0;

        foreach (T element in enumerable)
        {
            if (element == null)
            {
                nullCount++;
            }
            else
            {
                int num;
                dictionary.TryGetValue(element, out num);
                num++;
                dictionary[element] = num;
            }
        }

        return dictionary;
    }

    public int GetHashCode(IEnumerable<T> enumerable)
    {
        int hash = 17;

        foreach (T val in enumerable.OrderBy(x => x))
            hash = hash * 23 + val.GetHashCode();

        return hash;
    }
}

    其实现方法原理和第一个方法类似,首先是进行了一系列条件判断,是否为空,元素个数是否相等等等。然后也是创建一个Dictionary进行元素个数计算,并比较。

一种集合“相等性”的实现的更多相关文章

  1. JAVA:三种集合LIST、SET、MAP

    1. 集合框架介绍 我 们知道,计算机的优势在于处理大量的数据,在编程开发中,为处理大量的数据,必须具备相应的存储结构,数组可以用来存储并处理大量类型相同的数 据,但是会发现数组在应用中的限制:数组长 ...

  2. Windows Phone中的几种集合控件

    前言 Windows Phone开发过程中不可避免的就是和集合数据打交道,如果之前做过WP App的开发的话,相信你已经看过了各种集合控件的使用.扩展和自定义.这些个内容在这篇博客里都没有,那么我们今 ...

  3. Spring学习(三)几种集合属性的注入方式

    1.前言 众所周知.java中不只有八大简单类型.还有一些集合类型.本文围绕集合类型的注入做一个总结. 2.项目骨架 3.过程 1.创建实体类AllCollectionType package com ...

  4. Redis中7种集合类型应用场景&redis常用命令

    Redis常用数据类型 Redis最为常用的数据类型主要有以下五种: String Hash List Set Sorted set 在具体描述这几种数据类型之前,我们先通过一张图了解下Redis内部 ...

  5. OC中几种集合的遍历方法(数组遍历,字典遍历,集合遍历)

    // 先分别初始化数组.字典和集合,然后分别用for循环.NSEnumerator枚举器和forin循环这三个方法来实现遍历 NSArray *array = @[@"yinhao" ...

  6. Redis中7种集合类型应用场景

    StringsStrings 数据结构是简单的key-value类型,value其实不仅是String,也可以是数字.使用Strings类型,你可以完全实现目前 Memcached 的功能,并且效率更 ...

  7. Java测试开发--Set、Map、List三种集合(四)

    1.集合类型主要有3种:set(集).list(列表)和map(映射). 2.三者关系 3.Set set接口是Collection接口的一个子接口,是无序的,set去重,也就是说set中不存在两个这 ...

  8. Java常用的几种集合, Map集合,Set集合,List集合

    Java中  Object是所有类的根 Java集合常用的集合List集合.Set集合.Map集合 Map接口常用的一些方法 size() 获取集合中名值对的数量 put(key k, value v ...

  9. Java基础知识强化之集合框架笔记73:如何选择使用哪种集合

    1. 到底使用那种集合.    看需求 是否是键值对象形式: 是:Map 键是否需要排序: 是:TreeMap 否:HashMap 不知道,就使用HashMap. 否:Collection 元素是否唯 ...

随机推荐

  1. AIX 查看当前目录内最大的10个目录

       du  | sort -r n| head -n10 du  查看目录大小: srot  排序,-r 选择倒序,-n选项 看作数值排序(否则将作为字符排序): herd   -n10  查看开通 ...

  2. python hashlib模块

    用于加密相关的操作,3.x里代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5 算法 import hashlib m=hash ...

  3. 【java】:常用工具类

    PS; 平时用到的一些工具类,验证非空.字符切割.时间转换.金额转换 package com.jws.common.util; import java.io.UnsupportedEncodingEx ...

  4. ios页面弹出方式《笔记》

    1.presentViewController 方式,动画效果是从底部弹出,主要用在除导航类页面的弹出 let anotherVC = UIStoryboard(name: "Main&qu ...

  5. Hadoop2.6.0的FileInputFormat的任务切分原理分析(即如何控制FileInputFormat的map任务数量)

    前言 首先确保已经搭建好Hadoop集群环境,可以参考<Linux下Hadoop集群环境的搭建>一文的内容.我在测试mapreduce任务时,发现相比于使用Job.setNumReduce ...

  6. codesmith生成java类

    今天生成的时候的时候找不到类型转换的文件JavaAlias 后台发现我装完codesmith后,没有把类型转换类放入对应的文件 把System-JavaTableNameComments.csmap和 ...

  7. padding与margin的区别

    padding    是控件的内容相对控件的边缘的边距. margin      是控件边缘相对父空间的边距. android:gravity 属性是对该view 内容的限定.比如一个button 上 ...

  8. CentOS 7 关闭防火墙

    CentOS 7.0默认使用的是firewall作为防火墙 直接关闭防火墙 systemctl stop firewalld.service #停止firewall systemctl disable ...

  9. Raab判别法确定级数是否收敛

  10. 服务器TIME_WAIT和CLOSE_WAIT详解和解决办法

    转载的服务器TIME_WAIT和CLOSE_WAIT详解和解决办法