LINQ Distinct () na określonej właściwości


Bawię się z LINQ, aby się tego dowiedzieć, ale nie mogę dowiedzieć się, jak używać
Distinct
, gdy nie mam prostej listy (prosta lista liczb całkowitych jest dość łatwa zrobić, bez wątpienia). Co mogę zrobić, jeśli chcę skorzystać

Distinct
https://msdn.microsoft.com/lib ... .aspx
na liście obiektów wg

jeden

lub

kilka

właściwości obiektu?
Przykład: jeśli obiekt ma wartość
Person
, to z właściwością
Id
. Jak mogę pozyskać wszystkie osoby i użyć na nich
Distinct
z właściwością
Id
obiektu?
Person1: Id=1, Name="Test1"
Person2: Id=1, Name="Test1"
Person3: Id=2, Name="Test2"

Jak mogę uzyskać tylko
Person1
i
Person3
? Czy to możliwe?
Jeśli nie jest to możliwe z LINQ, jaki byłby najlepszy sposób na utworzenie listy
Person
w zależności od niektórych jej właściwości w .NET 3.5?
Zaproszony:
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

A co jeśli chcę otrzymać osobną listę na podstawie

jeden

lub

kilka

nieruchomości?

To takie proste! Chcesz je pogrupować i wybrać zwycięzcę z grupy.
List<Person> distinctPeople = allPeople
.GroupBy(p => p.PersonId)
.Select(g => g.First())
.ToList();

Jeśli chcesz zdefiniować grupy dla wielu usług, oto jak to zrobić:
List<Person> distinctPeople = allPeople
.GroupBy(p => new {p.PersonId, p.FavoriteColor} )
.Select(g => g.First())
.ToList();
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:


EDIT
: teraz to część

MoreLINQ
https://github.com/morelinq/MoreLINQ
.
To, czego chcesz, to efektywne „rozróżnianie”. Nie wierzę, że jest to część LINQ w obecnej postaci, chociaż dość łatwo jest napisać:
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;
}
}
}

Dlatego, aby znaleźć różne wartości przy użyciu tylko właściwości
Id
, możesz użyć:
var query = people.DistinctBy(p => p.Id);

Aby użyć wielu właściwości, możesz użyć typów anonimowych, które odpowiednio implementują równość:
var query = people.DistinctBy(p => new { p.Id, p.Name });

Nie przetestowane, ale powinno działać (a teraz przynajmniej się kompiluje).
Zakłada jednak domyślny komparator dla kluczy - jeśli chcesz przekazać komparator równości, po prostu przekaż go do konstruktora
HashSet
.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Posługiwać się:
List<Person> pList = new List<Person>();
/* Fill list */var result = pList.Where(p => p.Name != null).GroupBy(p => p.Id).Select(grp => grp.FirstOrDefault());

gdzie
pomaga filtrować rekordy (być może bardziej złożone), podczas gdy
groupby
i
select
robią różne rzeczy.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Możesz również użyć składni zapytania, jeśli chcesz, aby wyglądała całkowicie jak 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();
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Myślę, że to wystarczy:
list.Select(s => s.MyField).Distinct();
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Rozwiązanie Najpierw pogrupuj pola, a następnie wybierz pierwszy domyślny element.
List<Person> distinctPeople = allPeople
.GroupBy(p => p.PersonId)
.Select(g => g.FirstOrDefault())
.ToList();
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Możesz to zrobić za pomocą standardu
Linq.ToLookup()
https://msdn.microsoft.com/lib ... px... Spowoduje to utworzenie kolekcji wartości dla każdego unikalnego klucza. Po prostu wybierz pierwszą pozycję z kolekcji
Persons.ToLookup(p => p.Id).Select(coll => coll.First());
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Poniższy kod jest funkcjonalnie równoważny

do odpowiedzi Jona Skeeta
https://coderoad.ru/489258/
.
Testowany na .NET 4.5, powinien działać na dowolnej wcześniejszej wersji LINQ.
public static IEnumerable<TSource> DistinctBy<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
HashSet<TKey> seenKeys = new HashSet<TKey>();
return source.Where(element => seenKeys.Add(keySelector(element)));
}

Swoją drogą, sprawdź

najnowsza wersja DistinctBy.cs Jona Skeeta w Google Code
http://code.google.com/p/morel ... By.cs
.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Napisałem artykuł, w którym wyjaśniono, jak rozszerzyć funkcję Distinct, aby można było wykonać następujące czynności:
var people = new List<Person>();people.Add(new Person(1, "a", "b"));
people.Add(new Person(2, "c", "d"));
people.Add(new Person(1, "a", "b"));foreach (var person in people.Distinct(p => p.ID))
// Do stuff with unique list here.

Oto artykuł:
Rozszerzenie LINQ - określanie właściwości w oddzielnej funkcji

http://www.singingeels.com/Art ... .aspx
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Osobiście korzystam z następującej klasy:
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();
}
}

Następnie metoda rozszerzenia:
public static IEnumerable<TSource> Distinct<TSource, TCompare>( this IEnumerable<TSource> source, Func<TSource, TCompare> selector)
{
return source.Distinct(new LambdaEqualityComparer<TSource, TCompare>(selector));
}

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

