Anuncios

Bienvenidos sean a este post, las coincidencias de patrones es una poderosa herramienta para la manipulacion de cadenas, se pueden ejecutar muchas operaciones complejas con solo unas pocas llamadas a string.gsub, sin embargo como dice el refran:

Un gran poder conlleva una gran responsabilidad

Tio Ben
Anuncios

Las coincidencias por patrones no es un reemplazo por un analizador apropiado, para programas rapidos y sucios se pueden hacer utiles manipulaciones en el codigo fuente pero es dificil construir un producto con calidad, como un buen ejemplo consideremos el patron que usamos para coincidir con los comentarios en un programa de C ‘/%*. -%*/’ si tu programa tiene una cadena literal conteniendo “/*” podria producir un resultado incorrecto:

> texto = [[ char s[] = "una /* aqui"; /* una cadena engañosa */ ]]
> print(string.gsub(texto, "/%*.-%*/", "<COMENTARIO>"))
 char s[] = "una <COMENTARIO>   1
Anuncios

Cadenas con tales contenidos son raros y el patron deberia funcionar correctamente pero esto esta bien para uso personal y no para su distribucion, por lo general la coincidencia de patrones es lo bastante eficiente para programas de Lua, por ejemplo a un Pentium de 333 MHz (un equipo prehistorico) le toma una decima de segundo encontrar todas las palabras en un texto de 200000 caracteres siendo alrededor de 30000 palabras pero podes tomar precauciones haciendo al patron tan especifico como sea posible porque los patrones sueltos son mas lentos que los especificos.

Anuncios

Un ejemplo extremo es ‘(.-)%$’ que va a intentar conseguir todo el texto de una cadena hasta que encuentre el primer signo de pesos ($), si la cadena que trabajamos tiene un signo de pesos todo funcionara correctamente pero vamos a suponer que la cadena no posea dicho caracter, el algoritmo primero intentara coincidir el patron con la primera posicion de la cadena, ira a traves de toda la cadena buscando por un signo de pesos cuando la cadena termine el patron falla para la primera posicion de la cadena, luego el algoritmo hara la busqueda nuevamente pero comenzando desde la segunda posicion de la cadena, solo para descubrir que aca tampoco coincide y asi sucesivamente, dando como resultado en un tiempo cuadratico que resultan en mas de tres horas para el equipo y cadena anteriormente descriptos 😱

Anuncios

Esto se puede solucionar simplemente anclando el patron con ‘^(.-)%$’,’, el ancla le dice al algoritmo que detenga a busqueda si no encuentra una coincidencia en la primera posicion, con esta ancla el patron en menos de una decima de segundo pero cuidado con los patrones vacios porque coincidira con cadenas vacias, por ejemplo si intentas coincidir nombres con un patron ‘%a*’ vas a encontrar nombres en cualquier parte:

> i,j = string.find(";$%   **#$hola13","%a*")
> print(i, j)
1       0
Anuncios

En este caso la llamada a string.find ha encontrado correctamente una secuencia vacia de letras al comienzo de la cadena, no tiene sentido escribir un patron que comienza o termina con el modificador ‘-‘ proque solo coincide con la cadena vacia, el modificador siempre necesita algo alrededor para anclar su expansion, de forma similar un patron ‘.*’ es engañosa porque esta construccion puede expandir mucho mas de lo que pretendias, algunas veces es mas util usar el Lua mismo para construir un patron, este truco ya lo usamos con la funcion para convertir espacios en tabs en este post, veamos el siguiente ejemplo donde intentaremos encontrar largas lineas dentro de un texto, consideremos lineas con mas de 70 caracteres y esta se diferencia desde el caracter de la nueva linea, para ello podemos coincidir un unico caracter diferente de nueva linea con la clase de caracter ‘[^\n]’, por lo tanto podemos coincidir una larga linea con un patron que se repite 70 veces para un caracter, seguido por cero o estos caracteres, en lugar de escribir este patron a mano podemos crearlo con string.rep:

patron = string.rep("[^\n]", 70) .. "[^\n]*"
Anuncios

