LINQ: Відмінні значення


136

У мене є такий набір елементів із XML:

id           category

5            1
5            3
5            4
5            3
5            3

Мені потрібен чіткий перелік цих елементів:

5            1
5            3
5            4

Як я можу розрізнити також категорію І Id в LINQ?

Відповіді:


221

Ви намагаєтесь розрізнити більше, ніж одне поле? Якщо так, просто використовуйте анонімний тип та оператор Distinct, і це повинно бути добре:

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

Якщо ви намагаєтеся отримати чіткий набір значень типу "більший", але лише дивлячись на деякий підмножина властивостей для аспекту розрізнення, ви, мабуть, хочете, DistinctByяк це реалізовано в MoreLINQ у 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як порівняльник, він використовуватиме типовий порівняльник для типу ключа.)


Так, під "більшим типом" ви можете сказати, що я все-таки хочу всі властивості в результаті, хоча я хочу лише порівняти кілька властивостей, щоб визначити виразність?
Червоний горох

@TheRedPea: Так, саме так.
Джон Скіт


27

На додаток до відповіді Джона Скіта, ви також можете використовувати групу за виразами, щоб отримати унікальні групи по w / count для кожної групи ітерацій:

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() };

4
Ви писали "крім відповіді Джона Скіта" ... я не знаю, чи таке можливо. ;)
Єгуда Макаров

13

Для будь-кого, хто все ще шукає; ось ще один спосіб реалізації користувацького порівняння лямбда.

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, яке може прийняти лямбда

   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 використовує хеш-набір для зберігання елементів, які він вже отримав. Завжди повернення одного і того ж хеш-коду означає, що кожен раніше повертається елемент перевіряється кожен раз. Більш надійний хеш-код пришвидшив би справи, оскільки він порівнював би лише елементи в одному хеш-відрі. Нуль є розумним замовчуванням, але, можливо, варто підтримати друге лямбда для хеш-коду.
Дарріл

Гарна думка! Я спробую редагувати, коли отримаю час, якщо ви зараз працюєте в цьому домені, не соромтесь редагувати
Ricky G

8

Я трохи запізнююся з відповіддю, але ви можете зробити це, якщо вам потрібен цілий елемент, а не лише значення, за якими ви хочете згрупувати:

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

Це дасть вам перший цілий елемент, що відповідає вашій групі за вибором, подібно до другого прикладу Джона Скекета, використовуючи DistinctBy, але не застосовуючи порівняння IEqualityComparer. 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)); 
}

0

Оскільки ми говоримо про наявність кожного елемента рівно один раз, «набір» має для мене більше сенсу.

Приклад з класами та реалізованою 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()яким повертає різницю між множинами

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.