Удалить дубликаты в списке с помощью linq


314

У меня есть класс Itemsс properties (Id, Name, Code, Price).

Список Itemsзаполнен дублирующимися элементами.

Например:

1         Item1       IT00001        $100
2         Item2       IT00002        $200
3         Item3       IT00003        $150
1         Item1       IT00001        $100
3         Item3       IT00003        $150

Как удалить дубликаты в списке с помощью linq?


У меня есть еще один класс в качестве свойства в классе предметов
Прасад

Вы также можете сделать var set = new HashSet<int>(); var uniques = items.Where(x => set.Add(x.Id));. Это должно быть преступно ..
nawfal

Ответы:


394
var distinctItems = items.Distinct();

Чтобы сопоставить только некоторые свойства, создайте пользовательский компаратор равенства, например:

class DistinctItemComparer : IEqualityComparer<Item> {

    public bool Equals(Item x, Item y) {
        return x.Id == y.Id &&
            x.Name == y.Name &&
            x.Code == y.Code &&
            x.Price == y.Price;
    }

    public int GetHashCode(Item obj) {
        return obj.Id.GetHashCode() ^
            obj.Name.GetHashCode() ^
            obj.Code.GetHashCode() ^
            obj.Price.GetHashCode();
    }
}

Тогда используйте это так:

var distinctItems = items.Distinct(new DistinctItemComparer());

Привет, Кристиан! Что изменится в коде, если у меня есть List <my_Custom_Class> и List <string>. Мой пользовательский класс имеет различные элементы, в которых один - номер DCN, а список <string> имеет только номер DCN. Поэтому мне нужно проверить, что List <Custom_Class> содержит любой dcn из List <string>. Например, предположим, что List1 = List <Custom_Class> и List2 = List <String>. Если List1 имеет 2000 элементов, а list2 имеет 40000 элементов, для которых 600 элементов из List1 существует в List2. Так что в этом случае мне нужно 1400 в качестве моего списка вывода как list1. Так что бы было выражение. Заранее спасибо

Также здесь есть еще один случай, поскольку List1 содержит различные элементы, значения других элементов могут отличаться, но DCN должен быть таким же. Так что в моем случае Distinct не смог дать желаемый результат.

2
Я считаю классы сравнения чрезвычайно полезными. Они могут выражать логику, отличную от простого сравнения имен свойств. Я написал новый в прошлом месяце, чтобы сделать то, что GroupByне смог.
Кристиан Хейтер

Хорошо работает и заставил меня изучить что-то новое и исследовать XoRоператор ^в C #. Использовал в VB.NET через, Xorно должен был сделать двойной код для вашего кода, чтобы увидеть, что это было на первых порах.
atconway

