2010-12-31 18 views
6

Estoy escribiendo un Intellisense/Autocomplete como el que encuentras en Visual Studio. Todo está bien hasta cuando la lista contiene probablemente más de 2000 elementos.¿Cuál es la manera más rápida de filtrar una lista de cadenas al hacer una lista de Intellisense/Autocomplete?

estoy usando una declaración de LINQ simple para hacer el filtrado:

var filterCollection = from s in listCollection 
         where s.FilterValue.IndexOf(currentWord,  
         StringComparison.OrdinalIgnoreCase) >= 0 
         orderby s.FilterValue 
         select s; 

entonces asignar esta colección para ItemSource de un cuadro de lista de WPF, y ese es el final de la misma, funciona bien.

Teniendo eso en cuenta, el Listbox también se virtualiza también, por lo que solo habrá como máximo de 7 a 8 elementos visuales en la memoria y en el árbol visual.

Sin embargo, la advertencia ahora es que, cuando el usuario escribe extremadamente rápido en el richtextbox, y en cada tecla ejecuto el filtrado + enlace, existe esta condición de semirreproducción, o filtrado fuera de sincronización, como el primero El filtrado de los trazos clave todavía podría estar haciendo su trabajo de filtrado o enlace, mientras que el cuarto trazo clave también está haciendo lo mismo.

Sé que podría demorarme un poco antes de aplicar el filtro, pero estoy tratando de lograr un filtrado perfecto como el de Visual Studio.

No estoy seguro de dónde se encuentra exactamente mi problema, así que también lo estoy atribuyendo al funcionamiento de cadena de IndexOf, o tal vez mi lista de cadenas podría optimizarse en algún tipo de índice, lo que podría acelerar la búsqueda.

Cualquier sugerencia de ejemplos de código es muy bienvenida.

Gracias.

+0

¿Ha verificado que este es, de hecho, el cuello de botella? Solo tengo curiosidad por saber si hay otro código que pueda estar contribuyendo y, sin crear perfiles, no puede estar seguro de dónde está el cuello de botella. +1 por cierto, me encantan las preguntas como esta. –

+0

