Qué colección .NET proporciona la búsqueda más rápida

143

Tengo 60k elementos que deben verificarse en una lista de búsqueda de 20k. ¿Hay un objeto de colección (como List, HashTable) que proporcione un Contains()método excepcionalmente rápido ? ¿O tendré que escribir el mío? En otras palabras, es el Contains()método predeterminado simplemente escanear cada elemento o utiliza un mejor algoritmo de búsqueda.

foreach (Record item in LargeCollection)
{
    if (LookupCollection.Contains(item.Key))
    {
       // Do something
    }
}

Nota . La lista de búsqueda ya está ordenada.

Ondrej Janacek
fuente
Contiene para la lista no funciona para la lista de objetos porque está comparando referencias.
Fiur
2
Datos ordenados? Búsqueda binaria: vea la respuesta de @ Mark.
Hamish Smith
HashtTable supera cualquier cosa hasta 2 millones de elementos en mi experiencia
Chris S
Por otro lado, si sus elementos están en un orden significativo y están distribuidos de manera bastante uniforme, puede hacer una búsqueda binaria mucho más rápido haciendo que sus primeras conjeturas estén dentro del rango estimado de su artículo. Esto puede o no tener significado para su aplicación específica.
Brian
2
No se olvide de System.Collections.Generic.SortedList (TKey, TValue) si desea simplificar estas cosas pero evitar un hashset.
Brian

Respuestas:

141

En el caso más general, considérelo System.Collections.Generic.HashSetcomo su estructura de datos de caballo de batalla "Contiene" predeterminada, porque lleva tiempo constante evaluarla Contains.

La respuesta real a "¿Cuál es la colección de búsqueda más rápida" depende del tamaño de datos específico, el orden, el costo del hash y la frecuencia de búsqueda.

Palanqueta
fuente
36
Nota: No olvide anular la función de código hash. Para un rendimiento adicional, pregenere su código hash en su constructor.
Brian
1
@Brian: buen punto. Estaba asumiendo (sin fundamento) Record.Key era un tipo incorporado de algún tipo.
Jimmy
3
@Brian: en lugar de pregenerar, prefiero almacenar el generado la primera vez, ¿por qué ralentizar el constructor con algo que no sabes si se usará?
jmservera
8
FYI: Prueba de rendimiento: creé una comparación entre List <T> y HashSet <T> para cadenas. Descubrí que HashSet era aproximadamente 1000 veces más rápido que List.
Quango
10
@ Quango: 3 años después, pero realmente si no especifica el tamaño de su conjunto de datos, esta comparación de rendimiento no significa nada: los hashsets tienen O (1) búsqueda, las listas tienen O (n) búsqueda, por lo que la relación de rendimiento es proporcional a norte.
Clément
73

Si no necesita hacer un pedido, intente HashSet<Record>(nuevo en .Net 3.5)

Si lo hace, use un List<Record>y llame BinarySearch.

SLaks
fuente
8
O, en .NET> = 4, use SortedSet
StriplingWarrior el
2
O mejor aún, ImmutableSortedSetde System.ImmutableCollections
Alexei S
24

¿Lo has considerado List.BinarySearch(item)?

¿Dijiste que tu gran colección ya está ordenada, así que esta parece ser la oportunidad perfecta? Un hash definitivamente sería el más rápido, pero esto trae sus propios problemas y requiere mucha más sobrecarga para el almacenamiento.

marca
fuente
1
Tiene razón, un hash puede traer algunos problemas no deseados cuando se usan objetos mutables como clave.
jmservera
10

Debería leer este blog que probó la velocidad de varios tipos diferentes de colecciones y métodos para cada uno utilizando técnicas de subprocesos simples y múltiples.

De acuerdo con los resultados, una BinarySearch on a List y SortedList fueron los que tuvieron mejores resultados constantemente corriendo codo con codo al buscar algo como un "valor".

Cuando se usa una colección que permite "claves", Dictionary, ConcurrentDictionary, Hashset y HashTables obtuvieron el mejor rendimiento general.


fuente
4

Mantenga ambas listas x e y en orden ordenado.

Si x = y, realice su acción, si x <y, avance x, si y <x, avance y hasta que cualquiera de las listas esté vacía.

El tiempo de ejecución de esta intersección es proporcional a min (tamaño (x), tamaño (y))

No ejecute un bucle .Contains (), esto es proporcional a x * y que es mucho peor.

clemahieu
fuente
+1 para el algoritmo más eficiente. Incluso si las listas no están ordenadas actualmente, sería más eficiente ordenarlas primero y luego ejecutar este algoritmo.
Matt Boehm
¿No sería el tiempo de ejecución proporcional a max (tamaño (x), tamaño (y)) en el peor de los casos? Ejemplo: int [] x = {99,100}; int [] y = {0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
Matt Boehm
No, porque una vez que complete el conjunto más pequeño, puede agregar los elementos restantes del conjunto más grande porque ya están ordenados. Creo que este proceso es similar a Merge Sort.
3

Si es posible ordenar sus elementos, entonces hay una manera mucho más rápida de hacer esto, luego hacer búsquedas clave en una tabla hash o b-tree. Sin embargo, si sus artículos no se pueden ordenar, realmente no puede ponerlos en un árbol b de todos modos.

De todos modos, si se pueden ordenar ambas listas, entonces solo es cuestión de recorrer la lista de búsqueda en orden.

Walk lookup list
   While items in check list <= lookup list item
     if check list item = lookup list item do something
   Move to next lookup list item
Rich Schuler
fuente
Sí tan cierto. Si tiene dos listas ordenadas, solo necesita recorrerlas una vez.
denver
3

Si está utilizando .Net 3.5, puede hacer un código más limpio usando:

foreach (Record item in LookupCollection.Intersect(LargeCollection))
{
  //dostuff
}

No tengo .Net 3.5 aquí, por lo que no se ha probado. Se basa en un método de extensión. No LookupCollection.Intersect(LargeCollection)es probable que no sea lo mismo que LargeCollection.Intersect(LookupCollection)... esto último es probablemente mucho más lento.

Esto supone que LookupCollection es un HashSet

Brian
fuente
2

Si no está preocupado por chirriar hasta el último bit de rendimiento, la sugerencia de usar un HashSet o una búsqueda binaria es sólida. Sus conjuntos de datos simplemente no son lo suficientemente grandes como para que esto sea un problema el 99% del tiempo.

Pero si esta es solo una de las miles de veces que va a hacer esto y el rendimiento es crítico (y se demuestra que es inaceptable usando HashSet / búsqueda binaria), ciertamente podría escribir su propio algoritmo que recorrió las listas ordenadas haciendo comparaciones a medida que avanzaba. Cada lista se recorrería como máximo una vez y en los casos patológicos no sería malo (una vez que haya seguido esta ruta, probablemente encontrará que la comparación, suponiendo que sea una cadena u otro valor no integral, sería el gasto real y esa optimización sería el siguiente paso).

Robert Horvick
fuente