19

Necesito hacer coincidir una serie de palabras con el usuario contra un diccionario grande de palabras (para garantizar que exista el valor introducido).Estructura de datos eficiente para la búsqueda de palabras con comodines

lo tanto, si el usuario ha introducido:

"orange" it should match an entry "orange' in the dictionary. 

Ahora el problema es que el usuario también puede introducir un comodín o una serie de caracteres comodín como dicen

"or__ge" which would also match "orange" 

Los requisitos principales son:

* this should be as fast as possible. 

* use the smallest amount of memory to achieve it. 

Si el tamaño de la lista de palabras era pequeño, podría usar una cadena que contiene todo e palabras y usa expresiones regulares.

Sin embargo, dado que la lista de palabras podría contener potencialmente cientos de miles de enteros, supongo que esto no funcionaría.

Entonces, ¿algún tipo de 'árbol' será el camino a seguir para esto ...?

¡Cualquier comentario o sugerencia sobre esto sería totalmente apreciado!

Gracias de antemano, Matt

+1

No estoy seguro, pero creo que un Suffix Tree podría ser lo que estás buscando - http://en.wikipedia.org/wiki/Suffix_tree – Rubys

+1

Tienes que admitir todos los comodines estilo grep o solo ? (guión bajo _ en su caso)? –

+0

¿Los comodines coinciden solo con un solo carácter o pueden coincidir con una cadena de longitud arbitraria? – drawnonward

Respuesta

15

Ponga su lista de palabras en un DAWG (gráfico de palabras acíclica dirigido) como se describe en Appel and Jacobsen's paper on the World's Fastest Scrabble Program (free copy en Columbia). Para su búsqueda, recorrerá este gráfico manteniendo un conjunto de punteros: en una carta, realiza una transición determinista a niños con esa letra; en un comodín, agrega todos los hijos al conjunto.

La eficacia será más o menos la misma que la interpretación de NFA de Thompson para grep (son el mismo algoritmo). La estructura DAWG es extremadamente eficiente en el espacio — mucho más que simplemente almacenar las palabras. Y es fácil de implementar.

El costo del peor de los casos será el tamaño del alfabeto (26?) Elevado a la potencia del número de comodines. Pero a menos que su consulta comience con N comodines, una simple búsqueda de izquierda a derecha funcionará bien en la práctica. Sugeriría prohibir que una consulta comience con demasiados comodines, o bien crear varios dawgs, por ejemplo, dawg para imagen especular, dawg para rotar a la izquierda tres caracteres, y así sucesivamente.

Coincidir con una secuencia arbitraria de comodines, por ejemplo, ______ siempre va a ser costoso porque hay muchas soluciones combinatorias. El dawg enumerará todas las soluciones muy rápidamente.

+0

Como no tengo acceso a las publicaciones, me pregunto una cosa: ¿construyen un DAWG para cada longitud diferente o no? Creo que podría acelerar considerablemente la búsqueda, ya que en este caso sabemos de antemano cuántas letras tiene la palabra que buscamos. –

+0

@Matthieu: Google obtendrá el documento, pero también he agregado un enlace (posiblemente efímero). En cuanto a un DAWG por longitud, puede hacer esto, pero es una compensación de tiempo y espacio. El DAWG almacenará una larga lista de palabras de manera muy efectiva con mucho intercambio. Con un DAWG por longitud, perderá ese intercambio. En cuanto a la aceleración, es una pregunta experimental, y los experimentos pueden surgir de forma diferente según el caché de la máquina. –

2

Me gustaría probar primero la solución de expresiones regulares y ver si es lo suficientemente rápido - usted puede ser sorprendido! :-)

Sin embargo, si eso no fuera lo suficientemente bueno, probablemente usaría un árbol de prefijos para esto.

La estructura básica es un árbol donde:

  • Los nodos en el nivel superior son todas las posibles primeras letras (es decir, probablemente, 26 nodos de a-z suponiendo que está utilizando un diccionario completo ...).
  • El siguiente nivel inferior contiene todas las posibles segundas cartas para cada primera letra dada
  • Y así sucesivamente hasta llegar a un "final de la palabra" marcador para cada palabra

