LINQ:去重值

149

我有一个来自XML的以下物品集:

id           category

5            1
5            3
5            4
5            3
5            3

我需要一个不重复的这些项的列表:

5            1
5            3
5            4

如何在LINQ中同时对类别和ID进行区分?

8个回答

237

你是否试图按多个字段进行去重?如果是的话,只需使用匿名类型和Distinct运算符即可:

var query = doc.Elements("whatever")
               .Select(element => new {
                             id = (int) element.Attribute("id"),
                             category = (int) element.Attribute("cat") })
               .Distinct();

如果你想获取一个“较大”类型的不同值集,但只考虑一些子集属性的唯一性方面,那么你可能需要使用MoreLINQ中实现的DistinctBy函数, 该函数在DistinctBy.cs中:

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

(如果将null作为比较器传递,它将使用键类型的默认比较器。)


哦,所以你说的“更大的类型”是指我仍然希望在结果中获得所有属性,即使我只想比较几个属性来确定不同性? - Nate Anderson
@TheRedPea:是的,完全正确。 - Jon Skeet

35

31

除了Jon Skeet的答案外,您还可以使用group by表达式来获取唯一组及每个组的迭代次数:

var query = from e in doc.Elements("whatever")
            group e by new { id = e.Key, val = e.Value } into g
            select new { id = g.Key.id, val = g.Key.val, count = g.Count() };

6
你写道“除了Jon Skeet的回答之外”……我不知道这是否可能。 ;) - Yehuda Makarov

13

如果还有人在寻找方法,这里提供另一种实现自定义lambda比较器的方式。

public class LambdaComparer<T> : IEqualityComparer<T>
    {
        private readonly Func<T, T, bool> _expression;

        public LambdaComparer(Func<T, T, bool> lambda)
        {
            _expression = lambda;
        }

        public bool Equals(T x, T y)
        {
            return _expression(x, y);
        }

        public int GetHashCode(T obj)
        {
            /*
             If you just return 0 for the hash the Equals comparer will kick in. 
             The underlying evaluation checks the hash and then short circuits the evaluation if it is false.
             Otherwise, it checks the Equals. If you force the hash to be true (by assuming 0 for both objects), 
             you will always fall through to the Equals check which is what we are always going for.
            */
            return 0;
        }
    }

您可以为Linq的Distinct方法创建一个扩展,该扩展可以接受Lambda表达式。

   public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list,  Func<T, T, bool> lambda)
        {
            return list.Distinct(new LambdaComparer<T>(lambda));
        }  

使用方法:

var availableItems = list.Distinct((p, p1) => p.Id== p1.Id);

查看参考源代码,Distinct使用哈希集合来存储已经生成的元素。始终返回相同的哈希码意味着每次都要检查先前返回的每个元素。更强大的哈希码会加快速度,因为它只会与同一哈希桶中的元素进行比较。零是一个合理的默认值,但支持第二个lambda用于哈希码可能是值得的。 - Darryl
好的观点!等我有时间了我会尝试编辑,如果你现在正在这个领域工作,随时可以进行编辑。 - Ricky Gummadi

9

我有点晚回答,但是如果你想要整个元素而不仅仅是你想要分组的值,你可能需要这样做:

var query = doc.Elements("whatever")
               .GroupBy(element => new {
                             id = (int) element.Attribute("id"),
                             category = (int) element.Attribute("cat") })
               .Select(e => e.First());

这将为您提供与您的分组选择匹配的第一个完整元素,就像Jon Skeet的第二个示例使用DistinctBy一样,但不需要实现IEqualityComparer comparer。如果性能不是问题,DistinctBy很可能会更快,但上面的解决方案将涉及更少的代码。

4
// First Get DataTable as dt
// DataRowComparer Compare columns numbers in each row & data in each row

IEnumerable<DataRow> Distinct = dt.AsEnumerable().Distinct(DataRowComparer.Default);

foreach (DataRow row in Distinct)
{
    Console.WriteLine("{0,-15} {1,-15}",
        row.Field<int>(0),
        row.Field<string>(1)); 
}

2

既然我们谈论的是确保每个元素只出现一次,对我来说“集合”更有意义。

以下是使用类和IEqualityComparer实现的示例:

 public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public Product(int x, string y)
        {
            Id = x;
            Name = y;
        }
    }

    public class ProductCompare : IEqualityComparer<Product>
    {
        public bool Equals(Product x, Product y)
        {  //Check whether the compared objects reference the same data.
            if (Object.ReferenceEquals(x, y)) return true;

            //Check whether any of the compared objects is null.
            if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
                return false;

            //Check whether the products' properties are equal.
            return x.Id == y.Id && x.Name == y.Name;
        }
        public int GetHashCode(Product product)
        {
            //Check whether the object is null
            if (Object.ReferenceEquals(product, null)) return 0;

            //Get hash code for the Name field if it is not null.
            int hashProductName = product.Name == null ? 0 : product.Name.GetHashCode();

            //Get hash code for the Code field.
            int hashProductCode = product.Id.GetHashCode();

            //Calculate the hash code for the product.
            return hashProductName ^ hashProductCode;
        }
    }

现在
List<Product> originalList = new List<Product> {new Product(1, "ad"), new Product(1, "ad")};
var setList = new HashSet<Product>(originalList, new ProductCompare()).ToList();

setList 将只包含唯一元素。

我在处理返回集合差异的 .Except() 时想到了这个。


0

自 .NET 6 开始,可以使用框架本身的 DistinctBy。类似以下方式(利用 value tuples):

var query = doc.Elements("whatever")
   .DistinctBy(e => ((int) e.Attribute("id"), (int) e.Attribute("cat")))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接