Zaletą, jaką znalazłem stosując to podejście, jest to, że klasa
LambdaEqualityComparer
jest ponownie wykorzystywana dla innych metod, które akceptują
IEqualityComparer
. (Aha, i zostawiam rzeczy
yield
dla początkowej implementacji LINQ ...)
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Możesz użyć DistinctBy (), aby uzyskać różne wpisy dla właściwości obiektu. Po prostu dodaj następujące oświadczenie przed jego użyciem:

przy użyciu Microsoft.Ajax.Utilities;

a następnie użyj tego w ten sposób:
var listToReturn = responseList.DistinctBy(x => x.Index).ToList();

gdzie „Indeks” to właściwość, dla której chcę, aby dane były inne.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jeśli potrzebujesz osobnej metody dla wielu właściwości, możesz sprawdzić moją bibliotekę PowerfulExtensions. Obecnie jest na bardzo młodym etapie, ale już można stosować metody takie jak Distinct, Union, Intersect, z wyjątkiem dowolnej liczby właściwości;
Oto jak tego używasz:
using PowerfulExtensions.Linq;...
var distinct = myArray.Distinct(x => x.A, x => x.B);
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Kiedy napotkaliśmy taki problem w naszym projekcie, zdefiniowaliśmy małe API do tworzenia komparatorów.
Tak więc przypadek użycia wyglądał mniej więcej tak:
var wordComparer = KeyEqualityComparer.Null<Word>().
ThenBy(item => item.Text).
ThenBy(item => item.LangID);...
source.Select(...).Distinct(wordComparer);

Samo API And wygląda mniej więcej tak:
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;
}

Więcej informacji można znaleźć na naszej stronie internetowej:
IEqualityComparer w LINQ

http://www.nesterovsky-bros.co ... .aspx
.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Możesz to zrobić (choć nie błyskawicznie) w ten sposób:
people.Where(p => !people.Any(q => (p != q && p.Id == q.Id)));

To znaczy „wybierz wszystkie osoby, w przypadku których nie ma innej osoby na liście o tym samym identyfikatorze”.
Pamiętaj, że w twoim przykładzie będzie to po prostu wybór osoby 3. Nie jestem pewien, jak powiedzieć, czego chcesz od dwóch poprzednich.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jeśli nie chcesz dodawać biblioteki MoreLinq do swojego projektu tylko po to, aby uzyskać funkcjonalność
DistinctBy
, możesz uzyskać ten sam wynik końcowy, używając przeciążenia metody
Distinct
Linq który przyjmuje argument
IEqualityComparer
.
Zaczynasz od utworzenia ogólnej niestandardowej klasy porównania równości, która używa składni lambda do wykonania niestandardowego porównania między dwoma wystąpieniami klasy ogólnej:
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);
}
}

Następnie w swoim głównym kodzie używasz go w następujący sposób:
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! :)
Powyższe zakłada, że:
  • Właściwość
    Person.Id
    jest typu
    int
  • Kolekcja
    people
    nie zawiera żadnych elementów o wartości null

Jeśli kolekcja może zawierać zera, po prostu przepisz wyrażenia lambda, aby sprawdzić wartość null, na przykład:
Func<Person, Person, bool> areEqual = (p1, p2) => 
{
return (p1 != null && p2 != null) ? int.Equals(p1.Id, p2.Id) : false;
};


EDIT

To podejście jest podobne do tego zawartego w odpowiedzi Vladimira Nesterovsky'ego, ale prostsze.
Jest również podobna do odpowiedzi Joela, ale pozwala na złożoną logikę porównania obejmującą wiele właściwości.
Jeśli jednak Twoje obiekty mogą różnić się tylko przez
Id
, inny użytkownik podał poprawną odpowiedź, że jedyne, co musisz zrobić, to zastąpić domyślne implementacje
GetHashCode ()
i
Equals ()
w klasie
Person
, a następnie po prostu użyj gotowej metody
Distinct ()
Linq, aby odfiltrować wszelkie duplikaty.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Najlepszym sposobem, aby to zrobić, który będzie kompatybilny z innymi wersjami .NET, jest zastąpienie Equals i GetHash, aby sobie z tym poradzić (patrz pytanie Stack Overflow
ten kod zwraca różne wartości. Jednak chcę zwrócić kolekcję o silnym typie, w przeciwieństwie do typu anonimowego

https://coderoad.ru/7336275/), ale jeśli chcesz mieć coś wspólnego w całym kodzie, rozwiązania przedstawione w tym artykule są świetne.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Metody zastępujące

Equals(object obj)

i

GetHashCode()

:
class Person
{
public int Id { get; set; }
public int Name { get; set; } public override bool Equals(object obj)
{
return ((Person)obj).Id == Id;
// or:
// var o = (Person)obj;
// return o.Id == Id && o.Name == Name;
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
}

a potem po prostu zadzwoń:
List<Person> distinctList = new[] { person1, person2, person3 }.Distinct().ToList();
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

List<Person>lst=new List<Person>
var result1 = lst.OrderByDescending(a => a.ID).Select(a =>new Player {ID=a.ID,Name=a.Name} ).Distinct();
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Powinieneś mieć możliwość zastąpienia opcji Równa się osobie, aby faktycznie ustawić Równe wartości Person.id. Powinno to doprowadzić do zachowania, którego szukasz.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Wypróbuj poniższy kod.
var Item = GetAll().GroupBy(x => x .Id).ToList();

Aby odpowiedzieć na pytania, Zaloguj się lub Zarejestruj się