Это ошибка, которую я получаю, когда пытаюсь использовать Distinct Comparer: «LINQ to Entities не распознает метод» System.Linq.IQueryable 1[DataAccess.HR.Dao.CCS_LOCATION_TBL] Distinct[CCS_LOCATION_TBL](System.Linq.IQueryable1 [DataAccess.HR.Dao.CCS_LOCATION_TBL], System.Collections.Generic.IEqualityComparer`1 [ DataAccess.HR.Dao.CCS_LOCATION_TBL]) ', и этот метод не может быть преобразован в выражение хранилища.
user8128167

601
var distinctItems = items.GroupBy(x => x.Id).Select(y => y.First());

28
Спасибо - старался не писать класс для сравнения, поэтому я рад, что это сработает :)
Jen

8
+1 Это решение даже позволяет использовать тай-брейк: исключить дубликаты с критериями!
Адриано Карнейру

4
Но немного накладных расходов!
Amirhossein Mehrvarzi

1
Но, как предложил Виктор Юрий ниже: используйте FirstorDefault. не могу поверить, что решение может быть настолько простым (без пользовательского
сравнения

6
Вы можете группировать с несколькими свойствами: List <XYZ> MyUniqueList = MyList.GroupBy (x => new {x.Column1, x.Column2}). Select (g => g.First ()). ToList ();
Сумит Джоши

41

Если есть что-то, что отбрасывает ваш запрос Distinct, вы можете взглянуть на MoreLinq, использовать оператор DistinctBy и выбрать отдельные объекты по id.

var distinct = items.DistinctBy( i => i.Id );

1
В Linq нет метода DistinctBy ().
Fereydoon Barikzehy

7
@FereydoonBarikzehy Но он не говорит о чистом Линке. В посте есть ссылка на проект MoreLinq ...
Ademar

30

Так я смог сгруппироваться с Linq. Надеюсь, поможет.

var query = collection.GroupBy(x => x.title).Select(y => y.FirstOrDefault());

3
@nawfal, я предлагал FirstOrDefault () вместо First ()
sobelito

23
Если я прав, использование FirstOrDefaultздесь не дает никакой пользы, если Selectсразу следует GroupBy, поскольку нет никакой возможности создать пустую группу (группы были просто получены из содержимого коллекции)
Рой Тинкер

17

Используйте, Distinct()но имейте в виду, что он использует компаратор по умолчанию для сравнения значений, поэтому, если вы хотите чего-то большего, вам нужно реализовать свой собственный компаратор.

Пожалуйста, посмотрите http://msdn.microsoft.com/en-us/library/bb348436.aspx для примера.


Я должен заметить, что компаратор по умолчанию работает, если типы членов коллекции являются одним из типов значений. Но какой компаратор равенства по умолчанию выбирается csc для ссылочных типов. Типы ссылок должны иметь собственный компаратор (ы).
Нури Йылмаз

16

У вас есть три варианта удаления дубликата из списка:

  1. Используйте пользовательский компаратор равенства, а затем используйте Distinct(new DistinctItemComparer())как упомянутое @Christian Hayter .
  2. Используйте GroupBy, но, пожалуйста, обратите внимание, что GroupByвы должны группировать по всем столбцам, потому что если вы просто группируете по Idним, не всегда удаляйте дублирующиеся элементы. Например, рассмотрим следующий пример:

    List<Item> a = new List<Item>
    {
        new Item {Id = 1, Name = "Item1", Code = "IT00001", Price = 100},
        new Item {Id = 2, Name = "Item2", Code = "IT00002", Price = 200},
        new Item {Id = 3, Name = "Item3", Code = "IT00003", Price = 150},
        new Item {Id = 1, Name = "Item1", Code = "IT00001", Price = 100},
        new Item {Id = 3, Name = "Item3", Code = "IT00003", Price = 150},
        new Item {Id = 3, Name = "Item3", Code = "IT00004", Price = 250}
    };
    var distinctItems = a.GroupBy(x => x.Id).Select(y => y.First());

    Результат для этой группировки будет:

    {Id = 1, Name = "Item1", Code = "IT00001", Price = 100}
    {Id = 2, Name = "Item2", Code = "IT00002", Price = 200}
    {Id = 3, Name = "Item3", Code = "IT00003", Price = 150}

    Что неверно, потому что он считает {Id = 3, Name = "Item3", Code = "IT00004", Price = 250}дубликатом. Таким образом, правильный запрос будет:

    var distinctItems = a.GroupBy(c => new { c.Id , c.Name , c.Code , c.Price})
                         .Select(c => c.First()).ToList();

    3. Переопределить Equalи GetHashCodeв классе элемента:

    public class Item
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Code { get; set; }
        public int Price { get; set; }
    
        public override bool Equals(object obj)
        {
            if (!(obj is Item))
                return false;
            Item p = (Item)obj;
            return (p.Id == Id && p.Name == Name && p.Code == Code && p.Price == Price);
        }
        public override int GetHashCode()
        {
            return String.Format("{0}|{1}|{2}|{3}", Id, Name, Code, Price).GetHashCode();
        }
    }

    Тогда вы можете использовать это так:

    var distinctItems = a.Distinct();

12

Универсальный метод расширения:

public static class EnumerableExtensions
{
    public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> enumerable, Func<T, TKey> keySelector)
    {
        return enumerable.GroupBy(keySelector).Select(grp => grp.First());
    }
}

Пример использования:

var lstDst = lst.DistinctBy(item => item.Key);

Очень чистый подход
Стивен Райссарт

5

Попробуйте этот метод расширения. Надеюсь, это может помочь.

public static class DistinctHelper
{
    public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
    {
        var identifiedKeys = new HashSet<TKey>();
        return source.Where(element => identifiedKeys.Add(keySelector(element)));
    }
}

Использование:

var outputList = sourceList.DistinctBy(x => x.TargetProperty);

3
List<Employee> employees = new List<Employee>()
{
    new Employee{Id =1,Name="AAAAA"}
    , new Employee{Id =2,Name="BBBBB"}
    , new Employee{Id =3,Name="AAAAA"}
    , new Employee{Id =4,Name="CCCCC"}
    , new Employee{Id =5,Name="AAAAA"}
};

List<Employee> duplicateEmployees = employees.Except(employees.GroupBy(i => i.Name)
                                             .Select(ss => ss.FirstOrDefault()))
                                            .ToList();

0

Еще один обходной путь, не красивый купить работоспособным.

У меня есть XML-файл с элементом «MEMDES» с двумя атрибутами «GRADE» и «SPD» для записи информации модуля RAM. В СДПГ много дублирующих предметов.

Итак, вот код, который я использую для удаления дублированных элементов:

        IEnumerable<XElement> MList =
            from RAMList in PREF.Descendants("MEMDES")
            where (string)RAMList.Attribute("GRADE") == "DDR4"
            select RAMList;

        List<string> sellist = new List<string>();

        foreach (var MEMList in MList)
        {
            sellist.Add((string)MEMList.Attribute("SPD").Value);
        }

        foreach (string slist in sellist.Distinct())
        {
            comboBox1.Items.Add(slist);
        }

-1

Если вы не хотите писать IEqualityComparer, вы можете попробовать что-то вроде следующего.

 class Program
{

    private static void Main(string[] args)
    {

        var items = new List<Item>();
        items.Add(new Item {Id = 1, Name = "Item1"});
        items.Add(new Item {Id = 2, Name = "Item2"});
        items.Add(new Item {Id = 3, Name = "Item3"});

        //Duplicate item
        items.Add(new Item {Id = 4, Name = "Item4"});
        //Duplicate item
        items.Add(new Item {Id = 2, Name = "Item2"});

        items.Add(new Item {Id = 3, Name = "Item3"});

        var res = items.Select(i => new {i.Id, i.Name})
            .Distinct().Select(x => new Item {Id = x.Id, Name = x.Name}).ToList();

        // now res contains distinct records
    }



}


public class Item
{
    public int Id { get; set; }

    public string Name { get; set; }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.