Expresiones rápidas y regulares Sintaxis

1. Introducción

En pocas palabras, las expresiones regulares (expresiones regulares o expresiones regulares para abreviar) son una forma de especificar patrones de cadena. Sin duda, está familiarizado con la función de búsqueda y reemplazo en su editor de texto favorito o IDE. Puedes buscar palabras y frases exactas. También puede activar opciones, como la insensibilidad de mayúsculas, para que la búsqueda de la palabra "color" también encuentre "Color", "COLOR" y "CoLoR". Pero, ¿qué ocurre si desea buscar las variaciones ortográficas de la palabra "color" (ortografía americana: color, ortografía británica: color) sin tener que realizar dos búsquedas separadas??

Si ese ejemplo parece demasiado simple, ¿qué tal si desea buscar todas las variaciones ortográficas del nombre inglés "Katherine" (Catherine, Katharine, Kathreen, Kathryn, etc., por nombrar algunas)? En general, es posible que desee buscar en un documento todas las cadenas que se parecen a números hexadecimales, fechas, números de teléfono, direcciones de correo electrónico, números de tarjetas de crédito, etc..

Las expresiones regulares son una forma poderosa de abordar (parcial o totalmente) estos (y muchos otros) problemas prácticos relacionados con el texto..

contorno

La estructura de este tutorial es la siguiente. Presentaré los conceptos básicos que debe comprender adaptando un enfoque utilizado en los libros de texto teóricos (después de eliminar cualquier rigor o pedantería innecesarios). Prefiero este enfoque porque le permite ubicar su comprensión de tal vez el 70% de la funcionalidad que necesitará, en el contexto de algunos principios básicos. El 30% restante son características más avanzadas que puedes aprender más adelante o saltar, a menos que quieras convertirte en un maestro de expresiones regulares.

Hay una gran cantidad de sintaxis asociada con las expresiones regulares, pero la mayoría está ahí para permitirte aplicar las ideas centrales de la manera más sucinta posible. Presentaré estos incrementales, en lugar de eliminar una gran tabla o lista para que los memorice..

En lugar de saltar directamente a una implementación Swift, exploraremos lo básico a través de una excelente herramienta en línea que lo ayudará a diseñar y evaluar expresiones regulares con la mínima cantidad de fricción y equipaje innecesario. Una vez que se sienta cómodo con las ideas principales, escribir el código Swift es básicamente un problema de mapear su comprensión con la API de Swift.

En todo momento, trataremos de mantener una mentalidad pragmática. Las expresiones regulares no son la mejor herramienta para cada situación de procesamiento de cadenas. En la práctica, necesitamos identificar las situaciones en las que las expresiones regulares funcionan muy bien y las situaciones en las que no. También hay un punto medio donde las expresiones regulares se pueden usar para hacer parte del trabajo (generalmente, un preprocesamiento y filtrado) y el resto del trabajo queda en la lógica algorítmica.

Conceptos básicos

Las expresiones regulares tienen sus fundamentos teóricos en la "teoría de la computación", uno de los temas estudiados por la ciencia de la computación, donde desempeñan el papel de la entrada aplicada a una clase específica de máquinas de computación abstracta llamadas autómatas finitos..

Sin embargo, relájese, no es necesario que estudie los antecedentes teóricos para usar expresiones regulares en la práctica. Solo los menciono porque el enfoque que usaré para motivar inicialmente las expresiones regulares desde cero refleja el enfoque utilizado en los libros de texto de informática para definir expresiones regulares "teóricas".

Suponiendo que tenga cierta familiaridad con la recursión, me gustaría que tenga en cuenta cómo se definen las funciones recursivas. Una función se define en términos de versiones más simples de sí misma y, si realiza un seguimiento a través de una definición recursiva, debe terminar en un caso base que se define explícitamente. Lo menciono porque nuestra definición a continuación también será recursiva..

Tenga en cuenta que, cuando hablamos de cadenas en general, implícitamente tenemos un conjunto de caracteres en mente, como ASCII, Unicode, etc. Supongamos por el momento que vivimos en un universo en el que las cadenas están compuestas por las 26 letras en minúsculas Alfabeto (a, b,… z) y nada más..

Reglas

Comenzamos por afirmar que cada carácter de este conjunto puede considerarse como una expresión regular que coincide con una cadena. Asi que una como una expresión regular coincide con "a" (considerada como una cadena), segundo es una expresión regular que coincide con la cadena "b", etc. Digamos también que hay una expresión regular "vacía" Ɛ que coincide con la cadena vacía "". Tales casos corresponden a los "casos básicos" triviales de la recursión..

