Удалить дубликаты из списка сложных объектов [duplicate]

Обратите внимание также, что в других униксах метод указания раздела отличается. Например, на солярии это:

man -s 1 man
826
задан Peter Mortensen 16 January 2016 в 19:34
поделиться

16 ответов

EDIT: Это теперь часть MoreLINQ .

Что вам нужно, это «отличная» эффективность. Я не верю, что это часть LINQ в ее нынешнем виде, хотя ее довольно легко написать:

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

Итак, чтобы найти различные значения, используя только свойство Id, вы можете использовать:

var query = people.DistinctBy(p => p.Id);

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

var query = people.DistinctBy(p => new { p.Id, p.Name });

Untested, но он должен работать (и теперь он, по крайней мере, компилируется).

Он предполагает сопоставление по умолчанию для ключей, хотя - если вы хотите передать в компаратор равенства, просто передайте его конструктору HashSet.

934
ответ дан Thijs 21 August 2018 в 22:21
поделиться
  • 1
  • 2
    @ ashes999: Я не уверен, что вы имеете в виду. Код присутствует в ответе и в библиотеке - в зависимости от того, хотите ли вы принять зависимость. – Jon Skeet 19 February 2013 в 19:07
  • 3
    @ ashes999: Если вы делаете это только в одном месте, то, конечно, используя GroupBy, проще. Если вам это нужно в более чем одном месте, намного проще (IMO) инкапсулировать намерение. – Jon Skeet 19 February 2013 в 19:29
  • 4
    Цикл также можно было бы записать более кратко как return source.Where(e => seenKeys.Add(keySelector(e))). – Casey 24 July 2014 в 20:26
  • 5
    @MatthewWhited: Учитывая, что здесь нет упоминания IQueryable<T>, я не вижу, насколько это актуально. Я согласен с тем, что это не подходит для EF и т. Д., Но в LINQ to Objects я думаю, что больше подходит, чем GroupBy. Контекст вопроса всегда важен. – Jon Skeet 22 January 2017 в 18:10

Пожалуйста, попробуйте использовать код ниже.

var Item = GetAll().GroupBy(x => x .Id).ToList();
0
ответ дан Alien 21 August 2018 в 22:21
поделиться
  • 1
    Короткий ответ приветствуется, однако это не принесет большой пользы последним пользователям, которые пытаются понять, что происходит за этой проблемой. Пожалуйста, подождите некоторое время, чтобы объяснить, что является реальной проблемой, чтобы вызвать проблему и как ее решить. Спасибо ~ – Hearen 16 July 2018 в 05:46

Что делать, если я хочу получить отдельный список на основе свойств one или more ?

Простой! Вы хотите сгруппировать их и выбрать победителя из группы.

List<Person> distinctPeople = allPeople
  .GroupBy(p => p.PersonId)
  .Select(g => g.First())
  .ToList();

Если вы хотите определить группы по нескольким свойствам, вот как:

List<Person> distinctPeople = allPeople
  .GroupBy(p => new {p.PersonId, p.FavoriteColor} )
  .Select(g => g.First())
  .ToList();
1470
ответ дан Amy B 21 August 2018 в 22:21
поделиться
  • 1
  • 2
    Очень хороший ответ! Reallllly помог мне в Linq-to-Entities, выведенном из представления sql, где я не мог изменить представление. Мне нужно было использовать FirstOrDefault (), а не First () - все хорошо. – Alex KeySmith 16 May 2012 в 15:14
  • 3
    Я попробовал, и он должен измениться на Select (g = & gt; g.FirstOrDefault ()) – user585440 7 January 2016 в 00:38
  • 4
    Это должен быть принятый ответ, ИМО. – defines 15 June 2016 в 20:56
  • 5
    @DavidB вы можете сделать заметку о том, что для LinqToEntities это должно быть FirstOrDefault. Сначала не поддерживается. :) – Johny Skovdal 29 August 2016 в 16:48
  • 6
    @ChocapicSz Nope. И Single(), и SingleOrDefault() каждый бросок, когда источник имеет более одного элемента. В этой операции мы ожидаем, что каждая группа может иметь более одного элемента. В этом отношении First() предпочтительнее FirstOrDefault(), потому что каждая группа должна иметь хотя бы один элемент .... если вы не используете EntityFramework, который не может понять, что каждая группа имеет хотя бы один член и требует FirstOrDefault(). – Amy B 17 July 2017 в 13:41
List<Person>lst=new List<Person>
        var result1 = lst.OrderByDescending(a => a.ID).Select(a =>new Player {ID=a.ID,Name=a.Name} ).Distinct();
1
ответ дан Arindam 21 August 2018 в 22:21
поделиться

