Quiero eliminar los duplicados de la lista, sin cambiar el orden de los elementos únicos en la lista.
Jon Skeet y otros han sugerido usar lo siguiente
list = list.Distinct().ToList();
eliminar duplicados de una lista C #
Eliminar duplicados de una List <T> en C #
¿Está garantizado que el orden de los elementos únicos sea el mismo que antes? En caso afirmativo, proporcione una referencia que confirme esto, ya que no pude encontrar nada en la documentación.
c#
list
duplicates
Nitesh
fuente
fuente
Respuestas:
No está garantizado, pero es la implementación más obvia. Sería difícil de implementar en forma de transmisión (es decir, de modo que devolviera resultados tan pronto como pudiera, habiendo leído tan poco como pudo) sin devolverlos en orden.
Es posible que desee leer la publicación de mi blog sobre la implementación de Edulinq de Distinct () .
Tenga en cuenta que incluso si esto estuviera garantizado para LINQ to Objects (que personalmente creo que debería ser) eso no significaría nada para otros proveedores de LINQ como LINQ to SQL.
El nivel de garantías proporcionado dentro de LINQ to Objects es un poco inconsistente a veces, en mi opinión. Algunas optimizaciones están documentadas, otras no. Diablos, parte de la documentación es completamente incorrecta .
fuente
En .NET Framework 3.5, desensamblar el CIL de la implementación de Linq-to-Objects de
Distinct()
muestra que se conserva el orden de los elementos; sin embargo, esto no es un comportamiento documentado.Hice una pequeña investigación con Reflector. Después de desensamblar System.Core.dll, Version = 3.5.0.0, puede ver que Distinct () es un método de extensión, que se ve así:
public static class Emunmerable { public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source) { if (source == null) throw new ArgumentNullException("source"); return DistinctIterator<TSource>(source, null); } }
Entonces, interesante aquí es DistinctIterator, que implementa IEnumerable e IEnumerator. Aquí está la implementación simplificada (goto y lables eliminados) de este IEnumerator:
private sealed class DistinctIterator<TSource> : IEnumerable<TSource>, IEnumerable, IEnumerator<TSource>, IEnumerator, IDisposable { private bool _enumeratingStarted; private IEnumerator<TSource> _sourceListEnumerator; public IEnumerable<TSource> _source; private HashSet<TSource> _hashSet; private TSource _current; private bool MoveNext() { if (!_enumeratingStarted) { _sourceListEnumerator = _source.GetEnumerator(); _hashSet = new HashSet<TSource>(); _enumeratingStarted = true; } while(_sourceListEnumerator.MoveNext()) { TSource element = _sourceListEnumerator.Current; if (!_hashSet.Add(element)) continue; _current = element; return true; } return false; } void IEnumerator.Reset() { throw new NotSupportedException(); } TSource IEnumerator<TSource>.Current { get { return _current; } } object IEnumerator.Current { get { return _current; } } }
Como puede ver, la enumeración va en el orden proporcionado por la fuente enumerable (lista, a la que estamos llamando
Distinct
).Hashset
se usa solo para determinar si ya devolvimos dicho elemento o no. Si no, lo devolveremos, de lo contrario, continúe enumerando en la fuente.Por lo tanto, se garantiza que
Distinct()
devolverá los elementos exactamente en el mismo orden , que son proporcionados por la colección a la que se aplicó Distinct.fuente
Según la documentación, la secuencia está desordenada.
fuente
Sí , Enumerable. Distinct conserva el orden. Suponiendo que el método es perezoso "produce valores distintos tan pronto como se ven", sigue automáticamente. Piénsalo.
La fuente de .NET Reference confirma. Devuelve una subsecuencia, el primer elemento de cada clase de equivalencia.
foreach (TSource element in source) if (set.Add(element)) yield return element;
La implementación de .NET Core es similar.
Es frustrante que la documentación de Enumerable.Distinct se confunda en este punto:
Solo puedo imaginar que quieren decir "la secuencia de resultados no está ordenada". Usted podría implementar Distinto de clasificación previa luego comparar cada elemento a la anterior, pero esto no sería perezosa como se definió anteriormente.
fuente
dbQuery.OrderBy(...).Distinct().ToList()
no devuelven una lista en el orden especificado por el orden por predicado; eliminar Distinct (que resultó ser redundante) solucionó el error en mi casoDe forma predeterminada, cuando se usa Distinct, el operador linq usa el método Equals, pero puede usar su propio
IEqualityComparer<T>
objeto para especificar cuándo dos objetos son iguales con una implementación lógica personalizadaGetHashCode
y unEquals
método. Recuérdalo:GetHashCode
no debe usar una comparación de CPU pesada (por ejemplo, use solo algunas comprobaciones básicas obvias) y se usa como el primero para indicar si dos objetos son seguramente diferentes (si se devuelven diferentes códigos hash) o potencialmente iguales (el mismo código hash). En este último caso, cuando dos objetos tienen el mismo código hash, el marco pasará a verificar utilizando el método Equals como una decisión final sobre la igualdad de los objetos dados.Después de que tenga
MyType
y unaMyTypeEqualityComparer
clase siga el código, no asegúrese de que la secuencia mantenga su orden:var cmp = new MyTypeEqualityComparer(); var lst = new List<MyType>(); // add some to lst var q = lst.Distinct(cmp);
En la siguiente biblioteca de ciencia , implementé un método de extensión para asegurar que el conjunto de Vector3D mantenga el orden cuando se usa un método de extensión específico
DistinctKeepOrder
:sigue el código relevante:
/// <summary> /// support class for DistinctKeepOrder extension /// </summary> public class Vector3DWithOrder { public int Order { get; private set; } public Vector3D Vector { get; private set; } public Vector3DWithOrder(Vector3D v, int order) { Vector = v; Order = order; } } public class Vector3DWithOrderEqualityComparer : IEqualityComparer<Vector3DWithOrder> { Vector3DEqualityComparer cmp; public Vector3DWithOrderEqualityComparer(Vector3DEqualityComparer _cmp) { cmp = _cmp; } public bool Equals(Vector3DWithOrder x, Vector3DWithOrder y) { return cmp.Equals(x.Vector, y.Vector); } public int GetHashCode(Vector3DWithOrder obj) { return cmp.GetHashCode(obj.Vector); } }
En resumen,
Vector3DWithOrder
encapsula el tipo y un entero de orden, mientras queVector3DWithOrderEqualityComparer
encapsula el comparador de tipo original.y este es el método auxiliar para garantizar que se mantenga el orden
/// <summary> /// retrieve distinct of given vector set ensuring to maintain given order /// </summary> public static IEnumerable<Vector3D> DistinctKeepOrder(this IEnumerable<Vector3D> vectors, Vector3DEqualityComparer cmp) { var ocmp = new Vector3DWithOrderEqualityComparer(cmp); return vectors .Select((w, i) => new Vector3DWithOrder(w, i)) .Distinct(ocmp) .OrderBy(w => w.Order) .Select(w => w.Vector); }
Nota : una mayor investigación podría permitir encontrar una forma más general (usos de interfaces) y optimizada (sin encapsular el objeto).
fuente
Esto depende en gran medida de su proveedor de linq. En Linq2Objects puede permanecer en el código fuente interno
Distinct
, lo que hace suponer que se conserva el orden original.Sin embargo, para otros proveedores que resuelven algún tipo de SQL, por ejemplo, ese no es necesariamente el caso, ya que una
ORDER BY
declaración -por lo general viene después de cualquier agregación (comoDistinct
). Entonces, si su código es este:esto se traduce a algo similar a lo siguiente en SQL:
Obviamente, esto primero agrupa sus datos y luego los ordena. Ahora estás atascado en la propia lógica del DBMS de cómo ejecutar eso. En algunos DBMS esto ni siquiera está permitido. Imagina los siguientes datos:
mycol anothercol 1 2 1 1 1 3 2 1 2 3
al ejecutar
myArr.OrderBy(x => x.anothercol).GroupBy(x => x.mycol)
asumimos el siguiente resultado:mycol anothercol 1 1 2 1
Pero el DBMS puede agregar la otra columna de modo que siempre se use el valor de la primera fila, dando como resultado los siguientes datos:
mycol anothercol 1 2 2 1
que después de realizar el pedido resultará en esto:
mycol anothercol 2 1 1 2
Esto es similar a lo siguiente:
SELECT mycol, First(anothercol) from mytable group by mycol order by anothercol;
que es el orden completamente inverso al esperado.
Verá que el plan de ejecución puede variar según el proveedor subyacente. Es por eso que no hay garantía al respecto en los documentos.
fuente