最近在工作中遇到了一个小的功能,就是需要向一个服务发送请求命令,需要判断请求是否发生变化,如果发生变化了,则重新请求。该问题实际上就是判断两个集合是否相等,只需要记录最后一次请求的元素的集合,然后将其和最新一次进行比较是否相等。需要说明的是这里定义的集合相等是指:两个集合如果元素值一样并且出现的次数也一样,即使顺序不一样也认为是相等,比如集合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. display转块状化

        display:block           block元素会独占一行,多个block元素会各自新起一行.默认情况下,block元素宽度自动填满其父元素宽度.         block元素 ...

  2. IIS配置excel 权限

    http://www.cnblogs.com/zhuxiaohui/archive/2013/10/16/3371637.html

  3. android应用中增加权限判断

    android6.0系统允许用户管理应用权限,可以关闭/打开权限. 所以需要在APP中增加权限判断,以免用户关闭相应权限后,APP运行异常. 以MMS为例,在系统设置——应用——MMS——权限——&g ...

  4. Spring 容器

    Spring提供了两个核心接口:BeanFactory和ApplicationContext,其中applicationContext是BeanFactory的子接口. 他们都可代表Spring容器, ...

  5. 封装ios静态库碰到的一些问题(二)

    在静态库建立好了之后呢,于是应用程序就引用它,加上拷贝的h文件,但是引用之后Build之后提示很多sybmbol 重复 于是进行检查,确实由于是从其他工程修改过来的,很多基础库都引用了,删除之,最后就 ...

  6. js中replace的回调函数使用。

    这只是一个小问题,但是之前并没有发现.这个问题就是replace的第二个函数是支持回调函数的. var ext = new RegExp('f','g'); 1.str.replace(ext ,1) ...

  7. SVN版本控制系统

    SVN 版本控制系统 1.SVN作用 防止代码丢失 : 因为没有哪个项目能够一次性开发完成 代码版本回退 : 你可以在开发过程中找到以前上传到服务器上面的所有版本 多人代码整合 : 公司中多个人开发同 ...

  8. 关于js单页面实现跳转原理以及利用angularjs框架路由实现单页面跳转

    还记得我们刚开始学习html时使用的锚节点实现跳转吗? <a href="#target">我想跳转至目标位置</a> <p>第一条</p ...

  9. Linux系统布置java项目

    一.远程服务器 Linux系统是没有Windows那样可视化的界面的,所以首先我们需要一个远程Linux服务器的软件,有好多种,比较好用的XShell,下载地址:http://rj.baidu.com ...

  10. 用户行为数据采集核心思维(APP、web数据采集/埋点)

    关于数据采集(也就是所谓的埋点),有很多中形式,或者说方法.所有的数据采集都时围绕一个核心的三个点来做区别的处理. 数据采集核心思维三个点: 1.对象: 要采集谁,一个页面.一个按钮,页面或者按钮,就 ...