2011-04-13 27 views
8

Tengo que reconocer una gran lista de URL (pocos millones de líneas) como pertenecientes a una categoría en particular o no. Tengo otra lista que tiene sub-cadenas que, si están presentes en la URL, pertenecen a esa categoría. Diga, Categoría A.Buscando una manera más rápida de realizar búsquedas de cadenas

La lista de subcadenas a verificar tiene alrededor de 10k tales subcadenas. Lo que hice fue simplemente ir línea por línea en el archivo de subcadena y buscar la coincidencia, y si la URL pertenecía a la Categoría A. Encontré en las pruebas que esto consumía bastante tiempo.

No soy un estudiante de ciencias de la computación, así que no tengo mucho conocimiento sobre la optimización de algoritmos. Pero, ¿hay alguna forma de hacerlo más rápido? Solo ideas simples. El lenguaje de programación no es un gran problema, pero Java o Perl serían preferibles.

La lista de subcadenas que coincidan no cambiará mucho. Sin embargo, recibiré diferentes listas de URL, así que debo ejecutar esto cada vez que lo reciba. El cuello de botella parece ser las URL, ya que pueden ser muy largas.

+1

puede usar algún sistema de recuperación de información (es decir, Lucene - en Java) para indexar las URL, y luego buscar la cadena, la indexación llevará mucho tiempo, pero ahorrará tiempo para cada "consulta", sin tener que iterar sobre toda la lista. – amit

+1

10k veces, digamos, 10 millones es lo que, 100 mil millones? sí, eso tomará un tiempo sin importar el idioma. si algo está en la categoría A, ¿significa que no pueden estar en ninguna otra categoría?si es así, puede eliminar todo de la lista grande que se asigna a una categoría –

+1

la lista de subcadenas es constante, no hay ninguna razón para que tome mucho tiempo, consulte mi respuesta, la longitud de la lista solo afecta el tamaño tomado en memoria para el autómata e incluso eso probablemente sea pequeño – Asaf

Respuesta

8

Sí, he implementado el algoritmo Aho-Corasick algorithm en java para el problema que usted está sugiriendo y mostraron una mejora constante de aproximadamente x180 sobre la aplicación ingenua (lo que está haciendo). Hay varias implementaciones disponibles en línea, aunque las modificaría para un mejor rendimiento. Tenga en cuenta que la complejidad de las soluciones está limitada por la longitud de la palabra (en su caso, la URL) y no por el número de subcadenas. además, solo requiere un pase en promedio para emparejar.

P.S - Solíamos dar esta pregunta a las personas en las entrevistas de trabajo, por lo que hay muchas maneras de resolverlo. El que ofrezco es el que usamos en el código de producción, que (por ahora) supera a todas las demás soluciones.

Editar: escribió el nombre equivocado algoritmo previamente, fijo ...

+0

Oye, gracias, el algoritmo de Aho-Corasick funcionó como un encanto . obtuvo una mejora de x50 en la implementación ingenua. solo una curiosidad más, ¿cuál fue el rendimiento del algoritmo KMP? sería eso aún más rápido? :) – sfactor

+1

Nah, olvídate de KMP. Eso fue un error que cometí (copié el nombre del correo equivocado) el algoritmo Aho-Corasick es lineal en la longitud de la entrada y fácil de comprender/implementar. No me molestaría con más optimización a menos que sea realmente necesario. lo que hice para acelerarlo es simplemente usar matrices en todas partes para representar los bordes entre los nodos (a diferencia de los mapas) y el algoritmo original devuelve la ubicación de la coincidencia. puedes acelerarlo aún más si agitas esa habilidad. – Asaf

1

Puede comprimir las subcadenas en clases que comparten el mismo prefijo. Esto debería reducir el tiempo por un margen significativo.

Si está buscando coincidencias al cambiar la cadena por 1 en cada iteración, puede mejorar bastante la velocidad con un algoritmo mejor (como con expresiones regulares).

+0

La optimización del prefijo se realiza automáticamente si coloca todas las subcadenas en una expresión regular, al menos por una norma razonablemente optimizada motores de expresión. – jmg

2

Sugiero usar el venerable Grep en lugar de utilizar un lenguaje de programación para esta tarea. Utiliza el rápido Boyer-Moore string searching algorithm, que debería ser suficiente para unos pocos millones de líneas.

+0

No estoy seguro de que grep sea eficiente aquí, el algoritmo salta adelante si no es posible emparejar lo que funciona bien para un pequeño número de palabras, si tienes 10k palabras, grep probablemente tendrá dificultades para saltar adelante (o volver dependiendo de la optimización) ya que habrá muchos prefijos comunes (o sufijos) – Asaf

3

