ruby - solo - extraer caracteres de una cadena en r
¿Cuál es la mejor manera de analizar un cuerpo de texto en contra de múltiples(15+) expresiones regulares en cada línea? (10)
Tengo un cuerpo de texto que tengo que escanear y cada línea contiene al menos 2 y, a veces, cuatro partes de información. El problema es que cada línea puede ser 1 de 15-20 acciones diferentes.
en ruby, el código actual se parece a esto:
text.split("/n").each do |line| #around 20 times.. .............. expressions[''actions''].each do |pat, reg| #around 20 times .................
Esto es obviamente ''EL PROBLEMA''. Me las arreglé para hacerlo más rápido (en C ++ con un margen del 50%) al combinar todas las expresiones regulares en una, pero esa no es todavía la velocidad que necesito, ¡necesito analizar miles de estos archivos RÁPIDAMENTE!
Ahora mismo los emparejo con expresiones regulares, sin embargo, esto es intolerablemente lento. Empecé con ruby y salté a C ++ con la esperanza de obtener un impulso de velocidad y simplemente no está sucediendo.
He leído casualmente en PEG y análisis basados en la gramática, pero parece algo difícil de implementar. ¿Es esta la dirección a la que debería ir o hay diferentes rutas?
Básicamente estoy analizando historiales de manos de póquer y cada línea de la historia de la mano generalmente contiene 2-3 bits de información que necesito recopilar: quién era el jugador, cuánto dinero o qué cartas conllevaba la acción ... etc.
Ejemplo de texto que debe ser analizado:
buriedtens posts $5 The button is in seat #4 *** HOLE CARDS *** Dealt to Mayhem 31337 [8s Ad] Sherwin7 folds OneMiKeee folds syhg99 calls $5 buriedtens raises to $10
Después de recopilar esta información, cada acción se convierte en un nodo xml.
En este momento mi implementación de Ruby es mucho más rápida que la de C ++, pero eso es problemático. Solo porque no he escrito en el código c por más de 4-5 años
ACTUALIZACIÓN: no quiero publicar todo el código aquí, pero hasta ahora mis manos / segundo se ven así:
588 hands/second -- boost::spirit in c++ 60 hands/second -- 1 very long and complicated regex in c++ (all the regexen put together) 33 hands/second -- normal regex style in ruby
Actualmente estoy probando antlr para ver si podemos ir más lejos pero a partir de ahora estoy muy contento con los resultados de spirit.
Pregunta relacionada: consultar de manera eficiente una cadena contra expresiones múltiples múltiples.
¿La expresión regular coincide alguna vez con la superposición? Es decir, cuando dos o más expresiones regulares coinciden con la misma línea, ¿siempre coinciden con diferentes partes de la línea (sin superposición)?
Si las coincidencias nunca se superponen, ejecute su búsqueda utilizando una expresión regular que combine las 15 expresiones regulares que tiene ahora:
regex1|regex2|regex3|...|regex15
Utilice grupos de captura si necesita poder determinar cuál de las 15 expresiones regulares concuerda.
Buscar sus datos una vez para una expresión regular larga será más rápido que buscarlo 15 veces. Cuánto más rápido depende del motor de expresiones regulares que está utilizando y la complejidad de sus expresiones regulares.
Ver coincidencia de expresiones regulares puede ser simple y rápido (pero es lento en Java, Perl, PHP, Python, Ruby, ...) . Dependiendo del volumen de sus datos y de la complejidad de su expresión regular, podría ser más rápido escribir su propia lógica de análisis sintáctico.
Yo sugeriría
- Boost Spirit o
- Antlr si la gramática es compleja;
- Xpressive si es un poco más simple,
- Tokenizador y código hecho a mano si es trivial.
Buena suerte
Boost.Spirit es una biblioteca fantástica que le permite realizar un análisis detallado del analizador, y dado que el analizador sintáctico se genera y compila directamente en su código, debe ser mucho más rápido que una solución calculada dinámicamente. La sintaxis se realiza principalmente con plantillas de expresión (un término sofisticado para muchos operadores sobrecargados), lo que significa que realmente las escribe directamente en su código.
Aquí hay una forma de hacerlo, si usaba Perl.
copiado de perldoc perlfaq6
while (<>) {
chomp;
PARSER: {
m/ /G( /d+/b )/gcx && do { print "number: $1/n"; redo; };
m/ /G( /w+ )/gcx && do { print "word: $1/n"; redo; };
m/ /G( /s+ )/gcx && do { print "space: $1/n"; redo; };
m/ /G( [^/w/d]+ )/gcx && do { print "other: $1/n"; redo; };
}
}
Para cada línea, el bucle PARSER
primero intenta hacer coincidir una serie de dígitos seguidos por un límite de palabra. Este partido tiene que comenzar en el lugar donde quedó el último partido (o el comienzo de la cadena en el primer partido). Como m/ /G( /d+/b )/gcx
usa la bandera c
, si la cadena no coincide con esa expresión regular, perl no restablece pos()
y la siguiente coincidencia comienza en la misma posición para probar un patrón diferente.
Otra idea es si tienes un servidor quad quad u oct core para usarlo.
Construya una tubería de procesamiento que divide el trabajo. La Etapa Uno podría cortar archivos en un juego o entregarlos a cada uno, luego escribirlos en uno de los ocho tubos de la Etapa Dos que leen los datos, los procesan y producen resultados de alguna manera, probablemente en una base de datos en otra máquina.
En mi experiencia, estos diseños multiproceso basados en tuberías son casi tan rápidos y fáciles de depurar que los diseños de subprocesos múltiples. También sería fácil configurar un grupo de máquinas utilizando sockets de red en lugar de pipes.
Pruebe una prueba simple en Perl. Lea sobre la función de "estudio". Lo que podría intentar es:
- Lea el archivo completo o una gran cantidad de líneas si estos archivos son muy grandes en una sola cadena
- Agregue un número de línea al comienzo de cada línea sobre la marcha.
- "estudia" la cuerda. Esto construye una tabla de búsqueda por carácter, puede ser grande.
- Ejecuta coincidencias de expresiones regulares en la cadena, limitadas por líneas nuevas (usa los modificadores de expresiones regulares m y s). La expresión debería extraer el número de línea junto con los datos.
- Establezca un elemento de matriz indexado por número de línea a los datos encontrados en esa línea, o haga algo aún más inteligente.
- Finalmente puede procesar los datos almacenados en la matriz.
No lo he intentado, pero podría ser interesante.
OK, esto hace las cosas más claras (historiales de mano de póker). Supongo que estás haciendo una herramienta de estadísticas (factor de agresión, fue al enfrentamiento, voluntariamente metiste $ en una olla, etc.). No estoy seguro de por qué necesita velocidades excesivas para eso; incluso si está haciendo multitabling con 16 mesas, las manos solo deberían hacer cosquillas a una velocidad moderada.
No sé Ruby, pero en Perl haría una pequeña declaración de cambio, al mismo tiempo que obtengo las partes significativas en $ 1, $ 2, etc. En mi experiencia, esto no es más lento que hacer comparaciones de cadenas y luego dividirlas la línea con otros medios.
HAND_LINE: for ($Line)
{ /^/*/*/* ([A-Z ]+)/ and do
{ # parse the string that is captured in $1
last HAND_LINE; };
/^Dealt to (.+) /[(.. ..)/]$/ and do
{ # $1 contains the name, $2 contains the cards as string
last HAND_LINE; };
/(.+) folds$/ and do
{ # you get the drift
last HAND_LINE; }; };
No creo que realmente puedas hacerlo más rápido. Coloque las verificaciones de las líneas que ocurren más en una primera posición (probablemente las declaraciones de plegado) y aquellas que finalmente aparecen escasamente (iniciando una nueva mano, "*** NEXT PHASE ***"
).
Si descubres que la lectura real del archivo es un cuello de botella, tal vez puedas echar un vistazo a los módulos que puedes usar para abordar archivos grandes; para Perl, Tie::File
viene a la mente.
Asegúrate de leer cada mano solo una vez. No vuelva a leer todos los datos después de cada mano, sino que guarde, por ejemplo, una tabla hash de las identificaciones de mano ya analizadas.
He leído casualmente en PEG y análisis basados en la gramática, pero parece algo difícil de implementar. ¿Es esta la dirección a la que debería ir o hay diferentes rutas?
Personalmente, me encantan los PEG. Tal vez tarde un poco en sentirse cómodo con ellos, sin embargo creo que son mucho más fáciles de mantener que es una clara victoria. Encuentro que el código de análisis es la fuente de muchos errores inesperados a medida que encuentras nuevos casos extremos en las entradas. Las gramáticas declarativas con no terminales me son más fáciles de actualizar cuando esto sucede en comparación con el código pesado de expresión regular de bucle y condición. Naming es poderoso.
En Ruby está Treetop, que es un generador de analizadores que usa PEG. Recientemente me pareció bastante agradable reemplazar un analizador gramatical escrito a mano con una breve gramática.
Para un problema como este, cerraría los ojos y usaría un generador de analizadores Lexer +. Puede vencer eso con la optimización de la mano, probablemente, pero es mucho más fácil usar un generador. Además, es mucho más flexible cuando la entrada cambia repentinamente.