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

    1. git config简介 我们知道config是配置的意思,那么git config命令就是对git进行一些配置.而配置一般都是写在配置文件里面,那么git的配置文件在哪里呢?互动一下,先问下大 ...

  2. ubuntu 12.10 sourcelist软件更新源列表(zz)

        ubuntu 12.10正式版已经发布了,国内各大开源软件源也陆续更新了资源.今天分享一下ubuntu 12.10 软件更新源列表. 首先,备份一下ubuntu 12.04 原来的源地址列表文 ...

  3. 如何让openvpn在windows启动时自动加载

    在非常需要vpn的人群中,他们几乎一整天都连接着vpn,但是每次开机都要连接vpn的过程有时候比较繁琐对于新手而言.这篇文章主要是说明如何在windows启动时自动加载openvpn自动连接,该教程适 ...

  4. C# System.Timers.Timer的一些小问题?

    比如设置间隔时间是 1000msSystem.Timers.Timer mytimer = new System.Timers.Timer(1000);问题若响应函数执行的时间超过了 1000 ms, ...

  5. 检查或遍历android手机应程

    检查android手机中是否存在某应程 public boolean checkApp(String packageName) {        if (packageName == null || ...

  6. C#中的using和yield return混合使用

    最近写代码为了为了省事儿用了几个yield return,因为我不想New一个List<T>或者T[]对象再往里放元素,就直接返回IEnumerable<T>了.我的代码里还有 ...

  7. JavaScript-BOM-history:保存当前窗口打开后成功访问过的url历史记录栈

    history:保存当前窗口打开后成功访问过的url历史记录栈history.go(n):前进n步前进一步:history.go(1);后退一步:history.go(-1);刷新:history.g ...

  8. AutoMapper扩展

    扩展类:AutoMapExtention using System; using System.Collections.Generic; using System.Linq.Expressions; ...

  9. winfrom调用FastReport模板

    '存放模板的路径 If Dir(Application.StartupPath & "\ReportsFile\HB\01\IN.frx", FileAttribute.N ...

  10. VMware 克隆 Linux 系统后找不到 eth0 网卡问题(转)

    [问题描述] 使用 VMware 虚拟机的克隆功能,快速复制已安装好的 Linux 系统. 克隆完成之后,发现没有 eth0 网卡. [解决方法] 1. 编辑 /etc/udev/rules.d/70 ...