2008-10-10 12 views
42

Digamos que tengo 10.000 expresiones regulares y una cadena y quiero averiguar si la cadena coincide con cualquiera de ellas y obtener todas las coincidencias. La manera trivial de hacerlo sería simplemente consultar la cadena una por una contra todas las expresiones regulares. ¿Hay una manera más rápida y más eficiente de hacerlo?Consulta eficiente de una cadena contra expresiones múltiples

EDIT: He intentado sustituirlo con DFA (lex) El problema aquí es que solo le daría un patrón único. Si tengo una cadena "hola" y los patrones "[H | h] ello" y ". {0,20} ello", DFA solo coincidirá con uno de ellos, pero quiero que ambos peguen.

+0

Normalmente se trata de evitar la ambigüedad, por lo que hay criterios para elegir entre los patrones que pueden coincidir. Por lo general, se elige la coincidencia más a la izquierda más larga. Si tiene esta necesidad, probablemente tenga que escribir algo usted mismo –

+0

Responda esto mientras responde una pregunta relacionada. Desde que se formuló esta pregunta en el año 2008, he lanzado una biblioteca Java de código abierto que puede usar para construir un DFA que le dará todas las expresiones que coincidan si lo desea: http://mtimmerm.github.io/dfalex/ –

Respuesta

8

Martin Sulzmann Ha hecho bastante trabajo en este campo. Tiene a HackageDB project explicó breifly here que utiliza partial derivatives parece estar hecho a medida para esto.

El lenguaje utilizado es Haskell y, por lo tanto, será muy difícil traducirlo a un lenguaje no funcional si ese es el deseo (creo que la traducción a muchos otros lenguajes de FP todavía sería bastante difícil).

El código no se basa en convertir a una serie de autómatas y luego combinarlos, sino que se basa en la manipulación simbólica de las expresiones regulares.

También el código es muy experimental y Martin ya no es un profesor sino que está en 'empleo remunerado' (1) por lo que puede no estar interesado/no puede proporcionar ninguna ayuda o aporte.


  1. esto es una broma - me gustan los profesores, menos los más inteligentes tratan de trabajar, más posibilidades tengo de que me paguen!
+0

Estoy aceptando esta respuesta, porque he pasado por todas las otras rutas y he fallado (sí, realmente he implementado todas las otras soluciones y las encuentro incompletas en muchas áreas). He retomado el proyecto para portar la biblioteca de Haskell a C++. Puede abrir el código fuente más adelante. Esto podría no funcionar más tarde pero parece prometedor y teóricamente sólido. –

+1

buena suerte en el puerto. ¡Puedo ver que es muy popular! Háganos saber si funciona – ShuggyCoUk

+2

@shridhar lyer: ¿Alguna idea sobre el progreso? Me enfrento a una situación similar – Toad

-1

¿Intenta combinarlos en una gran expresión regular?

+0

Eso definitivamente vale la pena intentarlo - con suerte, el compilador de expresiones regulares optimizará un poco las cosas ... –

+0

que generalmente es significativamente más lento – Jimmy

+0

Esta es realmente una solución perfectamente razonable y rápida - si está usando la expresión directa basada en la simulación de NFA. Desafortunadamente, la mayoría de las implementaciones de expresiones regulares no lo hacen, por lo que esto no funcionará en la práctica. –

5

Tendría que tener alguna forma de determinar si una expresión regular dada era "aditiva" en comparación con otra. Crear una "jerarquía" de expresiones regulares que le permita determinar que todas las expresiones regulares de una determinada rama no coinciden

0

Casi me gustaría escribir un motor de expresiones regulares "de adentro hacia afuera", uno donde el "objetivo" fuera la expresión regular , y el 'término' fue la cadena.

Sin embargo, parece que su solución de probar cada uno de forma iterativa va a ser mucho más fácil.

4

Se puede combinar en grupos de tal 20.

(?=(regex1)?)(?=(regex2)?)(?=(regex3)?)...(?=(regex20)?) 

Mientras cada expresión regular tiene cero (o al menos el mismo número de) los grupos de captura, se puede ver lo que lo capturado para ver qué patrón (s) emparejado.

Si regex1 coincide, el grupo de captura 1 tendrá su texto coincidente. Si no, sería undefined/None/null/...

7

10,000 regexen eh? Eric Wendelin's sugerencia de una jerarquía parece ser una buena idea. ¿Has pensado en reducir la enormidad de estos regexen a algo así como una estructura de árbol?

Como un ejemplo simple: todos los regexen que requieren un número podrían derivarse de una comprobación de expresiones regulares para tal, todos regexen no requieren uno en otra rama. De esta forma, podría reducir el número de comparaciones reales a una ruta a lo largo del árbol en lugar de hacer cada comparación en 10,000.

