{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<p>\n",
    "<font size='5' face='Georgia, Arial'>IIC-2233 Apuntes Programación Avanzada</font><br>\n",
    "<font size='1'> Actualizados al 2026-1.</font>\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tabla de contenidos\n",
    "\n",
    "1. [Contenidos mínimos IIC1103](#contenidos-mínimos-iic1103)\n",
    "2. [Tipos de datos](#Tipos-de-datos)\n",
    "3. [Funciones](#Funciones)\n",
    "4. [Archivos](#Archivos)\n",
    "5. [Objetos](#Objetos)\n",
    "6. [Profundización](#Profundización)\n",
    "    1. [Más sobre Listas](#Más-sobre-listas)\n",
    "        1. [Listas por comprensión](#Listas-por-comprensión)\n",
    "        2. [*Slicing* de listas](#Slicing-de-listas)\n",
    "    2. [Más sobre *Strings*](#Más-sobre-Strings)\n",
    "        1. [Secuencia de escape](#Secuencia-de-escape)\n",
    "        2. [Métodos disponibles en *Strings*](#Métodos-disponibles-en-Strings)\n",
    "        3. [Uso de variables dentro de un *String*](#Uso-de-variables-dentro-de-un-String)\n",
    "        4. [Bonus: Mejorando la impresión de los *Strings*](#Bonus:-Mejorando-la-impresión-de-los-Strings)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Contenidos mínimos IIC1103\n",
    "\n",
    "El curso IIC1103 - Introducción a la programación es requisito para este curso. Es por eso, que para este curso se espera y asume tener conocimiento sobre los siguientes tópicos:\n",
    "\n",
    "1. Resolución de problemas con algoritmos.\n",
    "2. Tipos de datos, variables, operadores y expresiones.\n",
    "3. Control de flujo condicional.\n",
    "4. Control de flujo cíclico.\n",
    "5. Funciones importadas, propias y recursivas.\n",
    "6. Manejo de strings.\n",
    "7. Listas simples y listas de listas.\n",
    "8. Abrir y crear archivos.\n",
    "9. Programación orientada a objetos. \n",
    "10. Ordenamiento.\n",
    "\n",
    "Aquí te dejaremos un resumen de los contenidos mínimos más importantes a saber en el lenguaje Python."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tipos de datos\n",
    "### *Integer y float*\n",
    "Representan números enteros (`int`) y números decimales (`float`), y nos permiten trabajar con operadores aritméticos.\n",
    "\n",
    "Ejemplo: \n",
    "```python\n",
    "x = 10\n",
    "y  = 20\n",
    "z = x + y # 30\n",
    "```\n",
    "\n",
    "### *String*\n",
    "Es un objeto que representa cadenas de caracteres y nos permite trabajar con texto.\n",
    "\n",
    "Ejemplo: \n",
    "```python\n",
    "s = 'Bienvenidos a IIC2233'\n",
    "```\n",
    "\n",
    "### *Boolean*\n",
    "Representan el valor de verdad de una expresión. Por ejemplo, la expresión resultante de una operación de comparación 1 >= 0 es Verdadera o `True`, mientras que la expresión 10 == 5 es Falsa o `False`.\n",
    "\n",
    "Ejemplo: \n",
    "```python\n",
    "a = True\n",
    "b = 3 > 4\n",
    "print(a == b) # False\n",
    "```\n",
    "\n",
    "### Listas\n",
    "Colección finita de elementos ordenados, los cuales usualmente son otros tipos de datos como los vistos anteriormente. Las listas son mutables y su contenido puede ser accedido utilizando el índice correspondiente al orden en que se encuentra en la lista.\n",
    "\n",
    "\n",
    "Ejemplo: \n",
    "```python\n",
    "lista = [10, 'variable', True, 5.5]\n",
    "print(lista[0]) # 10\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Funciones\n",
    "\n",
    "Las funciones permiten encapsular código que resuelve una tarea específica. Existen funciones que vienen con python y se denominan *built-in functions*. Pueden ver en la [documentación oficial](https://docs.python.org/3.12/library/functions.html) una lista con todas ellas. Además de las funciones que están siempre presentes, podemos incorporar nuevas funcionalidades mediante la importación de **módulos**. Los módulos proveen diversas funciones agrupadas.\n",
    "\n",
    "#### Importar módulo completo\n",
    "\n",
    "```python\n",
    "import modulo\n",
    "\n",
    "modulo.funcion_1(parametro_1, parametro_2, ..., parametro_n)\n",
    "\n",
    "modulo.funcion_2(parametro_1, parametro_2, ..., parametro_n)\n",
    "```\n",
    "\n",
    "\n",
    "#### Importar función(es) específica(s)\n",
    "\n",
    "```python\n",
    "from modulo import funcion_1, funcion_2, ..., funcion_n\n",
    "\n",
    "funcion_1(parametro_1, parametros_2, ..., parametro_n)\n",
    "```\n",
    "\n",
    "#### *Aliasing*\n",
    "\n",
    "Es posible darle el nombre que nosotros queramos al módulo o a sus funciones. Esto se denomina *aliasing*.\n",
    "\n",
    "```python\n",
    "import modulo as alias\n",
    "\n",
    "alias.funcion_1(parametro_1, parametro_2, ..., parametro_n)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Funciones definidas por el programador\n",
    "Aparte de las *built-in functions* y las funciones importadas desde módulos, podemos definir nuestras propias funciones para utilizarlas en nuestros programas. Para crear una nueva función utilizamos la _keyword_ `def`, entregamos parámetros y podemos opcionalmente utilizar la _keyword_ `return` para retornar un valor.\n",
    "\n",
    "```\n",
    "def nombre_funcion(parametro_1, parametro_2, ..., parametro_n):\n",
    "    bloque_de_codigo_de_la_funcion\n",
    "    ...\n",
    "    bloque_de_codigo_de_la_funcion\n",
    "    return valor_de_retorno\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def sumar(numero1, numero2):\n",
    "    \"\"\"\n",
    "    Retorna la suma de dos números enteros.\n",
    "\n",
    "    Args:\n",
    "        numero1 (int): Primer número a sumar.\n",
    "        numero2 (int): Segundo número a sumar.\n",
    "\n",
    "    Returns:\n",
    "        int: Resultado de la suma de n1 y n2.\n",
    "    \"\"\"\n",
    "    # Calculamos la suma de los dos números recibidos\n",
    "    suma = numero1 + numero2\n",
    "\n",
    "    # Retornamos el resultado de la suma\n",
    "    return suma"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para utilizar la función debemos llamarla, y de retornar un valor debemos almacenar este en una variable.\n",
    "```python\n",
    "salida = nombre_funcion(entrada_1, entrada_2, ..., entrada_n)\n",
    "```\n",
    "Las funciones pueden recibir 0 o más parámetros y retornar 0 o más valores. Lo habitual es retornar 1 valor. Cuando se alcanza el primer `return`, la función termina."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "30\n"
     ]
    }
   ],
   "source": [
    "# Ejemplo de uso\n",
    "resultado = sumar(10,20)\n",
    "print(resultado)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finalmente, los parámetros de una función pueden tener valores por defecto, es decir, en caso de no entregar dicho parámetro cuando se llame la función, se utilizará el valor por defecto.\n",
    "\n",
    "Para lograr esto, al momento de definir la función, se incluye el valor junto al parámetro de la siguiente forma: `parametro_1=VALOR`. Por ejemplo, haremos que la función `sumar` tenga por defecto los valores `0` para cada parámetro."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Valor n1:  0\n",
      "Valor n2:  0\n",
      "0\n"
     ]
    }
   ],
   "source": [
    "def sumar(n1=0, n2=0):\n",
    "    \"\"\"Retorna la suma de dos números.\"\"\"\n",
    "    print(\"Valor n1: \", n1)\n",
    "    print(\"Valor n2: \", n2)\n",
    "    return n1 + n2\n",
    "\n",
    "resultado = sumar()\n",
    "print(resultado)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Valor n1:  10\n",
      "Valor n2:  0\n",
      "10\n"
     ]
    }
   ],
   "source": [
    "print(sumar(10)) # Daremos solo el primer parámetro, el segundo utilizará el por defecto"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Valor n1:  10\n",
      "Valor n2:  13\n",
      "23\n"
     ]
    }
   ],
   "source": [
    "print(sumar(10, 13)) # Daremos ambos parámetros para no utilizar los por defecto"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Archivos\n",
    "\n",
    "Un archivo es un documento binario con un nombre y una extensión, por ejemplo `tarea1.py`. Debemos partir abriendo y cerrando el archivo.\n",
    "* `open(path, modo)`: Retorna un objeto que representa al archivo en `path`. El parámetro `modo` indica lo que podemos hacer con el archivo.\n",
    "    - `modo='r'`: lectura (por defecto)\n",
    "    - `modo='w'`: escritura\n",
    "    - `modo='a'`: *append*\n",
    "* `file.close()`: Cierra el archivo.\n",
    "\n",
    "La diferencia entre los modos escritura y *append*, es que:\n",
    "* En el modo escritura, se crea un nuevo archivo en `path`. Si el archivo ya existe, borra su contenido. \n",
    "* En el modo *append*, si el archivo ya existe, el contenido nuevo se agrega a continuación del ya existente.\n",
    "\n",
    "Para leer archivos, Python provee de distintas funciones y métodos. Para las siguientes instrucciones, se asume que se guardó lo retornado por `open(path, modo)` en una variable de nombre `file`.\n",
    "\n",
    "* `file.readline()`: Retorna la siguiente línea del archivo.\n",
    "* `file.readlines()`: Retorna en una variable del tipo lista todo el contenido del archivo.\n",
    "\n",
    "Para crear archivos, usaremos:\n",
    "\n",
    "* `file.write(s)`: Escribe el string `s` en el archivo (**no** agrega el caracter de fin de liínea `'\\n'`). \n",
    "* `print(s, file=file)`: Escribe el string `s` en el archivo (**sí** agrega el caracter de fin de línea `'\\n'`)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Objetos\n",
    "\n",
    "La Programación Orientada a Objetos es un paradigma de programación que consiste en modelar un problema preocupándose de los **objetos** que en él participan: sus atributos, comportamiento e interacción. En el área de desarrollo de software, un **objeto** es una colección de **datos** que además tiene **comportamientos**\n",
    "asociados. Por una parte, los datos **describen** a los objetos, mientras que los comportamientos **representan acciones** que ocurren en ellos. \n",
    "\n",
    "La **Programación orientada a objetos** u **OOP** (*Object-oriented Programming*) es un *paradigma de programación* (una manera de programar) en el cual los programas modelan las funcionalidades a través de la interacción entre **objetos** por medio de sus datos y sus comportamientos. \n",
    "\n",
    "En OOP los objetos son descritos de manera general mediante **clases**. Una clase describe los datos que caracterizan a un objeto; a estos datos los llamamos **atributos**. Una clase también describe los comportamientos de los objetos, y a estos comportamientos los llamamos **métodos**. Cada vez que creamos un objeto a partir de una clase, decimos que estamos _instanciando_ esa clase, por lo tanto **un objeto es una instancia de una clase**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Persona:\n",
    "    \"\"\"\n",
    "    Clase que representa a una persona con nombre, apellido y edad.\n",
    "\n",
    "    Atributos:\n",
    "        nombre (str): Nombre de la persona.\n",
    "        apellido (str): Apellido de la persona.\n",
    "        edad (int): Edad de la persona. Por defecto 25.\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, nombre, apellido, edad=25):\n",
    "        \"\"\"\n",
    "        Inicializa una nueva instancia de Persona.\n",
    "\n",
    "        Args:\n",
    "            nombre (str): Nombre de la persona.\n",
    "            apellido (str): Apellido de la persona.\n",
    "            edad (int, optional): Edad de la persona. Por defecto 25.\n",
    "        \"\"\"\n",
    "        self.nombre = nombre\n",
    "        self.apellido = apellido\n",
    "        self.edad = edad  # Edad por defecto si no se pasa como argumento\n",
    "\n",
    "    def __str__(self):\n",
    "        \"\"\"\n",
    "        Devuelve una representación en string de la persona.\n",
    "\n",
    "        Returns:\n",
    "            str: Mensaje con el nombre completo de la persona.\n",
    "        \"\"\"\n",
    "        return f'Mi nombre es {self.nombre} {self.apellido}'\n",
    "\n",
    "    def saludar(self, otra_persona):\n",
    "        \"\"\"\n",
    "        Saluda a otra persona mostrando su nombre.\n",
    "\n",
    "        Args:\n",
    "            otra_persona (Persona): Instancia de Persona a quien se saluda.\n",
    "        \"\"\"\n",
    "        print(f'Hola {otra_persona.nombre}, soy {self.nombre} y te saludo.')\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Mi nombre es Renato Leal\n",
      "Hola Renato, soy Francesca y te saludo.\n"
     ]
    }
   ],
   "source": [
    "persona1 = Persona(\"Francesca\", \"Aguirre\")\n",
    "persona2 = Persona(\"Renato\", \"Leal\")\n",
    "print(persona2)\n",
    "persona1.saludar(persona2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para definir una clase en Python, usamos la _keyword_ `class`. Dentro de la definición de la clase, definimos todos los atributos dentro de un método especial: el método  `__init__()`. Este método se llama durante la **inicialización** de la clase, cada vez que ejecutamos:\n",
    "\n",
    "```\n",
    "persona1 = Persona(\"Francesca\", \"Aguirre\")\n",
    "persona2 = Persona(\"Renato\", \"Leal\")\n",
    "```\n",
    "de esta manera todos los objetos que vayamos crear de la clase `Persona` poseerán estos atributos inicializados.\n",
    "\n",
    "Podemos crear **métodos de la instancia**, los cuales reciben como primer argumento el nombre **`self`**, el cual es una referencia a la instanca de la clase sobre la que están actuando.\n",
    "\n",
    "```\n",
    "persona1.saludar(persona2)\n",
    "```\n",
    "estamos invocando al método `saludar`, donde `self` es una referencia al instancia `persona1` sobre la cual se accede a su atributo `nombre` y al atributo `nombre` de otra instancia de la misma clase. El nombre `self` no es un *keyword*, por lo que puede ser cualquier nombre. Sin embargo, en la práctica usar `self` se ha convertido en una convención.\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "-----\n",
    "Si no te acuerdas de alguno de estos contenidos, te recomendamos preguntar en las [_discussions_](https://github.com/IIC2233/Syllabus/discussions) del GitHub del curso."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Profundización\n",
    "\n",
    "A continuación, se profundizará con más detalle en 2 contenidos que te serán de ayuda recurrentemente en el curso: _string_ y listas.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Más sobre listas\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "AfxQPd60QeY2"
   },
   "source": [
    "Las **listas** (`list`) se utilizan para manejar datos de forma **ordenada** y **mutable**. Los contenidos pueden ser accedidos utilizando el índice correspondiente al orden en que se encuentran en la lista. El *orden* de los elementos de una lista, y *los elementos mismos* pueden cambiar mediante métodos que manipulan la lista.\n",
    "\n",
    "Las listas también pueden ser heterogéneas, lo que significa que pueden contener objetos pertenecientes a clases o tipos de datos distintos, incluyendo otras listas. Si bien no existe ninguna restricción de Python sobre los tipos de datos de las listas, estas tienen una relación estrecha con los **arreglos** disponibles en otros lenguajes de programación, por lo que, dado que los arreglos suelen tener estas restricciones, es más común ver listas con tipos de datos homogéneos que heterogéneos.\n",
    "\n",
    "En una lista, los elementos que se agregan usando `append` se ponen al final de la lista.\n",
    "\n",
    "Podemos crear listas de las siguientes maneras:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "base_uri": "https://localhost:8080/",
     "height": 68,
     "output_extras": [
      {
       "item_id": 1
      }
     ]
    },
    "colab_type": "code",
    "executionInfo": {
     "elapsed": 655,
     "status": "ok",
     "timestamp": 1520882213515,
     "user": {
      "displayName": "Cristian Ruz",
      "photoUrl": "//lh4.googleusercontent.com/-0WhxkzATSqY/AAAAAAAAAAI/AAAAAAAAIqQ/WE7Rcv4pnN8/s50-c-k-no/photo.jpg",
      "userId": "108117710965209443485"
     },
     "user_tz": 180
    },
    "id": "UtPo_dwcQeY4",
    "outputId": "e90a7990-8fa4-4529-efea-67b0486b078e"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[2015, 2016]\n",
      "2\n",
      "[1, 'string', 20.5, [False, True], 'último elemento']\n",
      "string\n",
      "5\n"
     ]
    }
   ],
   "source": [
    "# Creamos una lista vacía y agregamos elementos incrementalmente.\n",
    "# En este caso agregamos dos tuplas al final de la lista.\n",
    "lista = list()                # También puede ser con lista=[]\n",
    "lista.append(2015)   # Aquí estamos agregando UN elemento, que es un entero\n",
    "lista.append(2016)   # Luego de esto, la lista contiene 2 elementos que son enteros\n",
    "print(lista)\n",
    "print(len(lista))\n",
    "\n",
    "# También es posible agregar los objetos explícitamente al definirla por primera vez\n",
    "lista = [1, 'string', 20.5, [False,True]]\n",
    "lista.append('último elemento')\n",
    "print(lista)\n",
    "\n",
    "# Extraemos un elemento usando el índice respectivo\n",
    "print(lista[1])\n",
    "print(len(lista))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "Yus8I5QhQeY7"
   },
   "source": [
    "A veces es necesario agregar nuevos elementos contenidos en otras listas. En estos casos resulta muy útil agregar la lista completa y no cada elemento de forma individual con `append()`. Para eso podemos utilizar el método `extend()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "base_uri": "https://localhost:8080/",
     "height": 51,
     "output_extras": [
      {
       "item_id": 1
      }
     ]
    },
    "colab_type": "code",
    "executionInfo": {
     "elapsed": 749,
     "status": "ok",
     "timestamp": 1520882441332,
     "user": {
      "displayName": "Cristian Ruz",
      "photoUrl": "//lh4.googleusercontent.com/-0WhxkzATSqY/AAAAAAAAAAI/AAAAAAAAIqQ/WE7Rcv4pnN8/s50-c-k-no/photo.jpg",
      "userId": "108117710965209443485"
     },
     "user_tz": 180
    },
    "id": "V9TLr6LIQeY9",
    "outputId": "0d152db9-3657-462a-a233-b090e2ef9f04"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Radiohead', 'City and Colour', 'toe']\n",
      "['Radiohead', 'City and Colour', 'toe', 'Young the Giant', 'Portugal. The Man', 'Twenty One Pilots']\n"
     ]
    }
   ],
   "source": [
    "bandas = ['Radiohead', 'City and Colour', 'toe']\n",
    "print(bandas)\n",
    "\n",
    "nuevas_bandas = ['Young the Giant', 'Portugal. The Man', 'Twenty One Pilots']\n",
    "bandas.extend(nuevas_bandas)\n",
    "print(bandas)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "uHQWM4IYQeY_"
   },
   "source": [
    "También es posible insertar elementos en posiciones específicas mediante el método `insert(posición, elemento)`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "base_uri": "https://localhost:8080/",
     "height": 51,
     "output_extras": [
      {
       "item_id": 1
      }
     ]
    },
    "colab_type": "code",
    "executionInfo": {
     "elapsed": 628,
     "status": "ok",
     "timestamp": 1520882442995,
     "user": {
      "displayName": "Cristian Ruz",
      "photoUrl": "//lh4.googleusercontent.com/-0WhxkzATSqY/AAAAAAAAAAI/AAAAAAAAIqQ/WE7Rcv4pnN8/s50-c-k-no/photo.jpg",
      "userId": "108117710965209443485"
     },
     "user_tz": 180
    },
    "id": "gU00_vIgQeZA",
    "outputId": "1dae43fe-a2fa-4af5-a7f3-ef69900de7bf"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Radiohead', 'City and Colour', 'toe', 'Young the Giant', 'Portugal. The Man', 'Twenty One Pilots']\n",
      "['Radiohead', 'City and Colour', 'Of Monsters and Men', 'toe', 'Young the Giant', 'Portugal. The Man', 'Twenty One Pilots']\n"
     ]
    }
   ],
   "source": [
    "print(bandas)\n",
    "bandas.insert(2, 'Of Monsters and Men')\n",
    "print(bandas)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Listas por comprensión"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Desde el punto de vista de la lógica, la definición de comprensión es:  \"Conjunto de caracteres que forman un concepto\". Así mismo, las listas por comprensión se pueden ver como listas formadas por un conjunto de objetos que cumplen con un concepto o condición en particular. En Python, podemos crear **listas por comprensión**. Esta es una forma más práctica para crear listas en pocas líneas de código.\n",
    "\n",
    "Por ejemplo, ya tenemos la lista `bandas`, pero ahora queremos construir una lista con el largo de cada uno de los elementos. Una forma de hacerlo sería la siguiente:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[9, 15, 19, 3, 15, 17, 17]\n"
     ]
    }
   ],
   "source": [
    "largo_de_bandas = []\n",
    "\n",
    "for nombre in bandas:\n",
    "    largo_de_bandas.append(len(nombre))\n",
    "\n",
    "print(largo_de_bandas)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Usando **listas por comprensión**, podemos definir lo mismo de forma más clara y concisa siguiendo la siguiente sintaxis:\n",
    "\n",
    "`nueva_lista = [expresión for elemento in lista]`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[9, 15, 19, 3, 15, 17, 17]\n"
     ]
    }
   ],
   "source": [
    "largo_de_bandas = [len(nombre) for nombre in bandas]\n",
    "\n",
    "print(largo_de_bandas)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La sentencia `if` se puede usar dentro de una lista por comprensión para construir la lista incluyendo solamente los elementos que cumplan una cierta condición. En el siguiente ejemplo guardaremos **los nombres de las bandas** que tengan un **largo menor a 10 caracteres**. La sintaxis al usar un `if` en **listas por comprensión** es la siguiente:\n",
    "\n",
    "`nueva_lista = [expresión for elemento in lista if condición]`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Radiohead', 'toe']\n"
     ]
    }
   ],
   "source": [
    "bandas_con_nombre_corto = [nombre for nombre in bandas if len(nombre) < 10]\n",
    "\n",
    "print(bandas_con_nombre_corto)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El código anterior es equivalente a hacer lo siguiente:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Radiohead', 'toe']\n"
     ]
    }
   ],
   "source": [
    "bandas_con_nombre_corto = []\n",
    "for nombre in bandas:\n",
    "    if len(nombre) < 10:\n",
    "        bandas_con_nombre_corto.append(nombre)\n",
    "\n",
    "print(bandas_con_nombre_corto)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### *Slicing* de listas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "PigJzLMgQeZF"
   },
   "source": [
    "Es posible tomar secciones de la lista usando la notación de ***slicing***. En esta notación, los índices indican *desde donde* y *hasta donde* deseamos recuperar datos de la lista. La sintaxis de la notación de *slicing* es:\n",
    "\n",
    "`secuencia[inicio:término:pasos]`\n",
    "\n",
    "Por defecto, el número de pasos es 1. La siguiente figura muestra un ejemplo de cómo se deben considerar los índices al usar la notación de *slicing*. \n",
    "\n",
    "![](data/indices_slicing.png)\n",
    "\n",
    "Forma general de hacer *slicing* en Python:\n",
    "\n",
    "- `a[start:end]`: retorna los elementos desde `start` hasta `end - 1`.\n",
    "\n",
    "- `a[start:]`: retorna los elementos desde `start` hasta el final del arreglo.\n",
    "\n",
    "- `a[:end]`: retorna los elementos desde el principio hasta `end - 1`.\n",
    "\n",
    "- `a[:]`: crea una copia (*shallow*) del arreglo completo. Es decir, el arreglo retornado está en una nueva dirección de memoria, pero los elementos que están en este nuevo arreglo, hacen referencia a la dirección de memoria de los elementos del arreglo inicial.\n",
    "\n",
    "- `a[start:end:step]`: retorna los elementos desde `start` hasta no pasar `end`, en pasos de a `step`.\n",
    "\n",
    "- `a[-1]`: retorna el último elemento en el arreglo.\n",
    "\n",
    "- `a[-n:]`: retorna los últimos `n` elementos en el arreglo.\n",
    "\n",
    "- `a[:-n]`: retorna todos los elementos del arreglo menos los últimos `n` elementos.\n",
    "\n",
    "Podemos extraer un elemento específico desde una lista mediante indexación. Es posible recuperar una porción completa de la lista utilizando la notación de *slicing*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "base_uri": "https://localhost:8080/",
     "height": 102,
     "output_extras": [
      {
       "item_id": 1
      }
     ]
    },
    "colab_type": "code",
    "executionInfo": {
     "elapsed": 837,
     "status": "ok",
     "timestamp": 1520882477154,
     "user": {
      "displayName": "Cristian Ruz",
      "photoUrl": "//lh4.googleusercontent.com/-0WhxkzATSqY/AAAAAAAAAAI/AAAAAAAAIqQ/WE7Rcv4pnN8/s50-c-k-no/photo.jpg",
      "userId": "108117710965209443485"
     },
     "user_tz": 180
    },
    "id": "PFk2lDsEQeZF",
    "outputId": "4bd9bd1a-522b-4a02-c1ac-9c57248a13fe"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[2, 4, 10, 20]\n",
      "[2, 4, 10, 20, 25]\n",
      "[6, 7, 2, 4, 10]\n",
      "[6, 2, 10]\n",
      "[25, 20, 10, 4, 2, 7, 6]\n"
     ]
    }
   ],
   "source": [
    "# Tomando una tajada particular, en este caso desde la posición 2 hasta la anterior a 6\n",
    "numeros = [6, 7, 2, 4, 10, 20, 25]\n",
    "print(numeros[2:6])\n",
    "\n",
    "# tomando una sección desde la posición 2 hasta el final de la lista\n",
    "print(numeros[2:])\n",
    "\n",
    "# tomando una sección desde el principio hasta la posición anterior a 5\n",
    "print(numeros[:5:])\n",
    "\n",
    "# lo mismo anterior, pero saltando 2 posiciones a la vez\n",
    "print(numeros[:5:2])\n",
    "\n",
    "# invirtiendo una lista\n",
    "print(numeros[::-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "UiYd79gOQeZI"
   },
   "source": [
    "Las listas pueden ser ordenadas utilizando el método `sort()`. Esto ordena las listas en sí mismas (*in-place*) y no devuelve nada, es decir, el resultado no es asignable a una nueva lista."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "base_uri": "https://localhost:8080/",
     "height": 85,
     "output_extras": [
      {
       "item_id": 1
      }
     ]
    },
    "colab_type": "code",
    "executionInfo": {
     "elapsed": 636,
     "status": "ok",
     "timestamp": 1520882562759,
     "user": {
      "displayName": "Cristian Ruz",
      "photoUrl": "//lh4.googleusercontent.com/-0WhxkzATSqY/AAAAAAAAAAI/AAAAAAAAIqQ/WE7Rcv4pnN8/s50-c-k-no/photo.jpg",
      "userId": "108117710965209443485"
     },
     "user_tz": 180
    },
    "id": "MlvMpZaJQeZJ",
    "outputId": "5367f854-d4b0-4ac3-bc89-c96f5ed5f9bc"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[6, 7, 2, 4, 10, 20, 25]\n",
      "None\n",
      "[2, 4, 6, 7, 10, 20, 25]\n",
      "[25, 20, 10, 7, 6, 4, 2]\n"
     ]
    }
   ],
   "source": [
    "numeros = [6, 7, 2, 4, 10, 20, 25]\n",
    "print(numeros)\n",
    "\n",
    "# Ordenamos en sentido ascendente.\n",
    "# Observar como a no recibe ninguna asignación después de que la lista numeros es ordenada\n",
    "a = numeros.sort() \n",
    "print(a)\n",
    "print(numeros)\n",
    "\n",
    "# Ordenamos en sentido descendente\n",
    "numeros.sort(reverse=True)\n",
    "print(numeros)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "H1uP2UtqQeZN"
   },
   "source": [
    "Las listas han sido optimizadas para ser una estructura flexible y fácil de manejar. También se pueden recorrer con la notación de `for`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "base_uri": "https://localhost:8080/",
     "height": 85,
     "output_extras": [
      {
       "item_id": 1
      }
     ]
    },
    "colab_type": "code",
    "executionInfo": {
     "elapsed": 679,
     "status": "ok",
     "timestamp": 1520882774481,
     "user": {
      "displayName": "Cristian Ruz",
      "photoUrl": "//lh4.googleusercontent.com/-0WhxkzATSqY/AAAAAAAAAAI/AAAAAAAAIqQ/WE7Rcv4pnN8/s50-c-k-no/photo.jpg",
      "userId": "108117710965209443485"
     },
     "user_tz": 180
    },
    "id": "bhdy3jWrQeZO",
    "outputId": "4ce9d608-f448-4baf-dc4f-38c4a69d7a77"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tipo de pieza: Alfil    - cantidad: 2\n",
      "tipo de pieza: Peón     - cantidad: 8\n",
      "tipo de pieza: Rey      - cantidad: 1\n",
      "tipo de pieza: Reina    - cantidad: 1\n",
      "tipo de pieza: Caballo  - cantidad: 2\n",
      "tipo de pieza: Torre    - cantidad: 2\n"
     ]
    }
   ],
   "source": [
    "piezas = [[\"Alfil\", 2], [\"Peón\", 8], [\"Rey\", 1], [\"Reina\", 1], [\"Caballo\", 2], [\"Torre\", 2]]\n",
    "\n",
    "# Por cada recorrido en el ciclo, la variable pieza recibe un elemento de la lista,\n",
    "# de acuerdo al orden de la lista\n",
    "for pieza in piezas:\n",
    "    # El modificador \":8\" permite que el texto ocupe al menos 8 espacios en la línea\n",
    "    print(f'tipo de pieza: {pieza[0]:8} - cantidad: {pieza[1]}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "d2sXz4t6rX9W"
   },
   "source": [
    "En este ejemplo, cada elemento de la lista `piezas` es una lista que contiene 2 elementos: primero el tipo de pieza como `str`, y luego la cantidad de ellas como `int`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Más sobre *Strings*\n",
    "\n",
    "Son objetos que representan cadenas de caracteres y nos permiten trabajar con texto. Como tal vez sabrás, los *strings* corresponden a una secuencia inmutable de caracteres. En Python 3, todos los *strings* se representan en [Unicode](https://es.wikipedia.org/wiki/Unicode), codificación que permite representar virtualmente cualquier carácter en cualquier lenguaje que veremos posteriormente con más detalle. Entonces, pensemos que en Python un *string* es una secuencia inmutable de caracteres Unicode."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "m o\n"
     ]
    }
   ],
   "source": [
    "# Ejemplo de string\n",
    "cadena = \"mi cadena de texto\"\n",
    "print(cadena[0], cadena[-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Como se ve en el ejemplo anterior, podemos acceder a un elemento del _string_, pero no podemos modificarlo. Recordemos que un _string_ es una ***secuencia inmutable***, por lo tanto, no es posible modificar su valor una vez creado."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "'str' object does not support item assignment",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[9], line 2\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;66;03m# Intentaremos cambiar la primera letra por una mayúscula\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43mcadena\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mM\u001b[39m\u001b[38;5;124m\"\u001b[39m\n",
      "\u001b[0;31mTypeError\u001b[0m: 'str' object does not support item assignment"
     ]
    }
   ],
   "source": [
    "# Intentaremos cambiar la primera letra por una mayúscula\n",
    "cadena[0] = \"M\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para \"modificar\" un *string* hay que crear uno nuevo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Mi cadena de texto\n"
     ]
    }
   ],
   "source": [
    "cadena = \"Mi cadena de texto\"\n",
    "print(cadena)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "También se pueden concatenar *strings* para formar uno nuevo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Mi cadena de texto\n"
     ]
    }
   ],
   "source": [
    "otra_cadena = \"\"\n",
    "for caracter in cadena:\n",
    "    otra_cadena += caracter\n",
    "print(otra_cadena)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A continuación, algunas formas distintas de crear un *string* en Python:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "programando\n",
      "mucho\n",
      "un string\n",
      "con múltiples\n",
      "lineas\n",
      "Multiples con\n",
      "     comillas dobles\n",
      "Tres strings juntos\n",
      "un string concatenado\n",
      "Otra forma de string que nos permite utilizar más de una línea pero en verdad solo es una, lo que es muy útil para cumplir PEP-8 :)\n"
     ]
    }
   ],
   "source": [
    "a = \"programando\"\n",
    "b = 'mucho'\n",
    "c = '''un string\n",
    "con múltiples\n",
    "lineas'''\n",
    "d = \"\"\"Multiples con\n",
    "     comillas dobles\"\"\"\n",
    "e = (\"Tres\" \" strings\" \" juntos\")\n",
    "f = \"un string \" + \"concatenado\"\n",
    "g = (\"Otra forma de string que nos permite \"\n",
    "     \"utilizar más de una línea pero en verdad solo es una,\"\n",
    "     \" lo que es muy útil para cumplir PEP-8 :)\")\n",
    "\n",
    "print(a)\n",
    "print(b)\n",
    "print(c)\n",
    "print(d)\n",
    "print(e)\n",
    "print(f)\n",
    "print(g)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Secuencia de escape\n",
    "El `\\` se denomina carácter de escape y se combina con otros caracteres para darles un nuevo significado.\n",
    "\n",
    "Secuencia | Significado \n",
    "--- | --- \n",
    "\\\\\" | Comilla doble\n",
    "\\\\' | Comilla simple\n",
    "\\n | Salto de línea\n",
    "\\t | Tabulador\n",
    "\\\\\\ | Barra inversa o *Backslash*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hola quiero escribir comillas dobles \" y seguir escribiendo\n"
     ]
    }
   ],
   "source": [
    "a = \"hola quiero escribir comillas dobles \\\" y seguir escribiendo\"\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Métodos disponibles en *Strings*\n",
    "\n",
    "La clase `str` tiene muchos métodos para manipular *strings*. La función [`dir`](https://docs.python.org/3/library/functions.html#dir) nos permite obtener la lista:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(dir(str))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los siguientes métodos nos permiten responder preguntas sobre un _string_ (una instancia) en particular:\n",
    "- `str.isdigit()`: ¿Está compuesto solo por dígitos?\n",
    "- `str.endswith(s)`: ¿Termina con este otro _string_ `s`?\n",
    "- `str.index(s)`: ¿En qué posición empieza este otro _string_ `s`?\n",
    "\n",
    "Veamos algunos ejemplos:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "False\n",
      "True\n",
      "True\n",
      "True\n",
      "4\n",
      "4\n",
      "6\n"
     ]
    }
   ],
   "source": [
    "# El método isalpha retorna True si todos los caracteres del string\n",
    "# están en el alfabeto de algún lenguaje.\n",
    "print(\"abñ\".isalpha())\n",
    "# Si hay algún número, espacio o puntuación dentro del string, retornará False.\n",
    "print(\"t/\".isalpha())\n",
    "\n",
    "# El método isdigit retorna True si todos los caracteres en el string\n",
    "# son dígitos numéricos.\n",
    "print(\"34\".isdigit())\n",
    "\n",
    "# El método startswith retorna True si el string empieza exactamente\n",
    "# con el substring entregado.\n",
    "s = \"estoy programando\"\n",
    "print(s.startswith(\"est\"))\n",
    "# Mientras que endswith, retorna True si termina con el substring entregado.\n",
    "print(s.endswith(\"do\"))\n",
    "\n",
    "# Devuelve el índice donde comienza en el string la secuencia que\n",
    "# se pasa como argumento.\n",
    "print(s.find(\"y p\"))\n",
    "\n",
    "# El método index retorna el índice donde comienza la secuencia.\n",
    "# Acepta dos argumentos opcionales:\n",
    "# - la posición inicial donde comenzar la búsqueda y\n",
    "# - la posición final (hasta dónde llega buscando)\n",
    "print(s.index(\"y\", 4, 10))\n",
    "print(s.index(\"p\", 5, 10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A diferencia de los anteriores, existen otros métodos que actúan directamente sobre el *string* y retornan nuevos *strings*, sin modificar el original, ya que **los *strings* son objetos inmutables**. Algunos ejemplos útiles:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['hola', 'a', 'todos,', 'cómo', 'están']\n",
      "hola#a#todos,#cómo#están\n",
      "hola**a**todos,**cómo**están\n",
      "hola a todos, cómo están\n"
     ]
    }
   ],
   "source": [
    "string_inicial = \"hola a todos, cómo están\"\n",
    "\n",
    "# Retorna una lista de strings a partir de separar el original en base a \" \".\n",
    "string2 = string_inicial.split(\" \")\n",
    "print(string2)\n",
    "\n",
    "# Concatena todos los elementos de la secuencia dada por medio del string \"#\".\n",
    "string3 = \"#\".join(string2)\n",
    "print(string3)\n",
    "\n",
    "# Entrega un nuevo string en que se reemplazó cada \" \" por \"**\".\n",
    "print(string_inicial.replace(\" \", \"**\"))\n",
    "\n",
    "# Notar que el string inicial sigue intacto, solo obtuvimos nuevas versiones.\n",
    "print(string_inicial)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Uso de variables dentro de un *String*\n",
    "\n",
    "A lo largo del curso, seguramente has visto este uso de *strings*. Los *f-strings* son una forma de formatear *strings* que permite añadir expresiones de Python directamente en un *string*. Para indicarle a Python que utilizaremos esta funcionalidad, debemos anteponer el *string* con la letra `f` (de ahí el nombre *f-string*). \n",
    "\n",
    "Este método es muy útil para completar un *string* que estemos usando como \"plantilla\", para insertar valores de variables dentro de un *string*. Las ventajas de esta sintaxis son que el código queda más legible, conciso y es menos susceptible a errores, además de ser más rápida que otras formas de concatenación."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hola Juan Pérez, estás aprobado. Tu nota fue un 4.5.\n"
     ]
    }
   ],
   "source": [
    "nombre = \"Juan Pérez\"\n",
    "nota = 4.5\n",
    "\n",
    "if nota >= 4.0:\n",
    "    resultado = \"aprobado\"\n",
    "else:\n",
    "    resultado = \"reprobado\"\n",
    "\n",
    "print(f\"Hola {nombre}, estás {resultado}. Tu nota fue un {nota}.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si queremos incluir las *llaves* (`{`, `}`) dentro del *string*, podemos agregar un *escape character* que permite invocar una interpretación alternativa de los caracteres siguientes. Más concretamente, las llaves en este caso, se utilizan para encapsular la variable que queremos imprimir. Sin embargo, la representación alternativa, sería la representación literal de la llave. Luego, para este caso, si queremos imprimir una llave, esto se logrará con una doble llave.\n",
    "\n",
    "Veamos un ejemplo. Digamos que buscamos imprimir una simple definición de una clase en Java."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "public class MiClase\n",
      "{\n",
      "       public static void main(String[] args)\n",
      "       {\n",
      "           System.out.println('hola mundo');\n",
      "       }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "# Con estas variables generaremos el string\n",
    "clase = \"MiClase\"\n",
    "salida = \"'hola mundo'\"\n",
    "\n",
    "# En nuestra plantilla utilizamos llaves dobles cuando queremos mantenerlas...\n",
    "codigo = f\"\"\"\n",
    "public class {clase}\n",
    "{{\n",
    "       public static void main(String[] args)\n",
    "       {{\n",
    "           System.out.println({salida});\n",
    "       }}\n",
    "}}\"\"\"\n",
    "\n",
    "# ... pero en el resultado a imprimir solo se verá una llave simple\n",
    "print(codigo)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A veces queremos incluir muchas variables dentro de un *string*, pero como vimos, eso no es un problema. Sin embargo, a veces queremos asociar estas variables dentro de objetos más complejos, que hagan sentido en nuestra modelación. Pensemos en el siguiente ejemplo, dándole forma a un correo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "From: <cruz@ing.puc.cl>\n",
      "To: <alumnos@iic2233.com>\n",
      "Subject: IIC2233 - Este correo es urgente\n",
      "\n",
      "Este es un mail de prueba.\n",
      "\n",
      "Espero que el mensaje te sea de mucha utilidad!\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Las variables necesarias para crear nuestro string\n",
    "from_email = \"cruz@ing.puc.cl\"\n",
    "to_email = \"alumnos@iic2233.com\"\n",
    "message = (\"\\nEste es un mail de prueba.\\n\"\n",
    "           \"\\nEspero que el mensaje te sea de mucha utilidad!\")\n",
    "subject = \"IIC2233 - Este correo es urgente\"\n",
    "\n",
    "# Nuestro f-string\n",
    "print(f\"\"\"\n",
    "From: <{from_email}>\n",
    "To: <{to_email}>\n",
    "Subject: {subject}\n",
    "{message}\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "From: <a@ejemplo.com>\n",
      "To: <b@ejemplo.com>\n",
      "Subject: Tienes un correo\n",
      "\n",
      "Este es un correo para ti\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Las variables necesarias para crear nuestro string\n",
    "emails = (\"a@ejemplo.com\", \"b@ejemplo.com\")\n",
    "message = {\n",
    "    'subject': \"Tienes un correo\",\n",
    "    'message': \"\\nEste es un correo para ti\",\n",
    "}\n",
    "\n",
    "# Nuestro f-string\n",
    "print(f\"\"\"\n",
    "From: <{emails[0]}>\n",
    "To: <{emails[1]}>\n",
    "Subject: {message['subject']}\n",
    "{message['message']}\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "También podemos usar un diccionario que contenga listas e indexar la lista dentro del *string*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "From: <yo@ejemplo.com>\n",
      "To: <tu@ejemplo.com>\n",
      "Subject: mira este correo\n",
      "\n",
      "Sorry, no era tan importante\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Las variables necesarias para crear nuestro string\n",
    "message = {\n",
    "    \"emails\": [\"yo@ejemplo.com\", \"tu@ejemplo.com\"],\n",
    "    \"subject\": \"mira este correo\",\n",
    "    \"message\": \"\\nSorry, no era tan importante\"\n",
    "}\n",
    "\n",
    "# Nuestro f-string\n",
    "print(f\"\"\"\n",
    "From: <{message['emails'][0]}>\n",
    "To: <{message['emails'][1]}>\n",
    "Subject: {message['subject']}\n",
    "{message['message']}\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esto puede ser aún mejor: podemos pasar cualquier objeto como argumento, por ejemplo, una instancia de una clase. Luego, dentro del *string* podemos acceder a cualquiera de los atributos del objeto."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "From: <a@ejemplo.com>\n",
      "To: <b@ejemplo.com>\n",
      "Subject: Tienes un correo\n",
      "\n",
      "Que tengas un lindo día\n",
      "\n",
      "Saludos :)\n"
     ]
    }
   ],
   "source": [
    "# Esta clase representa a un e-mail\n",
    "class EMail:\n",
    "    def __init__(self, from_addr, to_addr, subject, message):\n",
    "        self.from_addr = from_addr\n",
    "        self.to_addr = to_addr\n",
    "        self.subject = subject\n",
    "        self.message = message\n",
    "\n",
    "\n",
    "# Creamos nuestra instancia de la clase\n",
    "email = EMail(\n",
    "    \"a@ejemplo.com\",\n",
    "    \"b@ejemplo.com\",\n",
    "    \"Tienes un correo\",\n",
    "    \"\\nQue tengas un lindo día\\n\\nSaludos :)\"\n",
    ")\n",
    "\n",
    "print(f\"\"\"\n",
    "From: <{email.from_addr}>\n",
    "To: <{email.to_addr}>\n",
    "Subject: {email.subject}\n",
    "{email.message}\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Otro elemento muy útil de Python es el uso de `str.format()`. Si queremos usar una plantilla, es decir, un _string_ que queremos usar muchas veces reemplazando elementos con distintas variables, podemos crear un _string_ con una notación similar a la de *f-strings* y usar `str.format()` para hacer el reemplazo de los valores."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hola a@ejemplo.com, te damos la bienvenida!\n",
      "Hola b@ejemplo.com, te damos la bienvenida!\n"
     ]
    }
   ],
   "source": [
    "correo_1 = \"a@ejemplo.com\"\n",
    "correo_2 = \"b@ejemplo.com\"\n",
    "\n",
    "saludar_usuario = \"Hola {}, te damos la bienvenida!\"\n",
    "\n",
    "print(saludar_usuario.format(correo_1))\n",
    "print(saludar_usuario.format(correo_2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sin embargo, dado que podemos hacer múltiples reemplazos a través del uso de múltiples llaves dentro del *string*, podemos asignarle un nombre a cada uno de estos espacios y elegir qué valor darle a ese espacio particular a través del uso de *keyword arguments*:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hola b@ejemplo.com, te saluda el usuario a@ejemplo.com\n"
     ]
    }
   ],
   "source": [
    "saludo_entre_usuarios = \"Hola {saludado}, te saluda el usuario {saludador}\"\n",
    "\n",
    "print(saludo_entre_usuarios.format(saludado=correo_2, saludador=correo_1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Bonus: Mejorando la impresión de los *Strings*\n",
    "\n",
    "También podemos mejorar el formato de los *strings* que se imprimen. Por ejemplo, en casos como la impresión de una tabla con datos, muchas veces queremos que datos pertenecientes a la misma variable se vean alineados en columnas:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [],
   "source": [
    "compra = [('leche', 2, 120), ('pan', 3.5, 800), ('arroz', 1.75, 960)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "PRODUCTO | CANTIDAD | PRECIO | SUBTOTAL\n",
      "leche | 120 | $2 | $240\n",
      "pan | 800 | $3.5 | $2800.0\n",
      "arroz | 960 | $1.75 | $1680.0\n"
     ]
    }
   ],
   "source": [
    "print(\"PRODUCTO | CANTIDAD | PRECIO | SUBTOTAL\")\n",
    "for producto, precio, cantidad in compra:\n",
    "    subtotal = precio * cantidad\n",
    "    print(f\"{producto} | {cantidad} | ${precio} | ${subtotal}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Como puedes ver, los elementos de cada línea no quedan alineados, pero Python nos permite utilizar elementos extra en la formación de *strings* para darle una forma más estructurada:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "PRODUCTO | CANTIDAD | PRECIO | SUBTOTAL\n",
      "leche    |   120    | $2.00  | $ 240.00\n",
      "pan      |   800    | $3.50  | $2800.00\n",
      "arroz    |   960    | $1.75  | $1680.00\n"
     ]
    }
   ],
   "source": [
    "print(\"PRODUCTO | CANTIDAD | PRECIO | SUBTOTAL\")\n",
    "for producto, precio, cantidad in compra:\n",
    "    subtotal = precio * cantidad\n",
    "    print(f\"{producto:8s} | {cantidad: ^8d} | ${precio: <5.2f} | ${subtotal: >7.2f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notar que, dentro de cada llave, existe un ítem tipo diccionario; es decir, antes de los dos puntos, va la variable que utilizamos normalmente en nuestro *f-string*. Después de los dos puntos, por ejemplo, `8s`, significa que el dato es un *string* de ocho caracteres. Por defecto, si el *string* es más corto que los ocho caracteres, el resto se llenará con espacios (por la derecha). Notar que también, por defecto, si el *string* que ingresamos es más largo que los 8 caracteres, este **no** será truncado:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "compra = [('lecheeeeeeeeeeeeeee', 2, 120), ('pan', 3.5, 800), ('arroz', 1.75, 960)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "PRODUCTO | CANTIDAD | PRECIO | SUBTOTAL\n",
      "lecheeeeeeeeeeeeeee |   120    | $2.00  | $ 240.00\n",
      "pan      |   800    | $3.50  | $2800.00\n",
      "arroz    |   960    | $1.75  | $1680.00\n"
     ]
    }
   ],
   "source": [
    "print(\"PRODUCTO | CANTIDAD | PRECIO | SUBTOTAL\")\n",
    "for producto, precio, cantidad in compra:\n",
    "    subtotal = precio * cantidad\n",
    "    print(f\"{producto:8s} | {cantidad: ^8d} | ${precio: <5.2f} | ${subtotal: >7.2f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Podemos cambiar esta situación obligando a que el *string* sea truncado si se pasa del largo máximo. Basta con agregar un punto y la precisión luego del número que indica el largo del *string*. La precisión para tipos no numéricos indica el largo máximo de caracteres usados para este campo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "PRODUCTO | CANTIDAD | PRECIO | SUBTOTAL\n",
      "lecheeee |   120    | $2.00  | $ 240.00\n",
      "pan      |   800    | $3.50  | $2800.00\n",
      "arroz    |   960    | $1.75  | $1680.00\n"
     ]
    }
   ],
   "source": [
    "print(\"PRODUCTO | CANTIDAD | PRECIO | SUBTOTAL\")\n",
    "for producto, precio, cantidad in compra:\n",
    "    subtotal = precio * cantidad\n",
    "    print(f\"{producto:8.8s} | {cantidad: ^8d} | ${precio: <5.2f} | ${subtotal: >7.2f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para la cantidad de producto el formato es `{cantidad: ^8d}`. `cantidad` corresponde a la variable de la tupla en el recorrido actual, el espacio después de los dos puntos dice que los lugares vacíos deben ser llenados con espacios (en los tipos enteros, por defecto, se llenará con ceros), el símbolo `^` es para que el número quede centrado en el espacio disponible, `8d` significa que será un entero (***d**ecimal*) de hasta ocho dígitos. Notar que siempre el orden de estos parámetros (aunque son opcionales) debe ser de izquierda a derecha después de los dos puntos: **primero el carácter para llenar los espacios vacíos, después el alineamiento, después el tamaño y finalmente el tipo**.\n",
    "\n",
    "Para el precio, el formato es `{precio: <5.2f}`. El dato se leerá de la variable `precio`, luego los lugares que queden libres se llenarán con espacios, el símbolo `<` significa que el alineamiento es a la izquierda, el formato será un número decimal (***f**loat*) de hasta cinco caracteres, con dos decimales.\n",
    "\n",
    "De la forma equivalente, para el subtotal el formato es `{subtotal: >7.2f}`. El dato se sacará de la variable `subtotal`, el carácter de llenado será espacio, el alineamiento es a la derecha y será un *float* de siete dígitos, dos de ellos decimales, incluyendo el `.` como carácter."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para más información sobre esta notación y los parámetros que recibe, puedes revisar la [documentación entregada por Python](https://docs.python.org/3/library/string.html#format-specification-mini-language)."
   ]
  }
 ],
 "metadata": {
  "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.11.14"
  },
  "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
}
