miércoles, 2 de febrero de 2011

El poder de las expresiones regulares!

En el trabajo se presentó una excelente oportunidad de comprobar el poder de las expresiones regulares o RegEx cuando un compañero estaba buscando una forma de formatear números en javascript. Básicamente lo que se quería era poder separar con una coma "," los miles, por poner un ejemplo:
Dado el siguiente número:

ejemplo 1 - número a formatear
1000000.00
transformarlo en la siguiente cadena:

ejemplo 1 - número formateado
"1,000,000.00"

Encontramos las siguiente solución en StackOverflow

ejemplo 2 - solución usando regex en javascript
//fomat number using string.replace and regex
function numberWithCommas(x) {
    return x.toString().replace(/\B(?=(?:\d{3})+(?!\d))/g, ",");
} 

Esta sencilla y elegante solución me desconcertó e intrigó ¡no se me habría ocurrido! por lo que decidí investigar como funciona y aquí les dejo mi análisis y un port de ésto código a Java.

El análisis.
Primero veamos la definición de la función replace; pueden consultar otras funciones útiles que aceptan una regex como parámetro en javascript.

ejemplo 3 - definición de la función String.replace
/**
* Replaces matches with the given string, 
* and returns the edited string.
*/
String.replace(pattern, string)

Veamos el significado de la regex usada, como solía decir Fredcpp "vamos por partes":

expresiónsignificado
/marca el inicio y el fin de la expresión regular en javascript
()los paréntesis marcan el inicio y el fin de una supexpresión.
\B"non-word boundary" selecciona una palabra es decir un conjunto de caracteres rodeado por espacios en blanco.
(?=patrón), (?!patrón)"lookahead" prosigue solo si la expresión precedente es seguida por el patrón indicado

(?=patrón) captura solo si existe un patrón a continuación.
(?!patrón) captura solo si no existe un patrón a continuación.
(?:patrón)busca el patrón completo pero no lo captura.
\d{3}captura un patrón de 3 números seguidos, por ejemplo: 681
patrón+indica que el patrón se puede repetir una o muchas veces.
(?:\d{3})+captura un o varios grupos de 3 digitos
(?!\d)captura si el siguiente elemento NO es un número


Basándome en la tabla anterior, la expresión \B(?=(?:\d{3})+(?!\d)) captura todos los caracteres delante de uno o varios grupos de tres dígitos que no vienen seguidos de un número [1].

La solución
Antes de procesar el número debemos convertirlo en una cadena ("1000000.00").
Entonces se usa la expresión para romper el número en un arreglo que consta de los siguientes elementos: ["1", "000", "000.00"]. Luego es solo cuestión de unir las partes usando una coma y obtenemos: "1,000,000.00" (asumo que esto último es lo que hace internamente replace)

Potando el código a Java}
Java soporta las mismas expresiones usadas en javascript por lo que esta función se puede escribir en java con algunos pequeños ajustes, quedando de la siguiente forma:
//fomat number using String.replaceAll and regex
public String numberWithCommas(Number number) {
    return number.toString().replaceAll("\\B(?=(?:\\d{3})+(?!\\d))", ",");
}


Hasta la próxima
Notas:
1. Si alguien tiene una mejor interpretación por favor compartirla.