Esto requeriría la descomposición de las expresiones regulares proporcionadas en géneros, teniendo cada género una prueba compartida que los descartaría si fallara. De esta forma, teóricamente podrías reducir drásticamente el número de comparaciones reales. Si tuviera que hacer esto en tiempo de ejecución, podría analizar sus expresiones regulares y "archivarlas" en géneros predefinidos (más fáciles de hacer) o géneros comparativos generados en ese momento (no tan fácil de hacer).

Su solución de comparación de "hello" a "[H | h] ello" y ". {0,20} ello" realmente no será ayudado por esta solución. Un caso simple en el que esto podría ser útil sería: si tuviera 1000 pruebas que solo devolverían verdadero si "ello" existe en alguna parte de la cadena y su cadena de prueba es "adiós"; solo tendrías que hacer una prueba en "ello" y saber que las 1000 pruebas que lo requieren no funcionarán, y debido a esto, no tendrás que hacerlas.

+0

Cualquier biblioteca que pueda hacer esto automáticamente? Esto no se puede manejar manualmente. Las expresiones regulares no están codificadas. –

+0

No conozco ninguna biblioteca que haga esto, pero podría escribir algo para analizar las expresiones regulares y "archivar" las que tiene bajo casos verificables. Incluso si solo puede hacer esto de manera muy amplia, posiblemente podría reducir el tiempo para ejecutar significativamente al descartar grandes franjas de lo que no encaja. – akdom

+1

esmre [http://code.google.com/p/esmre/] es una biblioteca de Python/C que hace algo similar automáticamente. –

-1

Creo que la respuesta corta es que sí, hay una manera de hacer esto, y que es bien conocida por la informática, y que no puedo recordar de qué se trata.

La respuesta corta es que puede encontrar que su intérprete de expresiones regulares ya se ocupa de todos estos de manera eficiente cuando juntos, o puede encontrar uno que sí lo haga. De lo contrario, es hora de que google los algoritmos de búsqueda y coincidencia de cadenas.

11

Tuvimos que hacer esto en un producto en el que trabajé una vez. La respuesta fue compilar todas sus expresiones regulares juntas en un Deterministic Finite State Machine (también conocido como autómata finito determinista o DFA). El DFA luego se puede recorrer carácter por carácter sobre su cadena y se activará un evento de "coincidencia" cuando una de las expresiones coincida.

Ventajas se ejecuta rápido (cada carácter se compara solo una vez) y no se vuelve más lento si se agregan más expresiones.

Desventajas son que requiere una enorme mesa de datos para el autómata, y hay muchos tipos de expresiones regulares que no son compatibles (por ejemplo, copias de referencias).

La que usamos fue codificada a mano por una tuerca de plantilla de C++ en nuestra compañía en ese momento, así que desafortunadamente no tengo ninguna solución de FOSS que lo dirija. Pero si busca expresiones regulares en Google o expresiones regulares con "DFA" encontrará cosas que lo orientarán en la dirección correcta.

+0

¡Una FSA es definitivamente el camino a seguir, aquí! –

+0

Esto ya lo implementamos, pero lamentablemente las desventajas que menciona es por qué estamos buscando una solución diferente. –

9

Esta es la manera en que trabajan los lexers.

Las expresiones regulares se convierten en un único autómata no determinista (NFA) y posiblemente se transforman en un autómata determinista (DFA).

El autómata resultante intentará hacer coincidir todas las expresiones regulares a la vez y tendrá éxito en una de ellas.

Existen muchas herramientas que pueden ayudarlo aquí, se llaman "generador de lexer" y existen soluciones que funcionan con la mayoría de los idiomas.

No dice qué idioma está utilizando. Para los programadores de C sugeriría echar un vistazo a la herramienta re2c. Por supuesto, el tradicional (f)lex es siempre una opción.

+0

La autogeneración de un archivo lex haría el trabajo - Espero que seas un maestro de tu sistema make si sigues este camino ;-) – ijw

+0

¡Gran observación! Esto obtiene mi voto. Estoy seguro de que hay una manera más fácil de reutilizar el código lexer sin recurrir a generar archivos fuente. – Ramon

2

Si está utilizando expresiones regulares reales (las que corresponden a los lenguajes normales de la teoría del lenguaje formal, y no a algo similar a Perl), entonces tiene suerte, porque los idiomas regulares se cierran bajo Unión. En la mayoría de los idiomas regex, pipe (|) es union. Por lo que debe ser capaz de construir una cadena (que representa la expresión regular que desee) de la siguiente manera:

(r1)|(r2)|(r3)|...|(r10000) 

en paréntesis son para la agrupación, que no coincida. Cualquier cosa que coincida con esta expresión regular coincide con al menos una de sus expresiones regulares originales.

+0

Ojalá hubiera implementaciones de expresiones regulares en los idiomas del mundo real. Desafortunadamente, Perl jodió y todos los demás los copiaron ... así que esto no funcionará en casi ningún motor de expresiones regulares. –