comprobar si una determinada cadena con comodines Está contenido en su diccionario, entonces es solo un algoritmo recursivo simple donde usted tiene una coincidencia directa para cada posición de personaje, o en el caso del comodín, verifica cada una de las posibles ramas.

En el peor de los casos (todos los comodines pero solo una palabra con el número correcto de letras justo al final del diccionario), recorrería todo el árbol, pero esto solo es O (n) en el tamaño del diccionario, por lo que no es peor que una exploración regex completa. En la mayoría de los casos, se necesitarían muy pocas operaciones para encontrar una coincidencia o confirmar que no existe tal coincidencia ya que las grandes ramas del árbol de búsqueda se "podan" con cada letra sucesiva.

3

Independientemente del algoritmo que elija, tiene una compensación entre la velocidad y el consumo de memoria.

Si puede permitirse ~ O (N * L) memoria (donde N es el tamaño de su diccionario y L es la longitud promedio de una palabra), puede probar este algoritmo muy rápido. Para simplificar, asumirá el alfabeto latino con 26 letras y MAX_LEN como la longitud máxima de la palabra.

Crear una matriz 2D de conjuntos de números enteros, set<int> table[26][MAX_LEN].

Para cada palabra en el diccionario de usted, añadir el índice palabra a los conjuntos en las posiciones que corresponden a cada una de las letras de la palabra. Por ejemplo, si "naranja" es la palabra 12345º en el diccionario, agregue 12345 a los conjuntos correspondientes a [o] [0], [r] [1], [a] [2], [n] [ 3], [g] [4], [e] [5].

Luego, para recuperar palabras que corresponden a "or..ge", se encuentra la intersección de los conjuntos en [o] [0], [r] [1], [g] [4], [e] [5].

1

Usted puede tratar de una cadena de matriz:

0,1: A 
1,5: APPLE 
2,5: AXELS 
3,5: EAGLE 
4,5: HELLO 
5,5: WORLD 
6,6: ORANGE 
7,8: LONGWORD 
8,13:SUPERLONGWORD 

Vamos a llamar a esto un índice de matriz irregular, de sobra algo de memoria. Pídalo en longitud y luego en orden alfabético. Para abordar un personaje utilizo la notación x,y:z: x es el índice, y es la longitud de la entrada, z es la posición. La longitud de su cadena es f y g es el número de entradas en el diccionario.

  • m Crear una lista, que contiene los posibles índices de concordancia x.
  • Iterar en z de 0 a f.
    • ¿Es un comodín y no el último carácter de la cadena de búsqueda?
      • Continuar con el bucle (todo el partido).
    • ¿m está vacío?
      • búsqueda a través de todas x 0-g para y que coincide con la longitud. !!¡¡UN!!
        • ¿El carácter z coincide con la cadena de búsqueda en ese z? Guarde x en m.
      • ¿m está vacío? Break loop (no coincide).
    • ¿m no está vacío?
      • Busque en todos los elementos de m. !!¡¡SEGUNDO!!
        • ¿no corresponde con la búsqueda? Eliminar de m.
      • ¿m está vacío? Break loop (no coincide).

Un comodín siempre pasará el "Partido con la cadena de búsqueda?". Y m se ordena igualmente como la matriz.

!! A !!: Binary search en la longitud de la cadena de búsqueda. O(log n)
!! B !!: búsqueda binaria por orden alfabético. O(log n)

La razón para usar una matriz de cuerdas es que ya almacena la longitud de cada cuerda (porque la hace buscar más rápido), pero también le da la longitud de cada entrada (asumiendo otros campos constantes), tales que puede encontrar fácilmente la siguiente entrada en la matriz, para iterar rápidamente. Ordenar la matriz no es un problema: ya que esto solo se hace una vez que el diccionario se actualiza, y no durante el tiempo de búsqueda.