Ahora, consideramos las siguientes reglas que nos ayudan a hacer nuevas expresiones regulares a partir de las existentes:

  1. los concatenación (es decir, "encadenar") de cualquiera de las dos expresiones regulares es una nueva expresión regular que coincide con la concatenación de cualquiera de las dos cadenas que coinciden con las expresiones regulares originales.
  2. los alternancia de dos expresiones regulares es una nueva expresión regular que coincide con cualquiera de las dos expresiones regulares originales.
  3. los Estrella de Kleene de una expresión regular coincide con cero o más instancias adyacentes de cualquier expresión coincidente con la expresión regular original.

Hagamos esto concreto con varios ejemplos simples con nuestras cadenas alfabéticas.

Ejemplo 1

De la regla 1, unasegundo siendo expresiones regulares que coinciden con "a" y "b", significa ab es una expresión regular que coincide con la cadena "ab". Ya que ab y do son expresiones regulares, a B C es una expresión regular que coincide con la cadena "abc", y así sucesivamente. Continuando de esta manera, podemos hacer expresiones regulares largas y arbitrarias que coincidan con una cadena con caracteres idénticos. Nada interesante ha sucedido todavía..

Ejemplo 2

De la regla 2, o y una siendo expresiones regulares, o | a coincide con "o" o "a". La barra vertical representa alternancia.. do y t Son expresiones regulares y, combinadas con la regla 1, podemos afirmar que c (o | a) t Es una expresión regular. Los paréntesis se están utilizando para agrupar.

Que coincide? do y t solo se emparejan, lo que significa que el regex c (o | a) t coincide con "c" seguido de una "a" o una "o" seguida de "t", por ejemplo, la cadena "cat" o "cuna". Tenga en cuenta que lo hace no emparejar "abrigo" como o | a solo coincide con "a" u "o", pero no ambas a la vez. Ahora las cosas empiezan a ponerse interesantes..

Ejemplo 3

De la regla 3, una* coincide con cero o más instancias de "a". Coincide con la cadena vacía o las cadenas "a", "aa", "aaa", y así sucesivamente. Vamos a ejercer esta regla en conjunto con las otras dos reglas.

Que hace caliente ¿partido? Coincide con "ht" (con cero instancias de "o"), "hot", "hoot", "hooot", etc. Qué pasa b (o | a) *? Puede coincidir con "b" seguido de cualquier número de instancias de "o" y "a" (sin incluir ninguno de ellos). "b", "boa", "baa", "bao", "baooaoaoaoo" son solo algunas de las infinitas cadenas de caracteres que esta expresión regular coincide. Tenga en cuenta de nuevo que los paréntesis se están utilizando para agrupar la parte de la expresión regular a la que * se está aplicando.

Ejemplo 4

Intentemos descubrir expresiones regulares que coincidan con las cadenas que ya tenemos en mente. ¿Cómo haríamos una expresión regular que reconozca el murmullo de las ovejas, lo que consideraré como cualquier número de repeticiones del sonido básico "baa" ("baa", "baabaa", "baabaabaa", etc.)

Si tú dijiste, (balido)*, entonces usted es casi correcto. Pero note que esta expresión regular también coincidirá con la cadena vacía, lo cual no queremos. En otras palabras, queremos ignorar a las ovejas que no hacen ruido.. BAA Baa)* Es la expresión regular que estamos buscando. Del mismo modo, una vaca murmurando podría ser moo (moo) *. ¿Cómo podemos reconocer el sonido de cualquiera de los animales? Sencillo. Usar alternancia. baa (baa) * | moo (moo) *

Si ha entendido las ideas anteriores, felicidades, está en el buen camino..

2. Asuntos de sintaxis.

Recordemos que pusimos una restricción tonta en nuestras cuerdas. Sólo podrían estar compuestas de letras minúsculas del alfabeto. Ahora eliminaremos esta restricción y consideraremos todas las cadenas compuestas de caracteres ASCII.

Debemos darnos cuenta de que, para que las expresiones regulares sean una herramienta conveniente, ellas mismas deben ser representadas como cadenas. Entonces, a diferencia de lo anterior, ya no podemos usar caracteres como *, |, (, ), etc., sin indicar de alguna manera si los estamos utilizando como caracteres "especiales" que representan alternancia, agrupación, etc. o si los estamos tratando como caracteres comunes que deben combinarse literalmente.

La solución es tratar estos y otros "metacaracteres" que pueden tener un significado especial. Para cambiar entre un uso y otro, necesitamos poder escapar de ellos. Esto es similar a la idea de usar "\ n" (escapar de la n) para indicar una nueva línea en una cadena. Es un poco más complicado, ya que, dependiendo del carácter de contexto que normalmente es "meta", podría representar su yo literal sin escape. Veremos ejemplos de esto más adelante..