+0

si haces esto, ¿existe una forma eficiente de extraer la expresión regular que coincide? Imagina que tienes que recorrer los 10000 grupos buscando uno distinto de cero ... –

2

Yo diría que es un trabajo para un analizador real. Un punto medio podría ser un Parsing Expression Grammar (PEG). Es una abstracción de nivel superior de coincidencia de patrones, una característica es que puede definir una gramática completa en lugar de un patrón único. Existen algunas implementaciones de alto rendimiento que funcionan al compilar su gramática en un bytecode y ejecutarlo en una máquina virtual especializada.

descargo de responsabilidad: la única que conozco es LPEG, una biblioteca para Lua, y no fue fácil (para mí) comprender los conceptos básicos.

+2

Los PEG son mucho más potentes (y lentos) de lo necesario. La unión de un conjunto de idiomas regulares es regular; es decir, un simple viejo NFA es suficiente. –

0

Puede compilar la expresión regular en un DFA/Bucchi automata híbrido, donde cada vez que BA entra en un estado de aceptación marca qué regla de expresiones regulares "acertar".

Bucchi es un poco excesivo para esto, pero modificar la forma en que funciona su DFA podría hacer el truco.

12

Me he encontrado con un problema similar en el pasado. Usé una solución similar a the one suggested by akdom.

Tuve la suerte de que mis expresiones regulares generalmente tenían alguna subcadena que debe aparecer en cada cadena que coincide. Pude extraer estas subcadenas usando un analizador simple e indexarlas en una FSA utilizando los algoritmos de Aho-Corasick. El índice se usó para eliminar rápidamente todas las expresiones regulares que trivialmente no coinciden con una cadena dada, dejando solo algunas expresiones regulares para verificar.

Lancé el código bajo LGPL como un módulo de Python/C. Ver esmre on Google code hosting.

+1

Parece que movió el código a GitHub: https://github.com/wharris/esmre.Pensé que se lo haría saber a todos;) – blong

5

Si está pensando en términos de "10.000 expresiones regulares", debe cambiar sus procesos. Si nada más, piense en términos de "10,000 cadenas de destino para que coincidan". Luego, busque métodos no regex diseñados para lidiar con situaciones de "carga de barco de cuerdas objetivo", como las máquinas Aho-Corasick. Francamente, sin embargo, parece que algunas cosas se salieron de los rieles mucho antes en el proceso que la máquina a usar, ya que 10,000 cadenas de destino suena mucho más como una búsqueda de base de datos que una coincidencia de cadena.

-2

La forma más rápida de hacerlo parece ser algo como esto (código C#):

public static List<Regex> FindAllMatches(string s, List<Regex> regexes) 
{ 
    List<Regex> matches = new List<Regex>(); 
    foreach (Regex r in regexes) 
    { 
     if (r.IsMatch(string)) 
     { 
      matches.Add(r); 
     } 
    } 
    return matches; 
} 

Oh, que significó el código más rápido ? no sé entonces ....

+1

Optimización prematura, bla, bla ... Vea cuánto demora demasiado su solución más fácil, especialmente porque el código para implementarlo es trivial. – ijw

+0

¿Qué sugieres para ser más eficiente? – RCIX

+0

Si está probando 10.000 expresiones regulares, será muy lento. Necesitas alguna forma de combinar el árbol para obtener un analizador de un solo pase. –

1

Aho-Corasick fue la respuesta para mí.

Tenía 2000 categorías de cosas que cada una tenía listas de patrones para hacer coincidir. La longitud de cadena promedió unos 100,000 caracteres.

Advertencia principal: Los patrones que coinciden son todos los patrones de idiomas, no los patrones regex, p. 'cat' contra r'\w+'.

Yo estaba usando python y así lo usé https://pypi.python.org/pypi/pyahocorasick/.

import ahocorasick 
A = ahocorasick.Automaton() 

patterns = [ 
    [['cat','dog'],'mammals'], 
    [['bass','tuna','trout'],'fish'], 
    [['toad','crocodile'],'amphibians'], 
] 

for row in patterns: 
    vals = row[0] 
    for val in vals: 
     A.add_word(val, (row[1], val)) 

A.make_automaton() 

_string = 'tom loves lions tigers cats and bass' 

def test(): 
    vals = [] 
    for item in A.iter(_string): 
     vals.append(item) 
    return vals 

Correr %timeit test() en mis categorías 2000 con 2-3 rastros por categoría y una longitud de alrededor de _string100,000 me tiene 2.09 ms vs 631 ms haciendo secuencial re.search()315x más rápido!.

+0

¡muy cierto! Aho-Corasick es la solución probada y probada en batalla para este hecho. y @ will-harris esmre también se basa en esta poderosa estructura de datos finos –

Cuestiones relacionadas