0

Si se le permite ignorar el caso, lo que supongo, a continuación, haga todas las palabras en su diccionario y todos los términos de búsqueda en el mismo caso antes que cualquier otra cosa. La mayúscula o minúscula no hace diferencia. Si tiene algunas palabras que distinguen entre mayúsculas y minúsculas y otras que no lo son, divida las palabras en dos grupos y busque cada una por separado.

Usted solo es palabras coincidentes, entonces usted puede partir el diccionario en una variedad de cadenas. Como solo hace una coincidencia exacta con una longitud conocida, divida la matriz de palabras en una matriz separada para cada longitud de palabra. Entonces por Longitud [3] es la matriz de todas las palabras con longitud 3. Cada matriz de palabras debe ser ordenada.

Ahora tiene una serie de palabras y una palabra con posibles comodines para encontrar. Dependiendo de dónde estén y de dónde estén los comodines, hay algunos enfoques.

Si el término de búsqueda no tiene comodines, realice una búsqueda binaria en la matriz ordenada. Podría hacer un hash en este punto, que sería más rápido pero no demasiado. Si la gran mayoría de los términos de búsqueda no tienen comodines, considere una tabla hash o una matriz asociativa con la clave hash.

Si el término de búsqueda tiene comodines después de algunos caracteres literales, realice una búsqueda binaria en la matriz ordenada para encontrar un límite superior e inferior, luego realice una búsqueda lineal en ese límite. Si los comodines están todos detrás, es suficiente encontrar un rango no vacío.

Si el término de búsqueda comienza con comodines, la matriz ordenada no es de ayuda y tendría que hacer una búsqueda lineal a menos que conserve una copia de la matriz ordenada por cadenas hacia atrás. Si crea una matriz así, selecciónela cada vez que haya más caracteres finales que literales iniciales. Si no permite comodines principales, entonces no hay necesidad.

Si el término de búsqueda comienza y termina con comodines, entonces tiene una búsqueda lineal dentro de las palabras con la misma longitud.

Así que una matriz de matrices de cadenas. Cada matriz de cadenas está ordenada y contiene cadenas de igual longitud. Opcionalmente, duplique toda la estructura con la clasificación basada en cadenas hacia atrás para el caso de comodines principales.

El espacio total es de uno o dos punteros por palabra, más las palabras. Debería poder almacenar todas las palabras en un solo buffer si su lenguaje lo permite. Por supuesto, si su lenguaje no lo permite, grep es probablemente más rápido de todos modos. Para un millón de palabras, eso es 4-16MB para las matrices y similar para las palabras reales.

Para un término de búsqueda sin comodines, el rendimiento sería muy bueno.Con comodines, ocasionalmente habrá búsquedas lineales en grupos grandes de palabras. Con el desglose por longitud y un solo personaje principal, nunca debería necesitar buscar más de un pequeño porcentaje del total del diccionario, incluso en el peor de los casos. La comparación de palabras completas de longitud conocida siempre será más rápida que la coincidencia de cadenas genéricas.

+1

"Si el término de búsqueda comienza y termina con comodines, entonces está atrapado con una búsqueda lineal dentro de las palabras con la misma longitud". Mira mi respuesta: omito los comodines solo si no es lo último en la cadena de búsqueda (en el caso de una búsqueda con comodines completos, que es lineal), lo que lo obliga a usar la búsqueda binaria, sin importar si es comodín. . – Pindatjuh

0

Intente crear un Generalized Suffix Tree si el diccionario se corresponde con la secuencia de consultas. Hay un algoritmo de tiempo lineal que se puede usar para construir dicho árbol (Ukkonen Suffix Tree Construction).

Puede hacer coincidir fácilmente (es O (k), donde k es el tamaño de la consulta) cada consulta al atravesar desde el nodo raíz y usar el carácter comodín para hacer coincidir cualquier carácter como hallazgo de patrón típico en árbol de sufijos.

Cuestiones relacionadas