Por supuesto, existen diferentes enfoques para optimizar esto. En cuanto a tu formación, te dibujaré una simple. Lo cual supone que la lista de subcadenas no cambia muy a menudo.

  1. Genera una enorme expresión regular de todas las subcadenas.
  2. Recopila esa expresión regular, ver. la clase Pattern en Java, por ejemplo. Almacene la referencia a esa expresión regular compilada.
  3. Usa la misma expresión regular compilada para que coincida con cada url.
+1

Apostaría que esto funcionará muy mal, ¿has intentado hacer algo así con 10k cuerdas? bullet (1) sería muy difícil de llevar a cabo de manera eficiente y salvo que el resto terminará siendo tan ineficiente como la implementación ingenua – Asaf

+0

@Asaf: funcionará mal si tiene un motor de expresiones regulares incorrecto o si no precompila el regexp. Pero de lo contrario, debería crear un autómata casi tan bueno como el algoritmo KMP. Quería dar un enfoque que sea aplicable sin conocimiento profundo de informática. De lo contrario, KMP es la solución obvia. – jmg

+1

@jmg, estoy de acuerdo KMP es un poco complejo, me refiero a Aho-Corasick y corrigió mi respuesta en consecuencia (utilicé KMP para algo diferente). Aho-Corasick tiene muchas [implementaciones] listas (https://hkn.eecs.berkeley.edu/~dyoo/java/index.html) y creo que es relativamente fácil de entender. Además, asumiendo que de alguna manera construirías la expresión regular "perfecta" de las 10k cuerdas, que creo que es un problema algorítmico más difícil que el original, no veo cómo la solución no será dependiente (en lo que se refiere a la complejidad) por el número de subcadenas – Asaf

4

Perl es muy bueno en la optimización de largas listas de cadenas alternativas en una expresión regular (hasta una cierta longitud de expresiones regulares en general compilado, donde se vuelve a un mecanismo menos eficiente). Usted debe ser capaz de construir una expresión regular para que coincida con una determinada categoría como:

$catAre = join('|', map quotemeta, @catAstrings); 
$catAre = qr/$catAre/; 
1

Para las bibliotecas Java que implementan los algoritmos de búsqueda de cuerda común ver las respuestas a https://stackoverflow.com/questions/5564610/fast-alernative-for-stringindexofstring-str. Junto con la paralelización, debería poder analizar millones de URL con bastante rapidez. Es bastante fácil de hacer; Probablemente deberías probarlo y ver si el tiempo es aceptable o no antes de buscar mucho más en la optimización.

+0

El enlace no funciona – therealprashant

1

lo escribí por primera vez como comentario, pero luego me di cuenta, yo creo que es más apropiada como respuesta
Se puede utilizar algún sistema de recuperación de información (como Apache Lucene en Java) y lo utilizan para indexar las URLs como documentos.
Luego, después de la indexación, puede recorrer las consultas y buscarlas, el resultado serán las URL coincidentes.
PROS:
* la búsqueda no requerirá iterar sobre todas las URL para cada consulta.
* si va a necesitar más adelante intersección o unión de subcadena/consultas - la biblioteca le ofrece esta funcionalidad
CONTRA:
* indexación tomará un tiempo ...
* es posible que necesite un poco de espacio extra en la RAM/disco para el índice.

Creo que es un método que vale la pena explorar, tal vez el tiempo que se consume al indexar vale la ganancia de la búsqueda.

2

He hecho este tipo de cosas antes en Perl, comparando una lista de ~ 13k palabras clave contra una corriente entrante de datos de Twitter para encontrar todos los tweets que coinciden con cualquiera de esas palabras clave (y qué palabras clave coinciden). En términos aproximados, el código es el siguiente:

use Regexp::Assemble; 
my $ra = Regexp::Assemble->new; 
$ra->add(@keywords); 
my $regex = $ra->re; 

for my $tweet (@tweets) { 
    my @matches = $tweet =~ /$regex/g; 
    # do whatever with @matches... 
} 

Tenga en cuenta que este utiliza Regexp::Assemble para construir la expresión regular, que no forma parte del núcleo de la distribución de Perl, por lo que tendrá que instalar si de CPAN si quieres adaptar este código

Si está utilizando Perl 5.10 o posterior, también existe el operador de "coincidencia inteligente" (~~) que puede hacer algo similar sin requerir ningún módulo adicional.

0

Actualmente estoy trabajando en este problema. Llegué a esta conclusión:

Aho-corasick consumirá más memoria al hacer árboles. Si no hay problema de memoria, es bueno. Pero mira en el HAT Trie una vez. Es la combinación de hash y trie (árbol). Hará un árbol en algún nivel y los caracteres restantes formarán un valor hash que debería marcarse en la tabla Hash.

Disculpe el lenguaje más técnico. Pero creo que es mejor opción HAT si estás buscando una URL específica de la lista de URL. (He formado un trie de HAT que consumirá 12MB para almacenar 6lacks de URL.)

+0

E incluso usted puede ajustarlo según su necesidad. (para la memoria de la ley o para un rendimiento más rápido) –

Cuestiones relacionadas