Вы также можете использовать синтаксис запроса, если хотите, чтобы он выглядел как LINQ-подобный:

var uniquePeople = from p in people
                   group p by new {p.ID} //or group by new {p.ID, p.Name, p.Whatever}
                   into mygroup
                   select mygroup.FirstOrDefault();
61
ответ дан burnttoast11 21 August 2018 в 22:21
поделиться
  • 1
    Хм, мои мысли - это синтаксис запроса, и свободный API-синтаксис подобен LINQ, как и друг другу, и его просто предпочтение, из-за которого люди используют. Я сам предпочитаю свободный API, поэтому я бы подумал, что больше LINK-Like, но потом я думаю, что это субъективно – Max Carroll 5 January 2018 в 16:57
  • 2
    – Ryan The Leach 2 October 2018 в 04:11

Сначала выберите первую группу по вашим полям, затем выберите элемент firstordefault.

    List<Person> distinctPeople = allPeople
   .GroupBy(p => p.PersonId)
   .Select(g => g.FirstOrDefault())
   .ToList();
15
ответ дан cahit beyaz 21 August 2018 в 22:21
поделиться

Если вы не хотите добавлять библиотеку MoreLinq в свой проект, чтобы получить функциональность DistinctBy, вы можете получить тот же конечный результат, используя перегрузку метода Linq Distinct, который принимает аргумент IEqualityComparer .

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

public class CustomEqualityComparer<T> : IEqualityComparer<T>
{
    Func<T, T, bool> _comparison;
    Func<T, int> _hashCodeFactory;

    public CustomEqualityComparer(Func<T, T, bool> comparison, Func<T, int> hashCodeFactory)
    {
        _comparison = comparison;
        _hashCodeFactory = hashCodeFactory;
    }

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

    public int GetHashCode(T obj)
    {
        return _hashCodeFactory(obj);
    }
}

Затем в вашем основном коде вы используйте его так:

Func<Person, Person, bool> areEqual = (p1, p2) => int.Equals(p1.Id, p2.Id);

Func<Person, int> getHashCode = (p) => p.Id.GetHashCode();

var query = people.Distinct(new CustomEqualityComparer<Person>(areEqual, getHashCode));

Voila! :)

Вышеприведенное предполагает следующее:

  • Свойство Person.Id имеет тип int
  • Коллекция people не содержит любые нулевые элементы

Если коллекция может содержать нули, то просто перепишите lambdas для проверки нулевого значения, например:

Func<Person, Person, bool> areEqual = (p1, p2) => 
{
    return (p1 != null && p2 != null) ? int.Equals(p1.Id, p2.Id) : false;
};

EDIT

Такой подход аналогичен такому в ответе Владимира Нестеровского, но проще.

Он также похож на ответ Джоэла, но допускает сложную логику сравнения, включающую несколько свойств.

Однако, если ваши объекты могут отличаться только на Id, другой пользователь дал правильный ответ, что все, что вам нужно сделать, это переопределить реализации по умолчанию GetHashCode() и Equals() в вашем Person, а затем просто используйте готовый метод Distinct() Linq для фильтрации любых дубликатов.

1
ответ дан Caspian Canuck 21 August 2018 в 22:21
поделиться

Лучший способ сделать это, который будет совместим с другими версиями .NET, - это переопределить Equals и GetHash, чтобы справиться с этим (см. вопрос о переполнении стека . Этот код возвращает различные значения. Однако, что я хочу, для возврата строго типизированной коллекции, в отличие от анонимного типа ), но если вам нужно что-то общее в вашем коде, решения в этой статье великолепны.

36
ответ дан Community 21 August 2018 в 22:21
поделиться
  • 1
    Это дало мне «последовательность не имеет ошибки значений», но ответ Скита дал правильный результат. – What Would Be Cool 23 April 2014 в 00:42

Вы должны иметь возможность переопределить Equals на лице, чтобы на самом деле сделать Equals on Person.id. Это должно привести к поведению, которое вы после.

0
ответ дан GWLlosa 21 August 2018 в 22:21
поделиться

Я думаю, что этого достаточно:

list.Select(s => s.MyField).Distinct();
46
ответ дан Ivan 21 August 2018 в 22:21
поделиться
  • 1
    Что, если ему нужно вернуть свой полный объект, а не только эту конкретную область? – Festim Cahani 12 August 2015 в 13:44
  • 2
    Какой именно объект нескольких объектов, имеющих одно и то же значение свойства? – donRumatta 3 September 2015 в 10:45

Лично я использую следующий класс:

