zoukankan      html  css  js  c++  java
  • C#黔驴技巧之去重(Distinct)

    前言

    关于C#中默认的Distinct方法在什么情况下才能去重,这个就不用我再多讲,针对集合对象去重默认实现将不再满足,于是乎我们需要自定义实现来解决这个问题,接下来我们详细讲解几种常见去重方案,孰好孰歹自行判之。

    分组

    首先给出我们需要用到的对象,如下:

    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    接下来我们添加100万条数据到集合中,如下:

    var list = new List<Person>();
    for (int i = 0; i < 1000000; i++)
    {
        list.Add(new Person() { Age = 18, Name = "jeffcky" });
    }

    接下来我们对年龄和名称进行分组,然后取第一条即可达到去重,如下:

    list = list.GroupBy(d => new { d.Age, d.Name })
        .Select(d => d.FirstOrDefault())
        .ToList();

    扩展方法(HashSet去重)

    我们知道在C#中HashSet对于重复元素会进行过滤筛选,所以我们写下如下扩展方法,遍历集合元素,最后利用HashSet进行过滤达到去重目的,如下:

    public static IEnumerable<TSource> Distinct<TSource, TKey>(
            this IEnumerable<TSource> source,
            Func<TSource, TKey> keySelector)
        {
            var hashSet = new HashSet<TKey>();
            
            foreach (TSource element in source)
            {
                if (hashSet.Add(keySelector(element)))
                {
                    yield return element;
                }
            }
        }

    最后调用上述扩展方法即可去重,如下:

     list = list.Distinct(d => new { d.Age, d.Name }).ToList();

    扩展方法(IEqualityComparer去重)

    在实际项目中有很多通过具体实现类实现该接口,通过重写Equals和HashCode比较属性值来达到去重目的,因为对于每一个类都得实现对应比较器,所以并不通用,反而利用上述方式才是最佳,其实我们大可借助该比较接口实现通用解决方案,对于每一个类都得实现一个比较器的原因在于,我们将属性比较放在类该接口内部,如果我们将属性比较放在外围呢,这个时候就达到了通用解决方案,那么我们怎么实现呢,通过委托来实现,实现该接口的本质无非就是比较HashCode,然后通过Equals比较其值,当比较HashCode时,我们强制其值为一个常量(比如0),当重写Equals方法我们调用委托即可,如下:

    public static class Extensions
    {
        public static IEnumerable<T> Distinct<T>(
            this IEnumerable<T> source, Func<T, T, bool> comparer)
            where T : class
            => source.Distinct(new DynamicEqualityComparer<T>(comparer));
    
        private sealed class DynamicEqualityComparer<T> : IEqualityComparer<T>
            where T : class
        {
            private readonly Func<T, T, bool> _func;
    
            public DynamicEqualityComparer(Func<T, T, bool> func)
            {
                _func = func;
            }
    
            public bool Equals(T x, T y) => _func(x, y);
    
            public int GetHashCode(T obj) => 0;
        }
    }

    最终通过指定属性进行比较即可去重,如下:

    list = list.Distinct((a, b) => a.Age == b.Age && a.Name == b.Name).ToList();

    性能比较

    以上3种常见方式我们已经介绍完毕了,当数据量比较小时,我们大可忽略对集合进行各种操作所带来的性能,但是一旦数据量很大时,我们可能需要考虑性能,能节省一点时间或许有必要,于是乎,在上述100万条数据前提下,我们来分析其耗时情况,如下:

    var list = new List<Person>();
    for (int i = 0; i < 1000000; i++)
    {
        list.Add(new Person() { Age = 18, Name = "jeffcky" });
    }
    
    var time1 = Time(() =>
    {
        list.GroupBy(d => new { d.Age, d.Name })
            .Select(d => d.FirstOrDefault())
            .ToList();
    });
    Console.WriteLine($"分组耗时:{time1}");
    
    var time2 = Time(() =>
    {
        list.Distinct(d => new { d.Age, d.Name }).ToList();
    });
    Console.WriteLine($"HashSet耗时:{time2}");
    
    var time3 = Time(() =>
    {
        list.Distinct((a, b) => a.Age == b.Age && a.Name == b.Name).ToList();
    });
    Console.WriteLine($"委托耗时:{time3}");
    
    
    static long Time(Action action)
    {
        var stopwatch = new Stopwatch();
        stopwatch.Start();
        action();
        stopwatch.Stop();
        return stopwatch.ElapsedMilliseconds;
    }

    总结

    上述结果耗时大小比较理论应该不会出现逆转的情况,只是多少的问题,数据量较少时理论上差异也很明显,本文对于去重方式只是基于性能角度来分析,还是那句话大部分情况下,我们完全不需要考虑这些问题,不过,作为程序员的我们可能也想写出高性能、高质量的代码吧,有时候多考虑考虑也无妨,对自身有个好的代码质量要求也未尝不可,也还是那句话,孰好孰歹,自行判之。 

  • 相关阅读:
    Serialize and Deserialize Binary Tree
    sliding window substring problem汇总贴
    10. Regular Expression Matching
    《深入理解计算机系统》(CSAPP)读书笔记 —— 第七章 链接
    程序员如何写一份合格的简历?(附简历模版)
    9个提高代码运行效率的小技巧你知道几个?
    《深入理解计算机系统》(CSAPP)读书笔记 —— 第六章 存储器层次结构
    24张图7000字详解计算机中的高速缓存
    《深入理解计算机系统》(CSAPP)实验四 —— Attack Lab
    《深入理解计算机系统》(CSAPP)读书笔记 —— 第五章 优化程序性能
  • 原文地址:https://www.cnblogs.com/CreateMyself/p/12863407.html
Copyright © 2011-2022 走看看