{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "04b97205",
   "metadata": {},
   "source": [
    "# <span style=\"color:#F72585\"><center>Iterables e iteradores</center></span>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "007bf0a6",
   "metadata": {},
   "source": [
    "<figure>\n",
    "<center>\n",
    "<img src=\"https://raw.githubusercontent.com/AprendizajeProfundo/Alejandria/main/Archivos_Generales/Imagenes/Defense.jpg\" width=\"600\" height=\"400\" align=\"center\" /> \n",
    "</center>   \n",
    "</figure>\n",
    "<center>\n",
    "Fuente: <a href=\"https://sp.depositphotos.com/stock-photos/nature.html\">View on la Defense - sp.depositphotos.com</a>\n",
    "</center>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "public-tuition",
   "metadata": {},
   "source": [
    "## <span style=\"color:#4361EE\">Introducción</span>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "settled-heavy",
   "metadata": {
    "colab_type": "text",
    "id": "Qwzm74tvByHP"
   },
   "source": [
    "Este es una lección introductoria al concepto de Iterables e iteradores en Python."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "human-revision",
   "metadata": {},
   "source": [
    "## <span style=\"color:#4361EE\">Iterables e iteradores</span>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "naval-semiconductor",
   "metadata": {},
   "source": [
    "Hay dos protocolos que es muy probable tenga que utilizar, o posiblemente necesite\n",
    "implementar en algún momento; estos son el protocolo Iterable y el protocolo Iterador. \n",
    "Estos dos protocolos están estrechamente relacionados y son muy utilizados y respaldados por un\n",
    "gran cantidad de tipos.\n",
    "\n",
    "Una de las razones por las que los iteradores y los iterables son importados es que se pueden usar con\n",
    "sentencias *for* en Python; esto hace que sea muy fácil integrar un iterable en el código\n",
    "que necesita procesar una secuencia de valores a su vez\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ee7501d",
   "metadata": {},
   "source": [
    "### <span style=\"color:#4CC9F0\">Iterables</span>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "exclusive-singapore",
   "metadata": {},
   "source": [
    "El protocolo Iterable es utilizado por tipos donde es posible procesar su contenido.\n",
    "uno a la vez.\n",
    "\n",
    "Un Iterable es un objeto que proporcionará un Iterador que puede ser\n",
    "utilizado para realizar este procesamiento.\n",
    "\n",
    "Como tal, el iterable no es el iterador en sí mismo; sino el proveedor del iterador.\n",
    "\n",
    "Hay muchos tipos iterables en Python, incluidas listas, conjuntos, diccionarios,\n",
    "tuplas, etc. Todos estos son contenedores iterables que proporcionarán un iterador.\n",
    "\n",
    "Es posible además construir clases que generen iteradores personalizados. De momento trabajaremos con los tipos que ya lo soportan, mencionados arriba."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0cdb40ab",
   "metadata": {},
   "source": [
    "### <span style=\"color:#4CC9F0\">Iteradores</span>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "institutional-teddy",
   "metadata": {},
   "source": [
    "Un iterador es un objeto que devolverá una secuencia de valores. \n",
    "\n",
    "Los iteradores pueden ser finitos de longitud o infinito (aunque la mayoría de  iteradores orientados a contenedores proporcionan un conjunto fijo de valores.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "089583e1",
   "metadata": {},
   "source": [
    "### <span style=\"color:#4CC9F0\">Generadores</span>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "leading-aurora",
   "metadata": {},
   "source": [
    "Un generador es una función especial que se puede utilizar para generar una secuencia de\n",
    "valores que se van a iterar sobre  demanda (es decir, cuando se necesitan los valores) en lugar de\n",
    "que producido todo por adelantado.\n",
    "\n",
    "Lo único que hace que una función  generadora trabaje como generador es el uso de\n",
    "palabra clave *yield*."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22cb9f35",
   "metadata": {},
   "source": [
    "### <span style=\"color:#4CC9F0\">Ejemplo de una función generadora</span>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 180,
   "id": "aware-relief",
   "metadata": {},
   "outputs": [],
   "source": [
    "def gen_numbers():\n",
    "    yield 1\n",
    "    yield 2\n",
    "    yield 3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "interim-greene",
   "metadata": {},
   "source": [
    "Esta es una función generadora, ya que tiene al menos una declaración *yield* (de hecho, tiene\n",
    "Tres). \n",
    "\n",
    "Cada vez que se llama a la función *gen_numbers ()* dentro de una instrucción *for*\n",
    "devolverá uno de los valores asociados con una declaración *yield*; en este caso el\n",
    "valor 1, luego el valor 2 y finalmente el valor 3 antes de que regrese (termine). \n",
    "\n",
    "Ahora construimos un ciclo *for* que va llamando los números a la medida que los necesita. Observe que la variable *i* en este ejemplos es un iterador entregado por la función *gen_numbers()*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 181,
   "id": "based-creator",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n",
      "2\n",
      "3\n"
     ]
    }
   ],
   "source": [
    "for i in gen_numbers():\n",
    "    print(i)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "hundred-scenario",
   "metadata": {},
   "source": [
    "Para entender un poco mejor cómo se ejecuta *yield* discuta en clase el siguiente fragmento (snnipet) de código."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "immune-elite",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Start\n",
      "3\n",
      "Continue\n",
      "2\n",
      "Final\n",
      "3\n",
      "End\n"
     ]
    }
   ],
   "source": [
    "def gen_numbers2():\n",
    "    print('Start')\n",
    "    yield 1\n",
    "    print('Continue')\n",
    "    yield 2\n",
    "    print('Final')\n",
    "    yield 3\n",
    "    print('End')\n",
    "\n",
    "for i in gen_numbers2():\n",
    "    print(i)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "353c7c61",
   "metadata": {},
   "source": [
    "### <span style=\"color:#4CC9F0\">Creación de una clase iterable</span>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "communist-tattoo",
   "metadata": {},
   "source": [
    "Más adelante veremos clases a plenitud. Aquí introducimos una clase muy sencilla para mostrar como crear un iterable personalizado."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 183,
   "id": "noticed-wilderness",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Evens(object):\n",
    "    def __init__(self, limit):\n",
    "        self.limit = limit\n",
    "        self.val = 0\n",
    "    \n",
    "    # Hace esta clase iterable\n",
    "    def __iter__(self):\n",
    "        return self\n",
    "    \n",
    "    # Hace esta clase un iterador\n",
    "    def __next__(self):\n",
    "        if self.val > self.limit:\n",
    "            raise StopIteration\n",
    "        else:\n",
    "            return_val = self.val\n",
    "            self.val += 2\n",
    "            return return_val"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f11a93e-c1d1-4bb7-8799-e67400a15d41",
   "metadata": {},
   "source": [
    "\n",
    "Solamente vamos a resaltar unas pocas cosas de esta clase *Events*\n",
    "\n",
    "+ El método  \\_\\_iter__()  retorna *self*; este es un patrón muy común y asume que la clase también implementa el protocolo iterador.\n",
    "+ El método  \\_\\_next__() retorna el siguiente valor en la secuencia o dispara una excepción con *StopIteration* para indicar que no hay más valores disponibles.\n",
    "\n",
    "\n",
    "El protocolo implica que cuando Python encuentra la definición de la función \\_\\_iter__() construye la maquinaria para que la clase sea un iterable. De la misma forma, cuando encuentra la función \\_\\_next__() construye la maquinaria para que la clase entregue un iterador.\n",
    "\n",
    "\n",
    "Veamos en acción *Evens*. Discuta en la clase."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 184,
   "id": "sunset-moderator",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "¡Empezamos\n",
      "0, 2, 4, 6, 8, 10, 12, 14, 16, \n",
      "Hecho!\n"
     ]
    }
   ],
   "source": [
    "print('¡Empezamos')\n",
    "for i in Evens(16):\n",
    "    print(i,end=', ')\n",
    "print('\\nHecho!')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c1474115",
   "metadata": {},
   "source": [
    "### <span style=\"color:#4CC9F0\">Ejercicio</span>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "df6ad0c6",
   "metadata": {},
   "source": [
    "¿Qué hace el código anterior línea por línea? Comente el código "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "colored-preview",
   "metadata": {},
   "source": [
    "## <span style=\"color:#4361EE\">Colecciones</span>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "peaceful-juice",
   "metadata": {},
   "source": [
    "Una colección es un contenedor de objetos del mismo tipo. Por defecto en Python existen cuatro tipos de contenedores:\n",
    "\n",
    "- **Tuplas (tuple).** Esta es una colección de objetos que están ordenados y son inmutables (no pueden modificarse). Las tuplas admiten elementos repetidos y sus miembros son indexados.\n",
    "\n",
    "- **Listas (list).** Son colecciones de objetos que están ordenado y son mutables, es decir, sus contenidos pueden ser modificados. Los elementos son indexados y permite duplicados.\n",
    "\n",
    "- **Conjuntos (set).** Son contenedores de datos que no son ordenados ni indexados. Son mutables, pero no admiten duplicados.\n",
    "\n",
    "- **Diccionarios (dictionary)** Son contenedores no ordenados, que son indexados mediante una clave, la cual referencia a un valor. El valor es retornado cuando se le solicita con la clave. No se admiten claves repetidas, pero si valores repetidos.\n",
    "\n",
    "Recuerde que todo en Python es realmente un tipo de objeto."
   ]
  }
 ],
 "metadata": {
  "interpreter": {
   "hash": "cf92aa13fedf815d5c8dd192b8d835913fde3e8bc926b2a0ad6cc74ef2ba3ca2"
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.9.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}