2011-06-09 15 views
14

Me pregunto si puedo obtener un consenso sobre qué método es el mejor enfoque para crear un conjunto distinto de elementos: un C# HashSet o usando IEnumerable's .Distinct(), que es una función Linq?¿Qué es mejor para crear estructuras de datos distintas: HashSet o Linq's Distinct()?

Digamos que estoy bucle a través de resultados de consulta de la base de datos con DataReader, y mis opciones son para agregar los objetos que construir un List<SomeObject> oa un HashSet<SomeObject> Con la opción List, me gustaría terminar teniendo que hacer algo como :

myList = myList.Distinct().ToList<SomeObject>();

con la HashSet, mi opinión es que la adición de elementos a que se encarga de la no duplicación por sí mismo, asumiendo que ha overrided los GetHashCode() y Equals() métodos en SomeObject. Me preocupan principalmente los aspectos de riesgos y rendimiento de las opciones.

Gracias.

Respuesta

2

"Mejor" es una palabra difícil de usar, puede significar tantas cosas diferentes para diferentes personas.

Para la legibilidad, yo iría por Distinct() ya que personalmente considero esto más comprensible.

Para el rendimiento, sospecho que una implementación HashSet hecha a mano podría funcionar ligeramente más rápido, pero dudo que sea muy diferente ya que la implementación interna de Distinct sin duda usará alguna forma de hashing.

Por lo que considero la "mejor" implementación ... creo que debería usar Distinct pero de alguna manera empujar esto hacia la capa de la base de datos, es decir, cambiar la base de datos subyacente SELECT antes de llenar el DataReader.

1

Para colecciones grandes, es probable que HashSet sea más rápido. Se basa en el código hash de los objetos para determinar rápidamente si un elemento ya existe en el conjunto o no.

En la práctica, (lo más probable) no importará (pero debe medir si le importa).

Intuí al principio que HashSet sería más rápido, debido a la rápida comprobación hash que utiliza. Sin embargo, busqué la implementación actual (4.0) de Distinct en las fuentes de referencia, y utiliza una clase similar Set (que también depende de hash) bajo las cubiertas. Conclusión; no hay diferencia de rendimiento práctico.

Para su caso, me gustaría ir con .Distinct para la legibilidad - claramente transmite la intención del código. Sin embargo, estoy de acuerdo con una de las otras respuestas, que probablemente deba realizar esta operación en el DB si es posible.

8

Lo que es mejor es lo más expresivo de describir su intención . Los detalles de la implementación interna son más o menos los mismos, la diferencia es "¿quién está escribiendo el código?"

Si su intención es crear desde cero una colección distinta de los elementos de una fuente que es no una colección de dichos artículos, yo diría para la HashSet<T>. Tienes que crear el elemento, usted tiene que construir la colección, que también podría construir el más adecuado desde el principio.

de lo contrario, si ya tiene una colección de artículos y desea eliminar duplicados, yo diría para invocar Distinct(). usted ya tiene una colección, solo quiere una forma expresiva de obtener los elementos distintivos de ella.

+0

+1 para la única respuesta correcta! – nawfal

1

Si hace un bucle a través de los resultados de un DbReader agregando sus resutls a un Hashset, sería mejor que agregarlo a una lista y hacer un Distintivo sobre eso. Guardarías una iteración. (Distinto usa internamente un HashSet)

11

Anthony Pegram ha dicho que es el mejor. Use la herramienta correcta para el trabajo. Digo esto porque un Distinct o HashSet no es muy diferente cuando se trata de rendimiento. Use un HashSet cuando la colección siempre contenga solo elementos diferentes. También le dice al programador que no puede agregarle duplicados. Use un List<T> y .Distinct() normal cuando tenga que agregar duplicados y eliminar duplicados más tarde. La intención importa

En general,

a) un HashSet no puede hacer ningún bien si va a añadir nuevos objetos a partir db y no se ha especificado una costumbre Equals de su cuenta. Cada objeto de db puede ser una nueva instancia para su hashset (si está recién iniciando) y eso generará duplicados en la colección. En ese caso, use normal List<T>.

