{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<p>\n",
    "<font size='5' face='Georgia, Arial'>IIC2233 Apunte Programación Avanzada</font><br>\n",
    "<font size='1'>&copy; 2016 Karim Pichara - Christian Pieringer. Todos los derechos reservados.</font>\n",
    "<br>\n",
    "<font size='1'> Modificado 2026-1 por Equipo Docente IIC2233</font>\n",
    "</p>"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tabla de contenidos\n",
    "\n",
    "1. [*Regular Expressions*](#Regular-Expressions)\n",
    "    1. [Clases de caracteres](#clases-de-caracteres)\n",
    "    2. [Cuantificadores](#cuantificadores)\n",
    "    3. [Aserciones](#aserciones)\n",
    "    4. [Grupos y operación binaría](#grupos-y-operación-binaría)\n",
    "2. [Módulo `re`](#módulo-re)\n",
    "    1. [_Matching_](#Matching)\n",
    "        1. [Verificación de e-mail](#Verificación-de-e-mail)\n",
    "        2. [Verificación de RUT](#Verificación-de-RUT)\n",
    "    2. [Búsqueda](#Búsqueda)\n",
    "    3. [Sustitución](#Sustitución)\n",
    "    4. [`split`](#split)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# *Regular Expressions*\n",
    "\n",
    "Hasta ahora cada vez que necesitamos procesar la información contenida en *strings* lo hemos hecho mediante los mismos métodos que la clase `str` nos provee. Esto es fácil cuando el texto que analizamos es relativamente simple. Por ejemplo, si queremos separar un *string* de acuerdo a un carácter especial solo deberíamos escribir `string.split('carácter')`; o bien si necesitamos buscar una secuencia dentro de un *string* cualquiera podemos usar el método `find()` o usando `carácter in string`. Sin embargo, cuando la información está contenida en *strings* complejos o bien cuando necesitamos buscar múltiples secuencias dentro del *string*, existe una forma más eficiente y poderosa que consiste en describir patrones de búsqueda.\n",
    "\n",
    "Estos patrones de búsqueda se conocen como **expresiones regulares**, **regex** ó **RE**. Las expresiones regulares corresponden a secuencias especiales de caracteres que nos permiten comparar y buscar *strings* o conjuntos de *strings*. Las expresiones regulares se definen como un patrón y se describen a través de una sintaxis especializada. Algunos ejemplos de situaciones que pueden ser resueltas usando expresiones regulares son: validación de formularios, búsqueda y reemplazo, transformación de texto, y procesamiento de registros (*logs*).\n",
    "\n",
    "Las expresiones regulares fueron propuestas el año 1956 por el matemático [Stephen Kleene](https://en.wikipedia.org/wiki/Stephen_Cole_Kleene), y hoy en día son bastante comunes por lo que varios lenguajes de programación permiten su uso."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las expresiones regulares permiten especificar un conjunto de *strings* que hacen *match* con ella. Cada carácter en una expresión regular hace *match* exactamente con el carácter correspondiente de un *string*. Por ejemplo, la expresión regular `abcde` permite hacer *match* únicamente con el *string* `abcde`. Esto no parece muy poderoso pues hemos especificado exactamente el mismo *string* que queríamos reconocer. Sin embargo, las expresiones regulares utilizan algunos caracteres especiales, denominados **meta-caracteres** para especificar patrones más generales.\n",
    "\n",
    "Los principales meta-caracteres son: ```. ^ $ * + ? { } [ ] \\ | ( )```, y su significado es el siguiente:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Clases de caracteres\n",
    "\n",
    "Las clases de caracteres, como un abecedario, nos indican un conjunto de caracteres. Estos conjuntos de caracteres puede ser letras, números, sub-conjuntos de los mismos, e incluso caracteres especiales.\n",
    "\n",
    "- **```[ ]```** \n",
    "\n",
    "    Permite especificar una *clase de caracteres*. Por ejemplo, la expresión regular ```[abc]``` permite hacer *match* con cada uno de los *strings* `a`, `b` ó `c`. Los meta-caracteres no funcionan dentro de la especificación de una **clase de caracteres**. Por ejemplo, la expresión regular ```[abc$]``` permite hacer *match* con cada uno de los *strings* `a`, `b`, `c` ó `$`.  \n",
    "    \n",
    "    También es posible utilizar `-` dentro de una *clase* de caracteres para definir un rango de caracteres. Por ejemplo, ```[a-p]``` permite hacer *match* con un *string* que corresponda a cualquiera de los caracteres entre `a` y `p` (inclusive). Un manera sencilla de describir una expresión regular que haga *match* con cada letra ó número del alfabeto inglés es ```[a-zA-Z0-9]```.   \n",
    "    \n",
    "    Si el grupo al interior de la clase empieza con `^`, el *match* se hará con cualquier carácter excepto los de la clase (es decir,`[^abc]` hará *match* con cualquier carácter excepto `a`, `b` y `c`).\n",
    "\n",
    "- **```.```** \n",
    "\n",
    "    Permite especificar un *match* con **cualquier carácter**, excepto un salto de línea. Por ejemplo, la expresión `.` permite hacer *match* con cualquier *string* de largo 1.\n",
    "\n",
    "- **`\\d`**, **`\\D`**, **`\\s`**, **`\\S`**, **`\\w`**, **`\\W`**\n",
    "\n",
    "    Son abreviaciones pre-existentes que permiten a acceder a *clases de caracteres* ya definidas. Estas serán explicadas con mayor detalle más adelante.\n",
    "\n",
    "- **```\\```** \n",
    "\n",
    "    Permite indicar que los meta-caracteres debe ser considerados como parte del patrón y no como meta-caracteres.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Cuantificadores\n",
    "\n",
    "Los cuantificadores indican el número de caracteres o expresiones que deben coincidir.\n",
    "\n",
    "- **```+```** \n",
    "\n",
    "    Permite indicar que una expresión regular se puede repetir **una o más veces**. Por ejemplo, ```ab+c``` permite hacer *match* con `abc`, `abbc`, `abbbc`, etc., pero no con `ac`. De la misma manera, `a[bc]+d` permite hacer *match* con `abd`, `acd`, `abbd`, `abcd`, `acbd`, `accd`, `abbbd`, etc., dado que el meta-carácter se aplica sobre la clase `[bc]`.\n",
    "\n",
    "\n",
    "- **```*```** \n",
    "\n",
    "    Permite indicar que una expresión regular se puede repetir **cero o más veces**. Por ejemplo, ```ab*c``` permite hacer *match* con `ac`, `abc`, `abbc`, `abbbc`, etc. Si usamos una clase de caracteres, `a[bc]*d` permite hacer *match* con el mismo conjunto descrito por `a[bc]+d`, y también con `ad`.\n",
    "\n",
    "\n",
    "- **```?```** \n",
    "\n",
    "    Permite indicar que la expresión regular definida en el patrón puede **estar exactamente una vez, o no estar**. Por ejemplo, `ab?c` permite hacer *match* solamente son `ac` y con `abc`.\n",
    "\n",
    "\n",
    "- **```{m, n}```** \n",
    "\n",
    "    Permite indicar que la expresión regular definida puede **repetirse entre *m* y *n* veces**, inclusive. Por ejemplo, `ab{3,5}c` permite hacer *match* con `abbbc`, `abbbbc`, y con `abbbbbc`.\n",
    "    \n",
    "    También puede indicar que la expresión se **repite exactamente *m* veces**.  Por ejemplo, `ab{2}c` solo permite hacer *match* con `abbc`.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Aserciones\n",
    "\n",
    "Las aserciones incluyen límites, que indican el comienzo y el final de líneas y palabras.\n",
    "\n",
    "- `^` \n",
    "    \n",
    "    Permite especificar la expresión de **inicio** del *string*.\n",
    "\n",
    "\n",
    "- `$` \n",
    "    \n",
    "    Permite especificar la expresión de **término** del *string*."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Grupos y operación binaría\n",
    "\n",
    "\n",
    "- *A* `|` *B* \n",
    "\n",
    "    Es un **operador binario** que permite especificar que se puede hacer *match* con la expresión regular *A*, o con la expresión regular *B*. Por ejemplo `ab+c|de*f` permite hacer _match_ con `abc`, `abbc`, `abbbc`, ..., `df`, `def`, `deef`, `deeef`, etc.\n",
    "\n",
    "- **`( )`** \n",
    "\n",
    "    Permitir delimitar una expresión regular y **definir _grupos_** dentro de ella. Por ejemplo, `a(bc)*(de)f` permite hacer *match* con `adef`, `abcdef`, `abcbcdef`, `abcbcbcdef`, etc.\n",
    "\n",
    "    Estos grupos son mejor conocidos como _grupos de captura_, ya que una vez que se produce una coincidencia dentro de la expresión regular, estos grupos son recordados y podemos acceder.\n",
    "\n",
    "- **`(?P<Nombre> )`** \n",
    "\n",
    "    Es un **_grupo de captura con nombre_**, el cual nos permite asignar una \"nombre\" a las coincidencias y acceder a ellos por medio de este nombre. Los corchetes angulares (`<` y `>`) son necesarios para el nombre del grupo. Por ejemplo, para extraer el código de área de un número de teléfono, podrías usar `\\+(?P<area>\\d{2})\\d{9}`. Más adelante veremos un ejemplo de cómo extraer el _grupo de captura con nombre_ de un match.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Módulo `re`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Python provee el módulo `re` para el uso de expresiones regulares. Dentro de las funciones disponibles en el módulo `re` de Python se encuentran:\n",
    "\n",
    "- [`re.match()`](https://docs.python.org/es/3/library/re.html#re.match) verifica si un *substring* cumple con la expresión regular a partir del inicio del *string*.\n",
    "- [`re.fullmatch()`](https://docs.python.org/es/3/library/re.html#re.fullmatch) verifica si el *string* completo cumple con la expresión regular\n",
    "- [`re.search()`](https://docs.python.org/es/3/library/re.html#re.search) verifica si algún *substring* cumple con la expresión regular.\n",
    "- [`re.sub()`](https://docs.python.org/es/3/library/re.html#re.sub) permite reemplazar un patrón por otra secuencia de caracteres en un *string*.\n",
    "- [`re.split()`](https://docs.python.org/es/3/library/re.html#re.split) permite separar un *string* de acuerdo a un patrón."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *Matching*\n",
    "\n",
    "Las expresiones regulares son fuertemente utilizadas en operaciones de comparación o *matching*. A continuación revisaremos algunos ejemplos y sus resultados."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Para cargar el módulo re\n",
    "import re"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Definimos un conjunto de secuencias que necesitamos verificar si cumplen con \n",
    "# un patrón.\n",
    "seq = [\n",
    "    \"4tt\", \"4ttGb\", \"4ttabcabc32\", \"4tssssghj3\",\n",
    "    \"44ttkbcdag60\", \"4ttabcfgh41\", \"3ttabc4ttyb\"\n",
    "]\n",
    "# El patrón que ocuparemos es '4tt.b'\n",
    "# (notar el uso de '.', es decir, cualquier carácter en esa posición)\n",
    "\n",
    "print(\"----------- Búsqueda con re.match() -----------\")\n",
    "# Verifica si algún substring cumple con la expresión regular\n",
    "# desde el inicio del string\n",
    "for s in seq:\n",
    "    print(\"{:15s} --> \".format(s), re.match('4tt.b', s))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"----------- Búsqueda con re.search() ----------\")\n",
    "# Verifica si algún substring cumple con la expresión regular\n",
    "# desde cualquier posición\n",
    "for s in seq:\n",
    "    print(\"{:15s} --> \".format(s), re.search('4tt.b', s))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"----------- Búsqueda con re.fullmatch() ----------\")\n",
    "# Verifica si el string completo cumple con la expresión regular\n",
    "# (de principio a fin)\n",
    "for s in seq:\n",
    "    print(\"{:15s} --> \".format(s), re.fullmatch('4tt.b', s))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El patrón que estamos utilizando es `4tt.b`. Al buscar con `match` la búsqueda se efectúa desde el inicio del *string*, y al buscar con `search` la búsqueda se efectúa en cualquier parte del *string*. Al buscar con `fullmatch` se verifica si todo el *string* cumple con la expresión regular.\n",
    "\n",
    "La búsqueda con `match` es equivalente a haber especificado `re.search('^4tt.b', s)`, ya que el meta-carácter `^` indica que el patron debe encontrarse al inicio del *string*.\n",
    "\n",
    "Tanto `match` como `search` y `fullmatch` retornan **un** objeto de tipo `Match`, donde algunos de sus atributos y métodos son:\n",
    "\n",
    "- `span`: tupla que indica el inicio y término del patrón encontrado en el *string*. Notar que en el caso de `match`, éste siempre empieza en la posición 0.\n",
    "\n",
    "- `group(identificador)`: cuando `identificador` se omite o es `0`, retornar el *substring* que hizo *match* con el patrón, y que aparece en el campo `match`. \n",
    "\n",
    "    Cuando `identificador` corresponde a un número y es mayor a `0`, este se comporta como un índice posicional, retorna el *substring* que hizo *match* con el _grupo de captura_ correspondiente al índice. Recordar que los _grupos de captura_ son aquellos que se definen usando `()`.\n",
    "\n",
    "    Cuando `identificador` corresponde a un texto, este se comporta como la llave de un diccionario, retorna el *substring* que hizo *match* con el _grupos de captura con nombre_ asociado a ese identificador. Recordar que los _grupos de captura con nombre_ son aquellos que se definen usando la expresión `(?P<identificador> )`. \n",
    "\n",
    "- `groupdict()`: retorna un diccionario con todos los _grupos de captura con nombre_ y sus respectivos valores.\n",
    "\n",
    "\n",
    "El resultado de `match()` puede ser utilizado directamente como condición de sentencias `if`, `while`, etc. En el siguiente ejemplo verificaremos si las secuencias en `seq` cumplen con el patrón de tener la sub-secuencia caracteres `4tt.b` al comienzo de la secuencia. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq = [\n",
    "    \"4tt\", \"4ttGb\", \"4ttabcabc32\", \"4tssssghj3\",\n",
    "    \"44ttkbcdag60\", \"4ttabcfgh41\", \"3ttabc4ttyb\"\n",
    "]\n",
    "for patron in seq:    \n",
    "    if re.match('^4tt', patron):\n",
    "        print(\"{:15s} cumple con el patrón\".format(patron))\n",
    "    else:\n",
    "        print(\"{:15s} NO cumple con el patrón\".format(patron))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora modificaremos el patrón para que permita detectar repeticiones de un grupo de caracteres. Para esto incorporaremos al patrón utilizado un grupo de caracteres como `(abc)`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq = [\n",
    "    \"4tt\", \"4ttGb\", \"4ttabcabc32\", \"4tssssghj3\",\n",
    "    \"44ttkbcdag60\", \"4ttabcfgh41\", \"3ttabc4ttyb\"\n",
    "]\n",
    "\n",
    "for patron in seq:    \n",
    "    if re.match('^4tt(abc)', patron):\n",
    "        print(\"{:15s} cumple con el patrón\".format(patron))\n",
    "    else:\n",
    "        print(\"{:15s} NO cumple con el patrón\".format(patron))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for patron in seq:    \n",
    "    if re.match('^4tt(abc){2}', patron):\n",
    "        # Indicaremos con los '{ }' el número de veces que el grupo debe estar\n",
    "        # presente, ya que como vimos en el ejemplo anterior, por defecto se asume\n",
    "        # que puede estar 1 o más veces.\n",
    "        print(\"{:15s} cumple con el patrón\".format(patron))\n",
    "    else:\n",
    "        print(\"{:15s} NO cumple con el patrón\".format(patron))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Verificación de *e-mail*\n",
    "\n",
    "Veamos ahora como podemos usar esto para asegurarnos de que una dirección de correo electrónico cumpla con un determinado formato. Las direcciones que admitiremos pueden tener cualquier tipo de carácter antes de la `@` y pertenecer a los dominios `mail.cl` o `mimail.cl`, y también a cualquiera de las direcciones que incluyan a este dominio, específicamente `seccion1.mimail.cl`, `seccion2.mimail.cl`. Para construir el patrón que nos permitirá verificar las direcciones, debemos hacerlo de la siguiente forma:\n",
    "\n",
    "1. Al comienzo debemos incluir los meta-caracteres `[a-zA-Z0-9_.]+`. Con esto estamos indicando que la cadena que vamos a ingresar contendrá 1 o más (`+`) caracteres entre letras mayúsculas y minúsculas, números o bien los caracteres `_` o `.`, especificados en la clase mediante \"`[ ]`\";\n",
    "2. Luego irá el símbolo `@`;\n",
    "3. A continuación debemos indicar que podría o no existir (`?`) los subdominios `((seccion1|seccion2)\\.)?`, seguidos de un `.`. En este caso debemos usar el meta-carácter `\\` para asegurarnos que el meta-carácter `.` sea considerado como punto.\n",
    "4. Finalmente, debemos verificar que esté presente el dominio del correo incluyendo en el patrón `(mi)?mail\\.cl`, indicando que la secuencia `mi` puede estar o no (`?`) en el dominio. Otra forma de escribir la misma regla es creando el grupo `(mimail|mail)\\.cl`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def es_mail_valido(email):\n",
    "    # Recordar que el método 'match()' retorna un objeto de tipo 'Match' que\n",
    "    # al ser usado en sentencias como IF y WHILE representa un valor lógico.\n",
    "    # Podemos hacer que una función retorne un valor lógico de la operación\n",
    "    # de match haciendo la conversión a bool.\n",
    "    #\n",
    "    # Otra forma de escribir el patrón es:\n",
    "    # patron = \"[a-zA-Z0-9_.]+@(seccion(1|2)\\.)?(mi)?mail\\.cl\"\n",
    "    patron = \"[a-zA-Z0-9_.]+@((seccion1|seccion2)\\.)?(mimail|mail)\\.cl\"\n",
    "    return bool(re.fullmatch(patron, email))\n",
    "\n",
    "\n",
    "# Las direcciones de correo tienen consistencia con el patrón utilizado\n",
    "print(es_mail_valido('nombre.apellido@mail.cl'))\n",
    "print(es_mail_valido('nombre_aprellido@mimail.cl'))\n",
    "print(es_mail_valido('nombre1010@seccion1.mimail.cl'))\n",
    "print(es_mail_valido('nombre1010@seccion2.mail.cl'))\n",
    "\n",
    "# Estos los correos incluyen elementos no considerados en el patrón\n",
    "print(es_mail_valido('nombre1010@tumail.cl'))\n",
    "print(es_mail_valido('nombre101-@tumail.cl'))\n",
    "print(es_mail_valido('nombre101@maillcl'))\n",
    "print(es_mail_valido('nombre101@mail.cll'))\n",
    "print(es_mail_valido('nombre101@seccion3.tumail.cl'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Siguiendo con el ejemplo anterior, ahora utilicemos los _grupos de captura con nombre_ para extraer las partes de los correos electrónicos:\n",
    "\n",
    "![](imgs/partes_correo_electronico.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def extraer_info_mail(email) -> dict:\n",
    "    # Recordar que los grupos de captura con nombre se definen\n",
    "    # utilizando la estructura \"(?P<nombre> )\"\n",
    "    # \n",
    "    # Tomaremos el patrón de la función \"es_mail_valido\" y le agregaremos grupos\n",
    "    patron = \"(?P<username>[a-zA-Z0-9_.]+)@((?P<subdominio>seccion1|seccion2)\\.)?(?P<dominio>mimail|mail)\\.cl\"\n",
    "    match = re.fullmatch(patron, email)\n",
    "    return (match.groupdict())\n",
    "\n",
    "print(extraer_info_mail('nombre.apellido@mail.cl'))\n",
    "print(extraer_info_mail('nombre_aprellido@mimail.cl'))\n",
    "print(extraer_info_mail('nombre1010@seccion1.mimail.cl'))\n",
    "print(extraer_info_mail('nombre1010@seccion2.mail.cl'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nótese que como el subdominio es un campo opcional en los correos, este puede tener un texto asociado o no (ser `None`)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Verificación de RUT\n",
    "\n",
    "Veamos otro ejemplo que consiste en verificar que el RUT ingresado en un campo de un formulario tenga el formato especificado como: ##.###.###-#, que es una secuencia de números separados por puntos y el dígito verificador separado por guion.\n",
    "\n",
    "Para este ejemplo vamos a considerar que el RUT más pequeño es `1.000.000-0`, y que el dígito verificador puede ser un dígito desde `0` hasta `9`, o la letra \"k\" minúscula o mayúscula. No verificaremos la validez del dígito verificador. La estructura del patrón entonces quedaría definida de la siguiente forma:\n",
    "\n",
    "1. Al comienzo, incluiremos la regla que indica que puede existir entre uno o dos caracteres numéricos seguidos de un \"`.`\". Esto lo indicamos como  `[0-9]{1,2}\\.`. Dentro de \"`[]`\" estamos incluyendo la clase de caracteres numéricos y en \"`{1,2}`\" estamos indicando que habrá entre uno y dos caracteres en esta sección antes del punto;\n",
    "2. Después, incluimos los siguientes tres caracteres numéricos, seguidos por un punto. Lo indicamos como `[0-9]{3}\\.`;\n",
    "3. Luego, indicamos que debe haber obligatoriamente 3 dígitos seguidos de un guion `[0-9]{3}-`;\n",
    "4. Finalmente, consideramos que haya un dígito entre 0 y 9, o bien la letra k mayúscula o minúscula: `([0-9kK])`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def es_rut_valido(rut):\n",
    "    # Uno o dos dígitos, un punto, 3 dígitos, un punto, 3 dígitos,\n",
    "    # y un dígito, 'k' o 'K'\n",
    "    patron = \"[0-9]{1,2}\\.[0-9]{3}\\.[0-9]{3}-([0-9kK])\"\n",
    "    return bool(re.fullmatch(patron, rut))\n",
    "\n",
    "\n",
    "# Casos válidos para el patrón\n",
    "casos = ['12.224.877-2', '12.745.331-k', '19.235.312-K', '1.113.221-2']\n",
    "for c in casos:\n",
    "    print(c, \"\\t\", es_rut_valido(c))\n",
    "\n",
    "# Casos no válidos para el patrón\n",
    "casos = ['13.427.974-a', '13.427.974-a', '113.427.974-1',\n",
    "         'ab.111.444-0', '13.20.830-6', '113.427.974-12']\n",
    "for c in casos:\n",
    "    print(c, \"\\t\", es_rut_valido(c))"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Existen abreviaciones pre-establecidas para ciertas clases de caracteres. Por ejemplo:\n",
    "\n",
    "- `\\d`: equivale a `[0-9]`;\n",
    "- `\\D`: es equivalente a `[^0-9]`, donde se compara con cualquier carácter que no sea dígito; \n",
    "- `\\s`: equivale a hacer `[\\t\\n\\r\\f\\v]`, compara cualquier tipo de espacio en blanco;\n",
    "- `\\S`: equivale a escribir la clase `[^\\t\\n\\r\\f\\v]`, que compara con cualquier carácter distinto a los espacios en blanco;\n",
    "- `\\w`: es equivalente a la clase `[a-zA-Z0-9\\_]`, donde se compara con cualquier carácter alfa numérico;\n",
    "- `\\W`: equivale a `[^a-zA-Z0-9\\_]`, que contempla que no haya ningún carácter alfa numérico.\n",
    "\n",
    "De esta forma podríamos escribir el patrón para comprobar el RUT de la siguiente forma equivalente:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def es_rut_valido(rut):\n",
    "    # Uno o dos dígitos, un punto, 3 dígitos, un punto, 3 dígitos,\n",
    "    # y un dígito, 'k' o 'K'\n",
    "    patron = \"\\d{1,2}\\.\\d{3}\\.\\d{3}-(\\d|k|K)\"\n",
    "    return bool(re.fullmatch(patron, rut))\n",
    "\n",
    "\n",
    "# Casos válidos para el patrón\n",
    "casos = ['12.224.877-2', '12.745.331-k', '19.235.312-K', '1.113.221-2']\n",
    "for c in casos:\n",
    "    print(c, \"\\t\", es_rut_valido(c))\n",
    "\n",
    "# Casos no válidos para el patrón\n",
    "casos = ['13.427.974-a', '13.427.974-a', '113.427.974-1',\n",
    "         'ab.111.444-0', '13.20.830-6', '113.427.974-12']\n",
    "for c in casos:\n",
    "    print(c, \"\\t\", es_rut_valido(c))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora, ¿qué ocurriría si es que en el futuro el rut llega a los mil millones? ¿O si llega una persona con un rut menor a 1.000.000? Nuestra regex no es tan robusta, pero podemos extenderla para que actúe sobre cualquier rut. Para esto, podemos aprovecharnos de lo mencionado anteriormente: en regex es posible definir grupos. Luego, podemos separar nuestro regex en 3 partes distintas:\n",
    "- El primer conjunto de números, puede tener entre 1 y 3 dígitos.\n",
    "- Si hay más dígitos después del primer conjunto, entonces todos deben estar antecedidos por un \".\" y deben haber 3 dígitos.\n",
    "- Luego debe haber un \"-\" seguido de un dígito entre 0 y 9, o una \"k\" mayúscula o minúscula.\n",
    "Para generalizar nuestro regex, podemos definir el segundo punto como un grupo, y hacer que puedan haber entre 0 y n grupos de esos. Teniendo esto en cuenta, nuestro regex quedaría como se muestra a continuación:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def es_rut_valido(rut):\n",
    "    # Uno o dos dígitos, un punto, 3 dígitos, un punto, 3 dígitos,\n",
    "    # y un dígito, 'k' o 'K'\n",
    "    patron = \"\\d{1,3}(\\.\\d{3})*-(\\d|k|K)\"\n",
    "    return bool(re.fullmatch(patron, rut))\n",
    "\n",
    "\n",
    "# Casos válidos para el patrón\n",
    "casos = ['12.224.877-2', '12.745.331-k', '19.235.312-K',\n",
    "         '1.113.221-2', '113.427.974-1', '1.113.427.974.653.339.143.452.173-1',\n",
    "         '3-1', '344-k'\n",
    "        ]\n",
    "print('Casos válidos')\n",
    "for c in casos:\n",
    "    print(\"{:35s}\".format(c), \"\\t\", es_rut_valido(c))\n",
    "\n",
    "\n",
    "# Casos no válidos para el patrón\n",
    "casos = ['13.427.974-a', '13.427.974-a', 'ab.111.444-0', \n",
    "         '13.20.830-6', '113.427.974-12']\n",
    "print('\\nCasos no válidos')\n",
    "for c in casos:\n",
    "    print(\"{:35s}\".format(c), \"\\t\", es_rut_valido(c))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Búsqueda\n",
    "\n",
    "La búsqueda es otra de las tareas donde comúnmente se utilizan expresiones regulares. En este caso el módulo `re` permite tres formas de búsqueda:\n",
    "\n",
    "- `search()`: busca en una secuencia cualquier posición donde el patrón coincida, y retorna la primera coincidencia.\n",
    "- `findall()`: Encuentra todas las sub-secuencias donde el patrón coincida, y las retorna como una lista.\n",
    "- `finditer()`: opera como `findall()`, pero retorna un iterador.\n",
    "\n",
    "Volvamos al ejemplo de las secuencias usadas anteriormente y busquemos en ellas donde aparece la sub-secuencia `\"ab\"`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq = [\"4tt\", \"4ttGb\", \"4ttabcabc32\", \"3ssafjabc3\", \"4tssssghj3\",\n",
    "       \"44ttkbcdag60\", \"4ttabcfgh41\", \"3ttabc4ttyb\", \"3tt4ttSbc4ttyb\"]\n",
    "\n",
    "for s in seq:\n",
    "    first_match = re.search('(ab)', s)\n",
    "    print('secuencia {:15s}: {}'.format(s, first_match))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Al igual que el método `match()`, el método `search()` retorna un objeto indicando la posición de la coincidencia. Si no encuentra alguna coincidencia retorna `None`.\n",
    "\n",
    "Veamos un ejemplo donde podamos recuperar un listado con todos las sub-secuencias de valores numéricos que estén en las secuencias:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq = [\"4tt\", \"4ttGb\", \"4ttabcabc32\", \"3ssafjabc3\", \"4tssssghj3\",\n",
    "       \"44ttkbcdag60\", \"4ttabcfgh41\", \"3ttabc4ttyb\", \"3tt4ttSbc4ttyb\"]\n",
    "\n",
    "for s in seq:\n",
    "    all_matches = re.findall('\\d+', s)\n",
    "    print('secuencia {:15s}: {}'.format(s, all_matches))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Sustitución\n",
    "\n",
    "La modificación de secuencias es también otra de las tareas en que las expresiones regulares son de gran ayuda. El módulo `re` provee el método `sub(<patron>, <reemplazar por>, secuencia)` que nos permite hacer sustitución de acuerdo al patrón indicado. Por ejemplo, eliminaremos todos los números en las secuencias usadas en los ejemplos anteriores:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "for s in seq:\n",
    "    # sub retorna un nuevo valor, por lo tanto,\n",
    "    # no modifica la secuencia original\n",
    "    result = re.sub('\\d+', '', s)\n",
    "    print('secuencia {:15s} queda como {}'.format(s, result))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En vez de usar un valor a sustituir, podemos también incluir una función donde podamos utilizar una regla de sustitución más compleja. Por ejemplo, procesemos una secuencia de ADN con bases \"A\", \"T\" y \"C\" reemplazándolas por sus bases complementarias:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def bases(base):\n",
    "    # A la función entra un objeto tipo Match. Debemos recuperar el \n",
    "    # valor de la coincidencia haciendo group(0) ya que sabemos que \n",
    "    # con el patrón utilizado nos llegará solo una coincidencia.\n",
    "    mapping = {'A': 'T', 'G': 'C', 'T': 'A', 'C': 'G'}\n",
    "    return mapping[base.group(0)]\n",
    "\n",
    "\n",
    "adn = 'ACAAGATGCCATTGTCCCCCGGCCTCCTGCTGCTGCTGCTCTCCGGGGCCACGGCCACCG'\n",
    "print(adn + \"\\n\")\n",
    "print(re.sub('[ATCG]', bases, adn))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `split`"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para separar una secuencia por el carácter `e` podemos usar el método `split()` de los objetos `str`. Esto quedaría como siguiente ejemplo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "msg = \"Este es un mensaje simple que vamos a procesar ehh!\"\n",
    "print(msg.split('e'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Podemos realizar el mismo procesamiento usado el módulo `re` de la siguiente forma:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "msg = \"Este es un mensaje simple que vamos a procesar ehh!\"\n",
    "re.split('e', msg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El método `split()`require como argumentos un _string_ con el patrón y el string donde vamos aplicar ese patrón. Hasta ahora ambos métodos nos entregan el mismo resultado. Para casos simples de procesamiento solo bastaría el uso de los métodos propios de la clase `str()` y dejar `re` para tareas más complejas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Dividimos sacando solo las vocales. Los patrones regules son case sensitive,\n",
    "# es decir, distinguen entre mayúsculas y minúsculas.\n",
    "print(re.split('[aeiou]', msg))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Volvamos al ejemplo de la secuencia de ADN. Supongamos que necesitamos dividir una secuencia dada usando los tripletas de genes (*codones*). La implementación mediante expresiones regulares para separar la siguiente secuencia usando las tripleras *GGG* y *GGA* sería:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "adn = \"AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGCTTCTGA\" + \\\n",
    "\"ACTGGTTACCTGCCGTGAGTAAATTAAAATTTTATTGACTTAGGTCACTAAATACTTTAACCAATATAGGCATAGCGCACAG\" + \\\n",
    "\"ACAGATAAAAATTACAGAGTACACAACATCCATGAAACGCATTAGCACCACCATTACCACCACCATCACCATTACCACAGGT\" + \\\n",
    "\"AACGGTGCGGGCTGACGCGTACAGGAAACACAGAAAAAAG\"\n",
    "\n",
    "re.split('(?:GGG|GGA)', adn)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En este caso hemos incorporado los meta-caracteres `?:` para indicar que vamos a buscar cualquiera de los grupos de  expresiones dentro de los paréntesis, pero que las sub-secuencias que coincidan no serán retornadas después realizar la búsqueda. Esto permite que `split` solo retorne las secuencias producto de la división. Esto se conoce como *non-capturing version*."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los conceptos vistos en este material corresponden a los usos básicos que pueden dar a las expresiones regulares. Les recomendamos revisar la documentación de Python para [expresiones regulares](https://docs.python.org/3/library/re.html) y [HOWTO](https://docs.python.org/3/howto/regex.html#regex-howto), para tener mayor detalle de cada método en el módulo `re` y conocer otros casos de uso. También pueden verificar las expresiones regular que escriban usando sitios como [regex101.com](https://regex101.com) o [pythex.org](http://pythex.org). "
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.11"
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
