c# - array - linq distinct by field
Distintivo de LINQ() en una propiedad particular (19)
¿Qué sucede si deseo obtener una lista distinta basada en una o más propiedades?
¡Sencillo! Quieres agruparlos y elegir un ganador del grupo.
List<Person> distinctPeople = allPeople
.GroupBy(p => p.PersonId)
.Select(g => g.First())
.ToList();
Si desea definir grupos en múltiples propiedades, aquí tiene cómo:
List<Person> distinctPeople = allPeople
.GroupBy(p => new {p.PersonId, p.FavoriteColor} )
.Select(g => g.First())
.ToList();
Estoy jugando con LINQ para aprender sobre esto, pero no puedo entender cómo usar Distinct cuando no tengo una lista simple (una lista simple de enteros es bastante fácil de hacer, no es la pregunta). ¿Qué debo hacer si deseo usar Distinct en una lista de un objeto en una o más propiedades del objeto?
Ejemplo: Si un objeto es Person
, con Id
. De Propiedad. ¿Cómo puedo obtener toda la Persona y usar Distinct
en ellos con el Id
propiedad del objeto?
Person1: Id=1, Name="Test1"
Person2: Id=1, Name="Test1"
Person3: Id=2, Name="Test2"
¿Cómo puedo obtener solo Person1 y Person3? ¿Es eso posible?
Si no es posible con LINQ, ¿cuál sería la mejor manera de tener una lista de Person
dependiendo de algunas de sus propiedades en .NET 3.5?
Creo que es suficiente:
list.Select(s => s.MyField).Distinct();
Cuando enfrentamos tal tarea en nuestro proyecto, definimos una pequeña API para componer comparadores.
Entonces, el caso de uso fue así:
var wordComparer = KeyEqualityComparer.Null<Word>().
ThenBy(item => item.Text).
ThenBy(item => item.LangID);
...
source.Select(...).Distinct(wordComparer);
Y la API se ve así:
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;
}
Más detalles están en nuestro sitio: IEqualityComparer en LINQ .
Debería poder anular Equals on person para realmente hacer Equals en Person.id. Esto debería resultar en el comportamiento que estás buscando.
El siguiente código es funcionalmente equivalente a la respuesta de Jon Skeet .
Probado en .NET 4.5, debería funcionar en cualquier versión anterior de 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)));
}
Incidencialmente, consulte code.google.com/p/morelinq/source/browse/MoreLinq/DistinctBy.cs .
En caso de que necesite un método Distinct en varias propiedades, puede consultar mi biblioteca PowerfulExtensions . Actualmente está en una etapa muy joven, pero ya puedes usar métodos como Distinct, Union, Intersect, Except en cualquier número de propiedades;
Así es como lo usas:
using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);
He escrito un artículo que explica cómo extender la función Distinct para que pueda hacer lo siguiente:
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.
Aquí está el artículo: Extendiendo LINQ - Especificando una propiedad en la función distinta
La mejor manera de hacer esto que sea compatible con otras versiones de .NET es reemplazar a Equals y GetHash para manejar esto (vea la pregunta sobre el desbordamiento de pila). Este código devuelve valores distintos. un tipo anónimo ), pero si necesita algo que sea genérico en todo su código, las soluciones en este artículo son excelentes.
Personalmente uso la siguiente clase:
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();
}
}
Entonces, un método de extensión:
public static IEnumerable<TSource> Distinct<TSource, TCompare>(
this IEnumerable<TSource> source, Func<TSource, TCompare> selector)
{
return source.Distinct(new LambdaEqualityComparer<TSource, TCompare>(selector));
}
Finalmente, el uso previsto:
var dates = new List<DateTime>() { /* ... */ }
var distinctYears = dates.Distinct(date => date.Year);
La ventaja que encontré utilizando este enfoque es la reutilización de la clase LambdaEqualityComparer
para otros métodos que aceptan un IEqualityComparer
. (Ah, y dejo la materia de yield
a la implementación LINQ original ...)
Por favor, pruébalo con el siguiente código.
var Item = GetAll().GroupBy(x => x .Id).ToList();
Puedes hacer esto con el estándar Linq.ToLookup()
. Esto creará una colección de valores para cada clave única. Solo selecciona el primer artículo de la colección.
Persons.ToLookup(p => p.Id).Select(coll => coll.First());
Puedes hacerlo (aunque no rápido como un rayo) así:
people.Where(p => !people.Any(q => (p != q && p.Id == q.Id)));
Es decir, "seleccione todas las personas donde no haya otra persona diferente en la lista con la misma ID".
Ten en cuenta que, en tu ejemplo, solo seleccionarías a la persona 3. No estoy seguro de cómo decir lo que quieres, de los dos anteriores.
Reemplace los métodos Equals (obj) y 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();
}
}
y luego simplemente llame:
List<Person> distinctList = new[] { person1, person2, person3 }.Distinct().ToList();
Si no desea agregar la biblioteca MoreLinq a su proyecto solo para obtener la funcionalidad DistinctBy
, puede obtener el mismo resultado final utilizando la sobrecarga del método Distinct
de Linq que IEqualityComparer
un argumento de IEqualityComparer
.
Comenzará por crear una clase de comparador de igualdad personalizada genérica que utiliza la sintaxis lambda para realizar una comparación personalizada de dos instancias de una clase genérica:
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);
}
}
Luego en tu código principal lo usas así:
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! :)
Lo anterior asume lo siguiente:
- Propiedad
Person.Id
es de tipoint
- La colección de
people
no contiene ningún elemento nulo.
Si la colección puede contener nulos, simplemente reescriba las lambdas para verificar si hay nulos, por ejemplo:
Func<Person, Person, bool> areEqual = (p1, p2) =>
{
return (p1 != null && p2 != null) ? int.Equals(p1.Id, p2.Id) : false;
};
EDITAR
Este enfoque es similar al de la respuesta de Vladimir Nesterovsky pero más simple.
También es similar al de la respuesta de Joel, pero permite una lógica de comparación compleja que involucra múltiples propiedades.
Sin embargo, si sus objetos solo pueden diferir según el Id
, otro usuario le dio la respuesta correcta que todo lo que necesita hacer es anular las implementaciones predeterminadas de GetHashCode()
y Equals()
en su clase de Person
y luego simplemente usar la El método Distinct()
caja de Linq para filtrar cualquier duplicado.
Solución primero agrupe por sus campos y luego seleccione el primer elemento de defecto.
List<Person> distinctPeople = allPeople
.GroupBy(p => p.PersonId)
.Select(g => g.FirstOrDefault())
.ToList();
También puede usar la sintaxis de consulta si desea que se vea como 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();
Utilizar:
List<Person> pList = new List<Person>();
/* Fill list */
var result = pList.Where(p => p.Name != null).GroupBy(p => p.Id).Select(grp => grp.FirstorDefault());
El lugar where
ayuda a filtrar las entradas (podría ser más complejo) y el groupby
y select
realizar la función distinta.
EDITAR : Esto ahora es parte de MoreLINQ .
Lo que necesitas es un "distinto" efectivamente. No creo que sea parte de LINQ tal como está, aunque es bastante fácil de escribir:
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;
}
}
}
Entonces, para encontrar los valores distintos usando solo la propiedad Id
, podría usar:
var query = people.DistinctBy(p => p.Id);
Y para usar múltiples propiedades, puede usar tipos anónimos, que implementan la igualdad de manera apropiada:
var query = people.DistinctBy(p => new { p.Id, p.Name });
No probado, pero debería funcionar (y ahora al menos compila).
Sin embargo, asume el comparador predeterminado para las claves: si desea pasar un comparador de igualdad, solo páselo al constructor HashSet
.
List<Person>lst=new List<Person>
var result1 = lst.OrderByDescending(a => a.ID).Select(a =>new Player {ID=a.ID,Name=a.Name} ).Distinct();