Hola Andrew, me alegro de haber hecho una pregunta esclarecedora :). Es solo una suposición, me refiero a filtrar o vincular al control que es el cuello de botella. Tampoco estoy del todo seguro de si realmente puede tener una función Intellisense/Autocomplete list que sea extremadamente eficiente con tantos elementos y en crecimiento. No soy exactamente bueno con el uso de herramientas de creación de perfiles, ¿tiene alguna buena recomendación sobre cómo puedo perfilar esto? He intentado el estudio visual interno, sin embargo, el gráfico bonito apunta a Application.Run ... = ( –

+0

¿Estás seguro de que el ListBox está virtualizado correctamente, ya que este código de filtro real debería ejecutarse en alrededor de un milisegundo? –

Respuesta

1

Sugeriría tratar de limitar el conjunto de resultados en algunos elementos y ver si el problema desaparece. Es decir, puede tener 5000 para elegir, pero trate de devolver como máximo 100, incluso si hay más coincidencias. Di:

var filterCollection = (from s in listCollection 
    where s.FilterValue.IndexOf(currentWord,StringComparison.OrdinalIgnoreCase)>=0 
    orderby s.FilterValue 
    select s).Take(100); 

Si el problema desaparece, la desaceleración puede ser causada por muchos artículos que son devueltos para el cuadro de lista. No estoy seguro de que el problema desaparezca, ya que el ListBox está virtualizado, pero vale la pena intentarlo. También puede intentar lo mismo, pero limitando el resultado del filtrado a 100 elementos, antes de ordenar (es decir, ordenar por) y ver si eso ayuda. Es más eficiente de hacerlo en este orden, de todos modos:

var filterCollection = (from s in listCollection 
    where s.FilterValue.IndexOf(currentWord,StringComparison.OrdinalIgnoreCase)>=0 
    select s).Take(100) 
      .OrderBy(s => s.FilterValue); 

El resultado final es determinar si el problema está en función del número de elementos devueltos y se asigna a filterColection o del número inicial de las partidas, o ambos .

1

Latencia no es su problema si tiene un conjunto de resultados de 2000 elementos. Estoy haciendo algunas suposiciones importantes aquí, pero solo necesita devolver 500 elementos como máximo: su usuario seguirá escribiendo para limitar el conjunto de resultados hasta que tenga un tamaño aceptable para examinar.

Debe optimizar el caso común (estoy asumiendo dónde terminará con decir ~ 50 elementos) - si su usuario está desplazándose a través de una pequeña lista de 2000 elementos, algo más está mal y la interfaz necesita más trabajo .

+0

Ese es un punto bastante válido, ya que el objetivo de la función es reducir el resultado.Sin embargo, theoritcally si solo escribieron 'p' y cada uno de los 20,000 (por ejemplo) resultados, entonces eso es 20,000 elementos para examinar un contenido/indexof en, y además si el usuario realmente solo tecleó p solo y decidió desplazarse en la lista por diversión, o por alguna razón extraña, sigue siendo legítimo, por lo que limitar el tamaño de devolución puede no ser exactamente deseable. –

0

Una optimización realmente simple es

if(currentWord.StartsWith(lastWord)) 

puede filtrar la lista de elementos filtrados devueltos por la última consulta.Es decir, a menos que reduzca la cantidad de elementos devueltos por su consulta LINQ como lo sugieren algunas de las otras respuestas. Siempre puedes almacenar lo que hay en la consulta en una variable y luego hacer el Take (100) después, aunque necesitarás asegurarte de que la ejecución diferida de LINQ no te muerda en ese caso.

En el lado de encuadernación, en lugar de reemplazar toda la colección, puede usar un ObservableCollection y simplemente agregar/quitar elementos del mismo. Sería una buena idea invertir lo que devuelve tu filtro si vas a hacer eso, pero verías una respuesta mucho más rápida y no verías un rendimiento tan grande si el usuario está escribiendo rápidamente.

+0

Iba a aplicar esta técnica, pero no he tenido tiempo, mi idea era simplemente, para cada pulsación de tecla, guardo la palabra actual con el conjunto de la colección filtrada, en un diccionario, por lo que cuando el usuario retrocede, Ya tendré a mano una colección filtrada. ¿Es más pesado reemplazar todo el ItemsSource versus modificar una colección existente? –

+0

Reemplazar la fuente es mucho más lento en mi experiencia. –

1

Creo que el problema es que su filtro realiza O(n) (donde n es el número total de elementos a partir de autocompletar), es decir, tiene que ir a través de cada artículo de averiguar cuáles partido. Cuantos más elementos tenga, peor será el filtro que realizará.

En lugar de utilizar una lista, intente con un trie. Intenta realizar O(m), donde m es el número de caracteres en la cadena. Esto significa que el tamaño del conjunto de datos no afecta el rendimiento de la búsqueda.

En Promptu (un iniciador de aplicaciones que escribí), utilizo try en intellisense/autocomplete. Si desea ver un ejemplo de intentos en acción, puede descargarlo y probarlo.

+0

Eso es lo que estaba pensando en términos de costo de tiempo de ejecución. Tenía sugerencias sobre cómo implementar un Trie, aunque no he propuesto una implementación de Trie. No es exactamente bueno poner un concepto en el código. ¿Conoces algún código de ejemplo de constructor de Trie que yo pueda referenciar? Gracias. –

+0

@ Winston: Desafortunadamente, no tengo ningún código que pueda dar, pero http://stackoverflow.com/questions/3665317 podría ayudarlo. La idea es bastante simple: es solo un montón de diccionarios de caracteres anidados. –

0

Aquí está su "condición de carrera".

orderby s.FilterValue 

Considere la secuencia de letras d, o, g.

  • "d" comienza a funcionar y coincidirá con el 30% del conjunto. 6000 artículos deben ser ordenados.
  • "do" comienza a funcionar y coincidirá con el 6% del conjunto. 1200 artículos deben ser ordenados.
  • "perro" comienza a funcionar y coincidirá con el 0,5% del conjunto. 100 artículos deben ser ordenados

Al considerar las diferentes cargas de trabajo de cada evento, no es sorprendente que el último evento termine antes del primer y el segundo evento.

El comportamiento más correcto que puedo imaginar es evitar el enlace de cualquier evento activo anterior cuando se inicia un evento. Si puede evitar el enlace deteniendo la ejecución en esos eventos, mucho mejor. Recuerda esos misiles, ya no tienen objetivos.

Cuestiones relacionadas