LINQ: разные значения

136

У меня есть следующий элемент из XML:

id           category

5            1
5            3
5            4
5            3
5            3

Мне нужен четкий список этих предметов:

5            1
5            3
5            4

Как я могу отличить для категории и идентификатора тоже в 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

В дополнение к ответу Джона Скита, вы также можете использовать выражения группы по группам, чтобы получить уникальные группы по количеству итераций для каждой группы:

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 использует хэш-набор для хранения уже полученных элементов. Всегда возвращать один и тот же хеш-код означает, что каждый ранее возвращенный элемент проверяется каждый раз. Более надежный хэш-код ускорит процесс, потому что он будет сравниваться только с элементами в том же самом хэш-сегменте. Ноль - разумное значение по умолчанию, но, возможно, стоит поддержать вторую лямбду для хеш-кода.
Дэррил
Хорошая точка зрения! Я постараюсь редактировать, когда у меня будет время, если вы работаете в этом домене, не стесняйтесь редактировать
Рикки Дж
8

Я немного опаздываю с ответом, но вы можете сделать это, если вы хотите весь элемент, а не только значения, которые вы хотите сгруппировать по:

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

Это даст вам первый полный элемент, соответствующий вашей группе по выбору, во многом как второй пример Jon Skeets с использованием 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)); 
}
Мохамед Эльсайед
источник
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()что возвращает разность множеств

Адитья АВС
источник