2012-01-13 19 views
12

El problema: una gran lista estática de cadenas se proporciona como A, una cadena larga se proporciona como B, las cadenas en A son todas muy cortas (una lista de palabras clave), quiero comprobar si cada cadena en A es una subcadena de B y obténgalos.búsqueda de cadenas cortas masivas de alto rendimiento en Python

Ahora uso un bucle simple como:

result = [] 
for word in A: 
    if word in B: 
     result.append(word) 

Pero es una locura lenta cuando A contiene ~ 500.000 o más elementos.

¿Hay alguna biblioteca o algoritmo que se ajuste a este problema? Hice mi mejor esfuerzo para buscar pero no tuve suerte.

¡Gracias!

+0

sólo una teoría - lo que si se intenta usar 'B.find (palabra)' 'si en lugar de la palabra en B'? Creo que 'in' es rápido si la subcadena está realmente en' B', pero será mucho más lenta si no es una subcadena. 'encontrar' podría ser más rápido. – birryree

+0

@birryree Gracias por el comentario, pero en mis pruebas usando un 'B.find (palabra)' en lugar de 'B' palabra en no hacer ningún esfuerzo en el rendimiento :( –

Respuesta

14

Su problema es suficientemente grande como para que probablemente tenga que golpearlo con el algoritmo bat.

Eche un vistazo al algoritmo Aho-Corasick. La declaración del problema es una paráfrasis del problema que aborda este algoritmo.

Además, vea el trabajo de Nicholas Lehuen con su paquete PyTST.

También hay referencias en un mensaje de desbordamiento de pila relacionados que mencionan otros algoritmos tales como Rabin-Karp: Algorithm for linear pattern matching?

+0

1 -.. esta es la respuesta Pensé en un [trie] (http://en.wikipedia.org/wiki/Trie) -based enfoque, pero esto es aún mejor. – senderle

+0

Gracias tanto que tengo que funcione perfectamente, aquí es el resultado de mi prueba: '2012-01-13 11: 48: 07,632212 Importación cases' prueba' 2012-01-13 11: 48: 17.191975 scaning usando in'' 2012-01-13 11: 48: 47.750070 Escaneado completado'' 2012-01-13 11: 48: 47.752614 TSTing'' 2012-01-13 11: 48: 56.780503 Scaning using tst'' 2012-01-13 11: 48: 56.798033 Scan completed' –

1

No sé si esto sería con mayor rapidez, pero es mucho más Pythonic:

result = [x for x in A if x in B] 
+3

sí, es más Pythonic, pero no más rápido que sucederá: ( –

+0

(1) no creo que hay una mejor solución. (Aparte del uso de un lenguaje desempeño en mente, como C). – FakeRainBrigand

1

empacar todas las palabras individuales de B en una nueva lista, que consiste en la cadena original dividido por ' '. Luego, para cada elemento en B, pruebe la pertenencia a cada elemento de A. Si encuentra uno (o más), elimínelos del A y salga cuando A esté vacío.

Parece que su enfoque se extenderá a través de 500,000 candidatos sin que se establezca un opt-out.

+0

lo siento, no dejan en claro, que las cadenas están en china, por lo que las palabras son no separados por espacios voy a tener que hacer mucho más trabajo para averiguar "todas las palabras individuales de 'B'" –

+1

@FelixYan mi último comentario a continuación, debe ser mi único consejo útil para usted;.. encontrar una manera de bajar de su lista de candidatos a medida que peinar 'B' un miembro menos en el bucle 'for' exterior usando' A' acelerará sus tiempos de búsqueda, no importa cómo se van haciendo sobre él – Droogans

2

Dependiendo de la duración de su cadena larga es, puede valer la pena hacer algo como esto:

ls = 'my long string of stuff' 
#Generate all possible substrings of ls, keeping only uniques 
x = set([ls[p:y] for p in range(0, len(ls)+1) for y in range(p+1, len(ls)+1)]) 

result = [] 
for word in A: 
    if word in x: 
     result.append(word) 

Obviamente, si su cadena larga es muy, muy larga, esto también se vuelve demasiado lenta, pero debería ser más rápida para cualquier cadena de unos pocos cientos de caracteres.

+1

el OP señaló un detalle muy importante aquí: está analizando caracteres chinos. Por lo tanto, 'ls = 'mylongstringofstuff'', y no se asignará a un conjunto entre las combinaciones de los índices de' ls' muy útilmente. – Droogans

+0

@Droogans Publiqué esto antes de que lo agregara, pero todavía no veo el problema. Let 'ls = '解析 す る た め に い く つ か の 文字' como un ejemplo al azar de caracteres (japoneses) - lo anterior sigue siendo (casi) funciona (en realidad hay un pequeño error en lo que he escrito que estoy tratando de arreglar , pero creo que la idea todavía está bien). Editar: el error debe ser reparado. – Yuushi

+0

Me disculpo. Si estás compilando tus ejemplos usando caracteres kanji sin espacios, y estás obteniendo resultados útiles, entonces no debo entender tu código (o el problema) tan claramente como debería. – Droogans

1

Suponga que tiene todas las palabras clave de la misma longitud (más adelante se podría extender esta algo para diferentes longitudes)

que podría sugerir siguiente:

  1. precalcular algunos hash para cada palabra clave (por ejemplo, XOR hash):

    hash256 = reduce(int.__xor__, map(ord, keyword)) 
    
  2. crear un diccionario donde la clave es un hash, y la lista de valores de palabras clave correspondientes

  3. Guardar longitud de palabra clave

    curr_keyword = [] 
    for x in B: 
        if len(curr_keyword) == keyword_length: 
        hash256 = reduce(int.__xor__, map(ord, curr_keyword)) 
        if hash256 in dictionary_of_hashed: 
         #search in list 
    
        curr_keyword.append(x) 
        curr_keyword = curr_keyword[1:] 
    

Algo como esto

Cuestiones relacionadas