Otra cosa que valoramos es la concisión. Muchas expresiones regulares que se pueden expresar usando solo la notación de la sección anterior serían tediosamente verbosas. Por ejemplo, suponga que solo desea buscar las dos cadenas de caracteres compuestas por una letra minúscula seguida de un número (por ejemplo, cadenas como "a0", "b9", "z3", etc.). Usando la notación que discutimos anteriormente, esto resultaría en la siguiente expresión regular:

(a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z) (0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9)

Solo tecleando ese monstruo me borró.

No hace [abcdefghijklmnopqrstuvwxyz] [0123456789] ¿Pareces una mejor representación? Nota los metacaracteres El y ] que significan un conjunto de caracteres, cualquiera de los cuales da una coincidencia positiva. En realidad, si consideramos que las letras de la A a la Z y los números del 0 al 9 aparecen en secuencia en el conjunto ASCII, podemos reducir la expresión regular hasta un punto frío. [a-z] [0-9].

Dentro de los límites de un conjunto de caracteres, el guión, -, Es otro metacarácter que indica un rango. Tenga en cuenta que puede comprimir varios rangos en el mismo par de corchetes. Por ejemplo, [0-9a-zA-Z] Puede coincidir con cualquier carácter alfanumérico. los 9 y una (y  z y UNA)apretados unos contra otros pueden parecer graciosos, pero recuerde que las expresiones regulares se basan en la brevedad y el significado es claro.

Hablando de brevedad, hay formas aún más concisas de representar ciertas clases de personajes relacionados, como veremos en un minuto. Tenga en cuenta que la barra de alternancia, |, Sigue siendo válida y útil la sintaxis como veremos en un momento..

Más sintaxis

Antes de empezar a practicar, echemos un vistazo a la sintaxis..

Período

El período, ., coincide con cualquier carácter individual, con la excepción de los saltos de línea. Esto significa que Connecticut puede coincidir con "cat", "crt", "c9t", "c% t", "c.t", "c t", y así sucesivamente. Si quisiéramos hacer coincidir el período como un carácter ordinario, por ejemplo, para que coincida con la cadena "c.t", podríamos escapar de él (Connecticut) o ponerlo en una clase de personaje propia (Connecticut).

En general, estas ideas se aplican a otros metacaracteres, como El, ], (, )*, y otros que no hemos encontrado todavía.

Paréntesis

Paréntesis (( y )) se utilizan para agrupar como vimos antes. Vamos a usar la palabra simbólico para significar un solo carácter o una expresión entre paréntesis. La razón es que muchos operadores de expresiones regulares pueden aplicarse a cualquiera.

Los paréntesis también se utilizan para definir grupos de captura, Lo que le permite averiguar qué parte de su partido fue capturado por un grupo de captura particular en la expresión regular. Hablaré más sobre esta funcionalidad muy útil más adelante..

Más

UNA + siguiendo un token es una o más instancias de ese token. En nuestro ejemplo de ovejas., BAA Baa)* podría representarse más sucintamente como (balido)+. Recordar que * significa cero o más ocurrencias. Tenga en cuenta que (balido)+ es diferente de balido+, porque en el primero el + se aplica a la balido token mientras que en este último solo se aplica a la una antes de eso. En este último, coincide con cadenas como "baa", "baaa" y "baaaa".

Signo de interrogación

UNA ? seguir un token significa cero o una instancia de ese token.

Práctica

RegExr es una excelente herramienta en línea para experimentar con expresiones regulares. Cuando se sienta cómodo leyendo y escribiendo expresiones regulares, será mucho más fácil usar la API de expresiones regulares del marco de Foundation. Incluso así, será más fácil probar primero su expresión regular en tiempo real en el sitio web.

Visite el sitio web y concéntrese en la parte principal de la página. Esto es lo que verás:

Ingresa una expresión regular en el cuadro en la parte superior e ingresa el texto en el que está buscando coincidencias.

La "/ g" al final del cuadro de expresión no es parte de la expresión regular per se. Es un indicador que afecta el comportamiento de coincidencia global del motor de expresiones regulares. Al agregar "/ g" a la expresión regular, el motor busca todas las coincidencias posibles de la expresión regular en el texto, que es el comportamiento que queremos. El punto culminante azul indica una coincidencia. Pasar el mouse sobre la expresión regular es una forma útil de recordarle el significado de sus partes constitutivas..