Para el siguiente ejemplo vamos a suponer una busqueda que no sea case sensitive (es decir que no se vea afectada por mayusculas o minusculas) y una manera para ello es cambiar cualquier x letra en el patron por la clase ‘(xX)’, esta clase incluye tanto a las versiones de mayusculas y minusculas de la letra original y esto se puede automatizar con la siguiente funcion:

function nocase(s)
	s = string.gsub(s, "%a", function(c)
		return "[" .. string.lower(c) .. string.upper(c) .. "]"
	end)
	return s
end
Anuncios

Aca lo mas importante es la funcion anonima que recibe caracter por caracter lo producido por string.gsub y devolveremos la concatenacion del caracter en mayuscula y minuscula, si lo probamos obtendremos la siguiente salida:

> print(nocase("Hola, como estan?"))
[hH][oO][lL][aA], [cC][oO][mM][oO] [eE][sS][tT][aA][nN]?
Anuncios

Algunas veces queremos cambiar cada coincidencia simple de la cadena s1 a la cadena s2 sin considerar a ningun caracter como magico, si ambas cadenas son literales se pueden agregar escapes apropiados para caracteres magicos mientras escribes las cadenas pero si estas cadenas son valores variables, se puede usar otro string.gsub para poner los escapes por ti:

s1 = string.gsub(s1, "(%W)", "%%%1")
s2 = string.gsub(s2, "%%", "%%%%")
Anuncios

En la cadena de busqueda escapamos de todos los caracteres no alfanumericos (para esto es la W), en el reemplazo solo escapamos al caracter %, otra tecnica util para la coincidencia de patrones es pre-procesar la cadena informada antes del trabajo real, vamos a suponer que queremos cambiar a mayusculas todas las comillas encomilladas en un texto donde una cadena encomillada comienza y termina con una doble comilla (“) pero puede contener escape de comillas (\”):

s = [[ a continuacion una tipica cadena: "Esto es \"buenisimo\"!!" ]]
Anuncios

Nuestro enfoque para manejar tales casos es pre-procesar el texto algo asi como codificar la problematica secuencia a algo mas, por ejemplo podriamos cambiar el (\”) como (\1) sin embargo si el texto original ya contiene a (\1) va a meternos en problemas, una manera facil de codificar y evitar este inconviente es codificar todas las secuencias “\x” como “\ddd” donde ddd es la representacion decimal del x caracter:

function codificar(s)
	return (string.gsub(s, "\\(.)", function(x)
		return string.format("\\%03d", string.byte(x)))
	end))
end
Anuncios

De ahora en mas cualquier secuencia “\ddd” en la cadena codificada debe venir de la codificacion porque cualquier “\ddd” de la cadena original tambien ha sido codificado, asi que la decodificacion se convertira en una tarea facil:

function decodificar(s)
	return (string.gsub(s, "\\(%d%d%d)", function(d)
		return "\\" .. string.char(d)
	end))
end
Anuncios

Con todo esto podemos completar nuestra tarea, como la cadena codificada no contiene ningun escape del tipo \” podemos simplemente buscar cadenas encomilladas con ‘”.-“‘, veamos el siguiente ejemplo:

> s = [[ a continuacion una tipica cadena: "Esto es \"buenisimo\"!!" ]]
> s = codificar(s)
> s = string.gsub(s, '".-"', string.upper)
> s = decodificar(s)
> print(s)
 a continuacion una tipica cadena: "ESTO ES \"BUENISIMO\"!!"
Anuncios

Pero tambien podemos optimizar esta demostacion de la siguiente forma:

> print(decodificar(string.gsub(codificar(s), '".-"', string.upper)))
 a continuacion una tipica cadena: "ESTO ES \"BUENISIMO\"!!"

Como pueden ver el resultado es el mismo y si bien es mas practico no es mas practico de individualizar y ver como trabaja cada accion pero tengan en cuenta esto a la hora de trabajar con algo tan concreto.

Anuncios

En resumen, hoy hemos visto como algunos trucos para trabajar con reemplazos, algunos consejos, algunos atajos, algunos inconvenientes que encontraremos y como solucioonarlo, espero les haya sido util sigueme en Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Tengo un Patreon donde podes acceder de manera exclusiva a material para este blog antes de ser publicado, sigue los pasos del link para saber como.

Tambien podes donar

Es para mantenimiento del sitio, gracias!

$1.00