b) Si tiene un comparador de igualdad definido para hashset, y su colección siempre debe contener solo objetos distintos, use hashset.

c) Si tiene un comparador de igualdad definido para hashset, y solo desea objetos distintos de db, no es necesario que la recopilación solo contenga objetos distintos (es decir, se deben agregar duplicados más adelante), un enfoque más rápido es obtener los elementos de db a hashset y luego devuelve una lista regular de ese hashset.

d) Lo mejor que debe hacer es dar la tarea de eliminar duplicados a la base de datos, esa es la herramienta correcta ¡Y eso es de primera clase!

En cuanto a las diferencias de rendimiento, en mis pruebas siempre encontré que HashSet es más rápido, pero eso es solo marginal. Eso es obvio teniendo en cuenta que con el enfoque de Lista primero debe agregar y luego hacer una distinción en él.

Método de ensayo: A partir de dos funciones generales,

public static void Benchmark(Action method, int iterations = 10000) 
{ 
    Stopwatch sw = new Stopwatch(); 
    sw.Start(); 
    for (int i = 0; i < iterations; i++) 
     method(); 

    sw.Stop(); 
    MsgBox.ShowDialog(sw.Elapsed.TotalMilliseconds.ToString()); 
} 

public static List<T> Repeat<T>(this ICollection<T> lst, int count) 
{ 
    if (count < 0) 
     throw new ArgumentOutOfRangeException("count"); 

    var ret = Enumerable.Empty<T>(); 

    for (var i = 0; i < count; i++) 
     ret = ret.Concat(lst); 

    return ret.ToList(); 
} 

implementación:

var d = Enumerable.Range(1, 100).ToList().Repeat(100); 
HashSet<int> hash = new HashSet<int>(); 

Benchmark(() => 
{ 
    hash.Clear(); 
    foreach (var item in d) 
    { 
     hash.Add(item); 
    } 
}); 

~ 3300 ms

var d = Enumerable.Range(1, 100).ToList().Repeat(100); 
List<int> list = new List<int>(); 

Benchmark(() => 
{ 
    list.Clear(); 
    foreach (var item in d) 
    { 
     list.Add(item); 
    } 

    list = list.Distinct().ToList(); 
}); 

~ 5800 ms

Una diferencia de 2,5 segundos no es malo para una lista de 10000 objetos cuando iterado otros 10000 veces. Para casos normales, la diferencia será apenas perceptible.

El mejor enfoque posible para usted con su diseño actual:

var d = Enumerable.Range(1, 100).ToList().Repeat(100); 
HashSet<int> hash = new HashSet<int>(); 
List<int> list = new List<int>(); 

Benchmark(() => 
{ 
    hash.Clear(); 
    foreach (var item in d) 
    { 
     hash.Add(item); 
    } 

    list = hash.ToList(); 
}); 

~ 3300 ms

no hay ninguna diferencia significativa, ver ..


Sin relación alguna: después de publicar esta respuesta, tenía curiosidad por saber cuál es el mejor enfoque en eliminando duplicados, de una lista normal.

var d = Enumerable.Range(1, 100).ToList().Repeat(100); 
HashSet<int> hash = new HashSet<int>(); 
List<int> list = new List<int>(); 

Benchmark(() => 
{ 
    hash = new HashSet<int>(d); 
}); 

~ 3900 ms

var d = Enumerable.Range(1, 100).ToList().Repeat(100); 
List<int> list = new List<int>(); 

Benchmark(() => 
{ 
    list = d.Distinct().ToList(); 
}); 

~ 3200 ms

Aquí la herramienta adecuada Distinct es más rápido que el hacker HashSet! Tal vez sea la sobrecarga de crear un conjunto de hash.


He probado con varias otras combinaciones similares de los tipos de referencia, sin duplicados en la lista original de etc. Los resultados son consistentes.

Cuestiones relacionadas