Sepa que las expresiones regulares vienen en varios sabores, según el idioma o la biblioteca que esté utilizando. Esto no solo significa que la sintaxis puede ser un poco diferente entre los distintos sabores, sino también las capacidades y características. Swift, por ejemplo, utiliza la sintaxis de patrón especificada por ICU. No estoy seguro de qué sabor se usa en RegExr (que se ejecuta en JavaScript), pero dentro del alcance de este tutorial, son bastante similares, si no idénticos.

También lo invito a explorar el panel en el lado izquierdo, que tiene mucha información presentada de manera concisa.

Nuestro primer ejemplo práctico

Para evitar posibles confusiones, debo mencionar que, al hablar de la concordancia de expresiones regulares, podemos significar una de estas dos cosas:

  1. buscando cualquiera (o todas) las subcadenas de una cadena que coincida con una expresión regular
  2. verificar si la cadena completa coincide con la expresión regular

El significado predeterminado con el que operan los motores de expresiones regulares es (1). De lo que hemos estado hablando hasta ahora es (2). Afortunadamente, es fácil implementar el significado (2) mediante metacaracteres que se presentarán más adelante. No te preocupes por esto por ahora.

Comencemos de manera simple probando nuestro ejemplo de ovejas. Tipo (balido)+ en el cuadro de expresión y algunos ejemplos para probar coincidencias como se muestra a continuación.

Espero que entiendas por qué los partidos que tuvieron éxito realmente tuvieron éxito y por qué los otros fallaron. Incluso en este ejemplo simple, hay algunas cosas interesantes que señalar.

Partidos codiciosos

¿La cadena "baabaa" contiene dos coincidencias o una? En otras palabras, ¿cada "baa" individual es una coincidencia o toda la "baabaa" es una única coincidencia? Esto se reduce a si se busca o no un "partido codicioso". Un partido codicioso intenta hacer coincidir la mayor cantidad posible de una cadena..

En este momento, el motor de expresiones regulares hace coincidir con avidez, lo que significa que "baabaa" es una única coincidencia. Hay formas de hacer una comparación perezosa, pero ese es un tema más avanzado y, como ya tenemos nuestros platos llenos, no lo cubriremos en este tutorial..

La herramienta RegExr deja un espacio pequeño pero perceptible en el resaltado si dos partes adyacentes de una cadena coinciden individualmente (pero no colectivamente) con la expresión regular. Veremos un ejemplo de este comportamiento en un momento..

Mayúsculas y minúsculas

"Baabaa" falla debido a la mayúscula "B". Digamos que desea permitir que solo la primera "B" esté en mayúsculas, ¿cuál sería la expresión regular correspondiente? Intenta resolverlo por ti mismo primero.

Una respuesta es (B | b) aa (baa) *. Ayuda si lo lees en voz alta. Una "b" mayúscula o minúscula, seguida de "aa", seguida de cero o más instancias de "baa". Esto es viable, pero tenga en cuenta que esto podría ser rápidamente inconveniente, especialmente si quisiéramos ignorar el uso de mayúsculas por completo. Por ejemplo, tendríamos que especificar alternativas para cada caso, lo que resultaría en algo difícil de manejar como ([Bb] [Aa] [Aa])+.

Afortunadamente, los motores de expresiones regulares suelen tener una opción para ignorar mayúsculas y minúsculas. En el caso de RegExr, haga clic en el botón que dice "banderas" y marque la casilla de verificación "ignorar mayúsculas". Observe que la letra "i" se adjunta a la lista de opciones al final de la expresión regular. Pruebe algunos ejemplos con mayúsculas y minúsculas, como "bAABaa".

Otro ejemplo

Intentemos diseñar una expresión regular que pueda capturar variantes del nombre "Katherine". ¿Cómo abordarías este problema? Anotaré tantas variaciones, miraré las partes comunes y luego trataré de expresar en palabras las variaciones (con énfasis en las letras alternativas y opcionales) como una secuencia. A continuación, intentaría formular la expresión regular que asimila todas estas variaciones.

Probémoslo con esta lista de variaciones: Katherine, Katharine, Catherine, Kathreen, Kathleen, Katryn y Catrin. Te dejaré a ti que escribas varios más si quieres. En cuanto a estas variaciones, puedo decir más o menos que:

  • el nombre comienza con "k" o "c"
  • seguido de "a"
  • seguido posiblemente por una "h"
  • posiblemente seguido de una "a" o "e"
  • seguido de una "r" o "l"
  • seguido de uno de "i", "ee" o "y"
  • y definitivamente seguido por una "n"
  • posiblemente una "e" al final

Con esta idea en mente, se me ocurre la siguiente expresión regular:

