13 de julio de 2020

Cuantificadores +, *, ? y {n}

Digamos que tenemos una cadena como +7 (903) -123-45-67 y queremos encontrar todos los números en ella. Pero contrastando el ejemplo anterior, no estamos interesados en un solo dígito, sino en números completos: 7, 903, 123, 45, 67.

Un número es una secuencia de 1 o más dígitos \d. Para marcar cuántos necesitamos, podemos agregar un cuantificador.

Cantidad {n}

El cuantificador más simple es un número entre llaves: {n}.

Se agrega un cuantificador a un carácter (o a una clase de caracteres, o a un conjunto [...], etc) y especifica cuántos necesitamos.

Tiene algunas formas avanzadas, veamos los ejemplos:

El recuento exacto: {5}

\d{5} Denota exactamente 5 dígitos, igual que \d\d\d\d\d.

El siguiente ejemplo busca un número de 5 dígitos:

alert( "Tengo 12345 años de edad".match(/\d{5}/) ); //  "12345"

Podemos agregar \b para excluir números largos: \b\d{5}\b.

El rango: {3,5}, coincide 3-5 veces

Para encontrar números de 3 a 5 dígitos, podemos poner los límites en llaves: \d{3,5}

alert( "No tengo 12, sino 1234 años de edad".match(/\d{3,5}/) ); // "1234"

Podemos omitir el límite superior

Luego, una regexp \d{3,} busca secuencias de dígitos de longitud 3 o más:

alert( "No tengo 12, sino, 345678 años de edad".match(/\d{3,}/) ); // "345678"

Volvamos a la cadena +7(903)-123-45-67.

Un número es una secuencia de uno o más dígitos continuos. Entonces la expresión regular es \d{1,}:

let str = "+7(903)-123-45-67";

let numbers = str.match(/\d{1,}/g);

alert(numbers); // 7,903,123,45,67

Abreviaciones

Hay abreviaciones para los cuantificadores más usados:

+

Significa “uno o más”, igual que {1,}.

Por ejemplo, \d+ busca números:

let str = "+7(903)-123-45-67";

alert( str.match(/\d+/g) ); // 7,903,123,45,67
?

Significa “cero o uno”, igual que {0,1}. En otras palabras, hace que el símbolo sea opcional.

Por ejemplo, el patrón ou?r busca o seguido de cero o uno u, y luego r.

Entonces, colou?r encuentra ambos color y colour:

let str = "¿Debo escribir color o colour?";

alert( str.match(/colou?r/g) ); // color, colour
*

Significa “cero o más”, igual que {0,}. Es decir, el carácter puede repetirse muchas veces o estar ausente.

Por ejemplo, \d0* busca un dígito seguido de cualquier número de ceros (puede ser muchos o ninguno):

alert( "100 10 1".match(/\d0*/g) ); // 100, 10, 1

Compáralo con + (uno o más):

alert( "100 10 1".match(/\d0+/g) ); // 100, 10
// 1 no coincide, ya que 0+ requiere al menos un cero

Más ejemplos

Los cuantificadores se usan con mucha frecuencia. Sirven como el “bloque de construcción” principal de expresiones regulares complejas, así que veamos más ejemplos.

Regexp para fracciones decimales (un número con coma flotante): \d+\.\d+

En acción:

alert( "0 1 12.345 7890".match(/\d+\.\d+/g) ); // 12.345

Regexp para una “etiqueta HTML de apertura sin atributos”, tales como <span> o <p>.

  1. La más simple: /<[a-z]+>/i

    alert( "<body> ... </body>".match(/<[a-z]+>/gi) ); // <body>

    La regexp busca el carácter '<' seguido de una o más letras latinas, y el carácter '>'.

  2. Mejorada: /<[a-z][a-z0-9]*>/i

    De acuerdo al estándar, el nombre de una etiqueta HTML puede tener un dígito en cualquier posición excepto al inicio, tal como <h1>.

    alert( "<h1>Hola!</h1>".match(/<[a-z][a-z0-9]*>/gi) ); // <h1>

Regexp para “etiquetas HTML de apertura o cierre sin atributos”: /<\/?[a-z][a-z0-9]*>/i

Agregamos una barra opcional /? cerca del comienzo del patrón. Se tiene que escapar con una barra diagonal inversa, de lo contrario, JavaScript pensaría que es el final del patrón.

alert( "<h1>Hola!</h1>".match(/<\/?[a-z][a-z0-9]*>/gi) ); // <h1>, </h1>
Para hacer más precisa una regexp, a menudo necesitamos hacerla más compleja

Podemos ver una regla común en estos ejemplos: cuanto más precisa es la expresión regular, es más larga y compleja.

Por ejemplo, para las etiquetas HTML debemos usar una regexp más simple: <\w+>. Pero como HTML tiene normas estrictas para los nombres de etiqueta, <[a-z][a-z0-9]*> es más confiable.

¿Podemos usar <\w+> o necesitamos <[a-z][a-z0-9]*>?

En la vida real, ambas variantes son aceptables. Depende de cuán tolerantes podamos ser a las coincidencias “adicionales” y si es difícil o no eliminarlas del resultado por otros medios.

Tareas

importancia: 5

Escriba una regexp para encontrar puntos suspensivos: 3 (¿o más?) puntos en una fila.

Revísalo:

let regexp = /tu regexp/g;
alert( "Hola!... ¿Cómo vas?.....".match(regexp) ); // ..., .....

Solución:

let regexp = /\.{3,}/g;
alert( "Hola!... ¿Cómo vas?.....".match(regexp) ); // ..., .....

Tenga en cuenta que el punto es un carácter especial, por lo que debemos escaparlo e insertarlo como \..

Escribe una regexp para encontrar colores HTML escritos como #ABCDEF: primero # y luego 6 caracteres hexadecimales.

Un ejemplo de uso:

let regexp = /...tu regexp.../

let str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2 #12345678";

alert( str.match(regexp) )  // #121212,#AA00ef

P.D. En esta tarea no necesitamos otro formato de color como #123 o rgb(1,2,3), etc.

Necesitamos buscar # seguido de 6 caracteres hexadecimales.

Un carácter hexadecimal se puede describir como [0-9a-fA-F]. O si usamos la bandera i, entonces simplemente [0-9a-f].

Entonces podemos buscar 6 de ellos usando el cuantificador {6}.

Como resultado, tenemos la regexp: /#[a-f0-9]{6}/gi.

let regexp = /#[a-f0-9]{6}/gi;

let str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2"

alert( str.match(regexp) );  // #121212,#AA00ef

El problema es que también encuentra el color en secuencias más largas:

alert( "#12345678".match( /#[a-f0-9]{6}/gi ) ) // #123456

Para corregir eso, agregamos \b al final:

// color
alert( "#123456".match( /#[a-f0-9]{6}\b/gi ) ); // #123456

// sin color
alert( "#12345678".match( /#[a-f0-9]{6}\b/gi ) ); // null
Mapa del Tutorial