代码之家  ›  专栏  ›  技术社区  ›  Tor Haugen

有lambda的distinct()?

  •  673
  • Tor Haugen  · 技术社区  · 15 年前

    对,所以我有一个可枚举的,并且希望从中获得不同的值。

    使用 System.Linq ,当然有一个扩展方法 Distinct . 在简单的情况下,它可以在没有参数的情况下使用,例如:

    var distinctValues = myStringList.Distinct();
    

    很好,但是如果我有一个可枚举的对象,需要为其指定相等性,那么唯一可用的重载是:

    var distinctValues = myCustomerList.Distinct(someEqualityComparer);
    

    相等比较器参数必须是的实例 IEqualityComparer<T> .当然,我可以这样做,但有点冗长,嗯,有点笨拙。

    我所期望的是一个需要lambda的过载,比如func<t、t、bool>:

    var distinctValues
        = myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);
    

    有人知道是否存在这样的扩展,或者类似的解决方法吗?还是我错过了什么?

    或者,是否有一种指定IEqualityComparer内联的方法(EmbarasMe)?

    更新

    我找到了安德斯·海斯伯格对 post 在一个关于这个主题的msdn论坛上。他说:

    你会遇到的问题是当两个物体比较时 等于,它们必须具有相同的GetHashCode返回值(否则 distinct内部使用的哈希表将无法正常工作)。 我们使用IEqualityComparer,因为它包兼容 在单个接口中实现Equals和GetHashCode。

    我想这是有道理的……

    18 回复  |  直到 6 年前
        1
  •  938
  •   Konrad Viltersten    9 年前
    IEnumerable<Customer> filteredList = originalList
      .GroupBy(customer => customer.CustomerId)
      .Select(group => group.First());
    
        2
  •  450
  •   Shimmy Weitzhandler 500 - Internal Server Error    8 年前

    在我看来就像你想要的 DistinctBy MoreLINQ . 然后你可以写:

    var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);
    

    这里有一个精简版的 区别于 (没有无效检查,也没有指定自己的键比较器的选项):

    public static IEnumerable<TSource> DistinctBy<TSource, TKey>
         (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
    {
        HashSet<TKey> knownKeys = new HashSet<TKey>();
        foreach (TSource element in source)
        {
            if (knownKeys.Add(keySelector(element)))
            {
                yield return element;
            }
        }
    }
    
        3
  •  25
  •   Anestis Kivranoglou    9 年前

    把事情总结起来 .我想大多数像我一样来到这里的人都希望 最简单的 可能的解决方案 不使用任何库 尽可能地 性能 .

    (对于我来说,我认为被接受的分组方法在性能上是一种过度杀伤力。)

    下面是一个使用 质量公司 也适用于空值的接口。

    用法:

    var filtered = taskList.DistinctBy(t => t.TaskExternalId).ToArray();
    

    扩展方法代码

    public static class LinqExtensions
    {
        public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> items, Func<T, TKey> property)
        {
            GeneralPropertyComparer<T, TKey> comparer = new GeneralPropertyComparer<T,TKey>(property);
            return items.Distinct(comparer);
        }   
    }
    public class GeneralPropertyComparer<T,TKey> : IEqualityComparer<T>
    {
        private Func<T, TKey> expr { get; set; }
        public GeneralPropertyComparer (Func<T, TKey> expr)
        {
            this.expr = expr;
        }
        public bool Equals(T left, T right)
        {
            var leftProp = expr.Invoke(left);
            var rightProp = expr.Invoke(right);
            if (leftProp == null && rightProp == null)
                return true;
            else if (leftProp == null ^ rightProp == null)
                return false;
            else
                return leftProp.Equals(rightProp);
        }
        public int GetHashCode(T obj)
        {
            var prop = expr.Invoke(obj);
            return (prop==null)? 0:prop.GetHashCode();
        }
    }
    
        4
  •  19
  •   Uwe Keim    10 年前

    不,没有这样的扩展方法重载。我发现这在过去让我自己很沮丧,因此我通常编写一个助手类来处理这个问题。目标是转换 Func<T,T,bool> IEqualityComparer<T,T> .

    例子

    public class EqualityFactory {
      private sealed class Impl<T> : IEqualityComparer<T,T> {
        private Func<T,T,bool> m_del;
        private IEqualityComparer<T> m_comp;
        public Impl(Func<T,T,bool> del) { 
          m_del = del;
          m_comp = EqualityComparer<T>.Default;
        }
        public bool Equals(T left, T right) {
          return m_del(left, right);
        } 
        public int GetHashCode(T value) {
          return m_comp.GetHashCode(value);
        }
      }
      public static IEqualityComparer<T,T> Create<T>(Func<T,T,bool> del) {
        return new Impl<T>(del);
      }
    }
    

    这允许您编写以下内容

    var distinctValues = myCustomerList
      .Distinct(EqualityFactory.Create((c1, c2) => c1.CustomerId == c2.CustomerId));
    
        5
  •  14
  •   tdog    8 年前

    速记解决方案

    myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());
    
        6
  •  12
  •   Gordon Freeman    14 年前

    这是你想要的,但我不知道性能:

    var distinctValues =
        from cust in myCustomerList
        group cust by cust.CustomerId
        into gcust
        select gcust.First();
    

    至少它不冗长。

        7
  •  10
  •   David Kirkland    13 年前

    这里有一个简单的扩展方法,可以满足我的需要…

    public static class EnumerableExtensions
    {
        public static IEnumerable<TKey> Distinct<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector)
        {
            return source.GroupBy(selector).Select(x => x.Key);
        }
    }
    

    遗憾的是,他们没有将这样一种独特的方法烘焙到框架中,但是,嘿,呵呵。

        8
  •  4
  •   Kleinux    15 年前

    我用过的对我很有用的东西。

    /// <summary>
    /// A class to wrap the IEqualityComparer interface into matching functions for simple implementation
    /// </summary>
    /// <typeparam name="T">The type of object to be compared</typeparam>
    public class MyIEqualityComparer<T> : IEqualityComparer<T>
    {
        /// <summary>
        /// Create a new comparer based on the given Equals and GetHashCode methods
        /// </summary>
        /// <param name="equals">The method to compute equals of two T instances</param>
        /// <param name="getHashCode">The method to compute a hashcode for a T instance</param>
        public MyIEqualityComparer(Func<T, T, bool> equals, Func<T, int> getHashCode)
        {
            if (equals == null)
                throw new ArgumentNullException("equals", "Equals parameter is required for all MyIEqualityComparer instances");
            EqualsMethod = equals;
            GetHashCodeMethod = getHashCode;
        }
        /// <summary>
        /// Gets the method used to compute equals
        /// </summary>
        public Func<T, T, bool> EqualsMethod { get; private set; }
        /// <summary>
        /// Gets the method used to compute a hash code
        /// </summary>
        public Func<T, int> GetHashCodeMethod { get; private set; }
    
        bool IEqualityComparer<T>.Equals(T x, T y)
        {
            return EqualsMethod(x, y);
        }
    
        int IEqualityComparer<T>.GetHashCode(T obj)
        {
            if (GetHashCodeMethod == null)
                return obj.GetHashCode();
            return GetHashCodeMethod(obj);
        }
    }
    
        9
  •  3
  •   Dmitry Ledentsov    12 年前

    我在这里看到的所有解决方案都依赖于选择一个已经具有可比性的领域。如果需要用不同的方式进行比较, this solution here 一般来说,它的作用是:

    somedoubles.Distinct(new LambdaComparer<double>((x, y) => Math.Abs(x - y) < double.Epsilon)).Count()
    
        10
  •  3
  •   Bob    8 年前

    换一种方式:

    var distinctValues = myCustomerList.
    Select(x => x._myCaustomerProperty).Distinct();
    

    序列返回不同的元素,并按属性“_myCauseTomerProperty”对它们进行比较。

        11
  •  2
  •   Community CDub    8 年前

    你可以使用 内联矩阵

    public class InlineComparer<T> : IEqualityComparer<T>
    {
        //private readonly Func<T, T, bool> equalsMethod;
        //private readonly Func<T, int> getHashCodeMethod;
        public Func<T, T, bool> EqualsMethod { get; private set; }
        public Func<T, int> GetHashCodeMethod { get; private set; }
    
        public InlineComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
        {
            if (equals == null) throw new ArgumentNullException("equals", "Equals parameter is required for all InlineComparer instances");
            EqualsMethod = equals;
            GetHashCodeMethod = hashCode;
        }
    
        public bool Equals(T x, T y)
        {
            return EqualsMethod(x, y);
        }
    
        public int GetHashCode(T obj)
        {
            if (GetHashCodeMethod == null) return obj.GetHashCode();
            return GetHashCodeMethod(obj);
        }
    }
    

    使用示例 :

      var comparer = new InlineComparer<DetalleLog>((i1, i2) => i1.PeticionEV == i2.PeticionEV && i1.Etiqueta == i2.Etiqueta, i => i.PeticionEV.GetHashCode() + i.Etiqueta.GetHashCode());
      var peticionesEV = listaLogs.Distinct(comparer).ToList();
      Assert.IsNotNull(peticionesEV);
      Assert.AreNotEqual(0, peticionesEV.Count);
    

    来源: https://stackoverflow.com/a/5969691/206730
    Using IEqualityComparer for Union
    Can I specify my explicit type comparator inline?

        12
  •  1
  •   Arturo Menchaca    9 年前

    要做到这一点,一个棘手的方法是使用 Aggregate() 扩展,使用字典作为 关键特性 值作为键:

    var customers = new List<Customer>();
    
    var distincts = customers.Aggregate(new Dictionary<int, Customer>(), 
                                        (d, e) => { d[e.CustomerId] = e; return d; },
                                        d => d.Values);
    

    还有一个 群组风格 解决方案正在使用 ToLookup() :

    var distincts = customers.ToLookup(c => c.CustomerId).Select(g => g.First());
    
        13
  •  1
  •   Валентин Миронов    6 年前

    您可以使用lambdaeQualityComparer:

    var distinctValues
        = myCustomerList.Distinct(new LambdaEqualityComparer<OurType>((c1, c2) => c1.CustomerId == c2.CustomerId));
    
    
    public class LambdaEqualityComparer<T> : IEqualityComparer<T>
        {
            public LambdaEqualityComparer(Func<T, T, bool> equalsFunction)
            {
                _equalsFunction = equalsFunction;
            }
    
            public bool Equals(T x, T y)
            {
                return _equalsFunction(x, y);
            }
    
            public int GetHashCode(T obj)
            {
                return obj.GetHashCode();
            }
    
            private readonly Func<T, T, bool> _equalsFunction;
        }
    
        14
  •  0
  •   MattH    15 年前

    我假设您有一个IEnumerable,在您的示例委托中,您希望c1和c2引用这个列表中的两个元素?

    我相信你可以通过自我加入来实现这个目标 var distinctresults=来自mylist中的c1 在我的列表中加入C2

        15
  •  0
  •   Jon Egerton Aditya kumar sahoo    13 年前

    如果 Distinct() 不会产生独特的结果,请尝试以下方法:

    var filteredWC = tblWorkCenter.GroupBy(cc => cc.WCID_I).Select(grp => grp.First()).Select(cc => new Model.WorkCenter { WCID = cc.WCID_I }).OrderBy(cc => cc.WCID); 
    
    ObservableCollection<Model.WorkCenter> WorkCenter = new ObservableCollection<Model.WorkCenter>(filteredWC);
    
        16
  •  0
  •   Niall Connaughton    10 年前

    这个 Microsoft System.Interactive package 具有采用键选择器lambda的distinct版本。这实际上与JonSkeet的解决方案相同,但它可能有助于人们了解,并查看图书馆的其余部分。

        17
  •  0
  •   Matt    8 年前

    你可以这样做:

    public static class Extensions
    {
        public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query,
                                                        Func<T, V> f, 
                                                        Func<IGrouping<V,T>,T> h=null)
        {
            if (h==null) h=(x => x.First());
            return query.GroupBy(f).Select(h);
        }
    }
    

    此方法允许您通过指定一个参数来使用它,例如 .MyDistinct(d => d.Name) ,但它还允许您将HAVING条件指定为第二个参数,如下所示:

    var myQuery = (from x in _myObject select x).MyDistinct(d => d.Name,
            x => x.FirstOrDefault(y=>y.Name.Contains("1") || y.Name.Contains("2"))
            );
    

    N.B. 这还允许您指定其他函数,例如 .LastOrDefault(...) 也。


    如果您只想公开条件,可以通过将其实现为:

    public static IEnumerable<T> MyDistinct2<T, V>(this IEnumerable<T> query,
                                                    Func<T, V> f,
                                                    Func<T,bool> h=null
                                                    )
    {
        if (h == null) h = (y => true);
        return query.GroupBy(f).Select(x=>x.FirstOrDefault(h));
    }
    

    在这种情况下,查询看起来就像:

    var myQuery2 = (from x in _myObject select x).MyDistinct2(d => d.Name,
                        y => y.Name.Contains("1") || y.Name.Contains("2")
                        );
    

    N.B. 这里,表达式更简单,但是注意 .MyDistinct2 使用 .FirstOrDefault(...) 隐含地。


    注: 上面的示例使用以下演示类

    class MyObject
    {
        public string Name;
        public string Code;
    }
    
    private MyObject[] _myObject = {
        new MyObject() { Name = "Test1", Code = "T"},
        new MyObject() { Name = "Test2", Code = "Q"},
        new MyObject() { Name = "Test2", Code = "T"},
        new MyObject() { Name = "Test5", Code = "Q"}
    };
    
        18
  •  0
  •   Quality Catalyst hrincong    8 年前

    IEnumerable lambda扩展名:

    public static class ListExtensions
    {        
        public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, int> hashCode)
        {
            Dictionary<int, T> hashCodeDic = new Dictionary<int, T>();
    
            list.ToList().ForEach(t => 
                {   
                    var key = hashCode(t);
                    if (!hashCodeDic.ContainsKey(key))
                        hashCodeDic.Add(key, t);
                });
    
            return hashCodeDic.Select(kvp => kvp.Value);
        }
    }
    

    用法:

    class Employee
    {
        public string Name { get; set; }
        public int EmployeeID { get; set; }
    }
    
    //Add 5 employees to List
    List<Employee> lst = new List<Employee>();
    
    Employee e = new Employee { Name = "Shantanu", EmployeeID = 123456 };
    lst.Add(e);
    lst.Add(e);
    
    Employee e1 = new Employee { Name = "Adam Warren", EmployeeID = 823456 };
    lst.Add(e1);
    //Add a space in the Name
    Employee e2 = new Employee { Name = "Adam  Warren", EmployeeID = 823456 };
    lst.Add(e2);
    //Name is different case
    Employee e3 = new Employee { Name = "adam warren", EmployeeID = 823456 };
    lst.Add(e3);            
    
    //Distinct (without IEqalityComparer<T>) - Returns 4 employees
    var lstDistinct1 = lst.Distinct();
    
    //Lambda Extension - Return 2 employees
    var lstDistinct = lst.Distinct(employee => employee.EmployeeID.GetHashCode() ^ employee.Name.ToUpper().Replace(" ", "").GetHashCode());