[kc] ath? [ae]? (r | l) (i | ee | y) ne?

Tenga en cuenta que la primera línea "KatherineKatharine" tiene dos coincidencias sin ninguna separación entre ellas. Si lo observa detenidamente en el editor de texto de RegExr, puede observar la pequeña ruptura en el resaltado entre las dos coincidencias, que es de lo que estaba hablando anteriormente..

Tenga en cuenta que la expresión regular anterior también coincide con nombres que no consideramos y que podrían no existir, por ejemplo, "Cathalin". En el contexto presente, esto no nos afecta negativamente en absoluto. Pero en algunas aplicaciones, como la validación de correo electrónico, desea ser más específico sobre las cadenas que coincide y las que rechaza. Esto generalmente aumenta la complejidad de la expresión regular..

Más sintaxis y ejemplos

Antes de pasar a Swift, me gustaría discutir algunos aspectos más de la sintaxis de las expresiones regulares.

Representaciones concisas

Varias clases de personajes relacionados tienen una representación concisa:

  • \ w carácter alfanumérico, incluido el subrayado, equivalente a [a-zA-Z0-9_]
  • \re representa un dígito, equivalente a [0-9]
  • \ s representa espacios en blanco, es decir, espacio, tabulador o salto de línea

Estas clases también tienen clases negativas correspondientes:

  • \ W representa un carácter no alfanumérico, sin subrayado
  • \RE un no-dígito
  • \ S un personaje no espacial

Recuerde las clases no capitalizadas y luego recuerde que la correspondiente con mayúsculas coincide con lo que la clase sin capitalizar no coincide. Tenga en cuenta que estos pueden combinarse incluyendo corchetes interiores si es necesario. Por ejemplo, [\ s \ S] Representa a cualquier personaje, incluyendo saltos de línea. Recordemos que el periodo . coincide con cualquier carácter excepto saltos de línea.

Anclas

^ y PS son anclajes que representan el inicio y el final de una cadena respectivamente. ¿Recuerda que escribí que podría querer hacer coincidir una cadena completa, en lugar de buscar coincidencias de subcadenas? Así es como haces eso.. ^ c [oau] t $ coincide con "gato", "cuna" o "corte", pero no, por ejemplo, "captura" o "recorte".

Límites de palabras

\segundo representa un límite entre palabras, como el espacio o la puntuación, y también el principio o el final de la cadena. Tenga en cuenta que es un poco diferente, ya que coincide con una posición en lugar de un carácter explícito. Podría ayudar pensar en un límite de palabra como un divisor invisible que separa una palabra de la anterior / siguiente. Como es de esperar, \SEGUNDO representa "no es un límite de palabra". \ bcat \ b encuentra coincidencias en "cat", "a cat", "Hola, cat", pero no en "acat" o "catch".

Negación

La idea de negación se puede hacer más específica usando el ^ metacarácter dentro de un conjunto de caracteres. Este es un uso completamente diferente de ^ a partir de "inicio de cadena de anclaje". Esto significa que, para la negación., ^ Debe usarse en un juego de caracteres justo al inicio.. [^ a] coincide con cualquier carácter además de la letra "a" y [^ a-z] coincide con cualquier carácter excepto una letra minúscula.

Puedes representar \ W ¿Usando rangos de negación y carácter? La respuesta es [^ A-Za-z0-9_]. Qué piensas [a ^] ¿partidos? La respuesta es un carácter "a" o "^" ya que no se produjo al principio del conjunto de caracteres. Aquí "^" coincide literalmente.

Alternativamente, podríamos escapar explícitamente de esta manera: [\ ^ a]. Con suerte, estás empezando a desarrollar cierta intuición sobre cómo funciona el escape..

Cuantificadores

Vimos como * (y +) se puede usar para hacer coincidir un token cero o más veces (y una o más) veces. Esta idea de hacer coincidir un token varias veces se puede hacer más específica usando cuantificadores en llaves. Por ejemplo, 2, 4  significa de dos a cuatro partidos del token anterior. 2, significa dos o más partidos y 2 significa exactamente dos partidos.

Veremos ejemplos detallados que utilizan la mayoría de estos elementos en el siguiente tutorial. Pero por el bien de la práctica, lo invito a inventar sus propios ejemplos y probar la sintaxis que acabamos de ver con la herramienta RegExr..

Conclusión

En este tutorial, nos hemos centrado principalmente en la teoría y la sintaxis de las expresiones regulares. En el siguiente tutorial, añadimos Swift a la mezcla. Antes de continuar, asegúrese de entender lo que hemos cubierto en este tutorial jugando con RegExr.