public class LambdaEqualityComparer<TSource, TDest> : 
    IEqualityComparer<TSource>
{
    private Func<TSource, TDest> _selector;

    public LambdaEqualityComparer(Func<TSource, TDest> selector)
    {
        _selector = selector;
    }

    public bool Equals(TSource obj, TSource other)
    {
        return _selector(obj).Equals(_selector(other));
    }

    public int GetHashCode(TSource obj)
    {
        return _selector(obj).GetHashCode();
    }
}

Затем метод расширения:

public static IEnumerable<TSource> Distinct<TSource, TCompare>(
    this IEnumerable<TSource> source, Func<TSource, TCompare> selector)
{
    return source.Distinct(new LambdaEqualityComparer<TSource, TCompare>(selector));
}

Наконец, предполагаемое использование:

var dates = new List<DateTime>() { /* ... */ }
var distinctYears = dates.Distinct(date => date.Year);

Преимущество, которое я нашел с использованием этого подхода, - это повторное использование класса LambdaEqualityComparer для других методов, которые принимают IEqualityComparer. (О, и я оставляю материал yield в исходной реализации LINQ ...)

2
ответ дан Joel 21 August 2018 в 22:21
поделиться

Вы можете сделать это (хотя и не молниеносно) так:

people.Where(p => !people.Any(q => (p != q && p.Id == q.Id)));

То есть «выберите всех людей, в которых нет другого человека в списке с тем же идентификатором. «

Имейте в виду, что в вашем примере вы просто выберите человека 3. Я не уверен, как сообщить, что вы хотите, из предыдущих двух.

3
ответ дан mquander 21 August 2018 в 22:21
поделиться

Когда мы столкнулись с такой задачей в нашем проекте, мы определили небольшой API для составления компараторов.

Итак, пример использования был таким:

var wordComparer = KeyEqualityComparer.Null<Word>().
    ThenBy(item => item.Text).
    ThenBy(item => item.LangID);
...
source.Select(...).Distinct(wordComparer);

И сам API выглядит следующим образом:

using System;
using System.Collections;
using System.Collections.Generic;

public static class KeyEqualityComparer
{
    public static IEqualityComparer<T> Null<T>()
    {
        return null;
    }

    public static IEqualityComparer<T> EqualityComparerBy<T, K>(
        this IEnumerable<T> source,
        Func<T, K> keyFunc)
    {
        return new KeyEqualityComparer<T, K>(keyFunc);
    }

    public static KeyEqualityComparer<T, K> ThenBy<T, K>(
        this IEqualityComparer<T> equalityComparer,
        Func<T, K> keyFunc)
    {
        return new KeyEqualityComparer<T, K>(keyFunc, equalityComparer);
    }
}

public struct KeyEqualityComparer<T, K>: IEqualityComparer<T>
{
    public KeyEqualityComparer(
        Func<T, K> keyFunc,
        IEqualityComparer<T> equalityComparer = null)
    {
        KeyFunc = keyFunc;
        EqualityComparer = equalityComparer;
    }

    public bool Equals(T x, T y)
    {
        return ((EqualityComparer == null) || EqualityComparer.Equals(x, y)) &&
                EqualityComparer<K>.Default.Equals(KeyFunc(x), KeyFunc(y));
    }

    public int GetHashCode(T obj)
    {
        var hash = EqualityComparer<K>.Default.GetHashCode(KeyFunc(obj));

        if (EqualityComparer != null)
        {
            var hash2 = EqualityComparer.GetHashCode(obj);

            hash ^= (hash2 << 5) + hash2;
        }

        return hash;
    }

    public readonly Func<T, K> KeyFunc;
    public readonly IEqualityComparer<T> EqualityComparer;
}

Подробнее на нашем сайте: IEqualityComparer в LINQ .

18
ответ дан Peter Mortensen 21 August 2018 в 22:21
поделиться
  • 1
    В вашей статье есть ошибка, должен быть & lt; T & gt; после Distinct: public static IEnumerable & lt; T & gt; Отличный (это ... Также не похоже, что он будет работать (красиво) на более чем одном свойстве, т. Е. Комбинации имени и фамилии. – row1 17 March 2010 в 11:01
  • 2
    +1, незначительная ошибка не является достаточной причиной для downvote, что просто так глупо, часто опрашивало опечатку. И еще я должен увидеть общую функцию, которая будет работать для любого количества свойств! Надеюсь, что downvoter уменьшил все остальные ответы в этой теме. Но эй, что этот второй тип является объектом? Я объект ! – nawfal 22 November 2012 в 14:08
41
ответ дан Community 4 November 2018 в 19:25
поделиться
19
ответ дан Peter Mortensen 4 November 2018 в 19:25
поделиться
0
ответ дан Waldemar Galezinowski 4 November 2018 в 19:25
поделиться
Другие вопросы по тегам:

Похожие вопросы: