MAR DE NUBES Y LÍMITE EN ALTURA DEL PINAR. ZONA NORTE DE LAS CAÑADAS DEL TEIDE. TENERIFE. |
PILAS:
Este apartado tiene que ver con la manera en que se almacenan en memoria las devoluciones, esto es, los resultados que vamos obteniendo en sendas llamadas a las funciones cuando se las invoca dese otra función, y de la manera en que podemos acceder a éstos.
Las pilas (stacks, en inglés) son TAD, esto es, Tipos Abstractos de Datos, que se estructuran en un formato de tipo lista a cuyos elementos se accede de una forma particular, tanto que tiene hasta su propio acrónimo en inglés: LIFO, de Last In, First Out, que podemos traducir como "Lo último en entrar (será) lo primero en salir".
Una buena (y socorrida) analogía para entender el concepto de LIFO es imaginarnos una pila de platos, stack of dishes, en un restaurante donde se está desarrollando una cena de empresa que, perfectamente, podemos asimilar a la ejecución de nuestro programa: Tenemos en la cocina un pinche que va lavando los platos (ítems, elementos de una lista) que le van trayendo del salón comedor. Conforme los introduce en el lavaplatos y los seca y abrillanta dejándolos impolutos, los va apilando (pila → lista de ítems) en un escurridor vertical (bloque de memoria) "apilados" cada plato uno encima del otro (push). Cada cierto tiempo, aparece por allí un ayudante de cocina que va cogiendo los platos limpios de la pila (extracción/desapilamiento (pop)) para usarlo de nuevo en el servicio de comensales... pero, ¿qué platos coge? Pues para que la pila no se venga abajo y termine todo en un tremendo estropicio, tomará siempre, y de uno en uno, el primero de la pila, el que se encuentre encima (TOS, de Top Of Stack) y, si tiene que retira más platos, pues justo el que ocupe cada vez la cima de la pila, en orden descendente.
A partir de este ejemplo podríamos deducir que la propia estructura de almacenamiento de datos en un mismo bloque de memoria, la pila, produce un "apilamiento" de datos y, en consecuencia, determina la forma en que podemos acceder a ellos y recuperarlos.
Una vez que sabemos esto cabe preguntarnos para qué sirven las pilas, dónde se usan, dónde se aplican. Podemos enumerar los siguientes cuatro ámbitos de programación:
- En la evaluación de algoritmos en la Notación de Prefijo o Notación Polaca Inversa (los operadores se escriben a continuación de los operandos). Es el uso más particular. Podemos obtener algo más de información sobre este sistema de notación a través del siguiente artículo en la Wikipedia: Notación Polaca Inversa.
- En el reconocimiento de la sintaxis de lenguajes al margen de su contexto.
- En el uso de las funciones recursivas.
- Funciones main().
Ahora que sabemos esto podríamos preguntarnos qué operaciones podemos llevar a cabo con las pilas.
Pues de entrada, las que nos dicta la lógica de su estructura: apilar (push) y desapilar (pop), lo que "traducido" a nuestra analogía significa poner platos (encima de la pila) por lo que cada nuevo plato que apilemos estará necesariamente en el TOS, y quitarlos (aquél plato que se encuentre en ese mismo instante encima de la pila, ocupando el TOS, y no otro), lo cual nos lleva a colegir que estas dos operaciones básicas actúan siempre sobre un objeto (plato) TOS, estando su espacio de actuación siempre en la cima de la pila, bien para añadir un nuevo objeto (plato) que será, inevitablemente, sobre un plato que ya estuviera ocupando una posición TOS (push, en este caso), o bien para quitarlo que será, inevitablemente, el objeto (plato) que en ese momento se encuentre en la cima de la pila, esto es, en el TOS.
A parte de estas dos operaciones básicas contamos con otras, unas poquitas más, que vienen a ser las siguientes:
- CREAR: Construimos una 'pila vacía', el elemento contenedor, dentro de la que iremos amontonando los datos como amontonábamos platos: uno encima del otro. Para hacerlo utilizamos el constructor __init__().
- CIMA: Obtenemos aquél elemento que se encuentre en lo más alto de la pila en ese momento (TOS), y que coincide, como es lógico, con el último elemento en ser agregado a la pila.
- TAMAÑO: Devuelve cuántos elementos hay depositados en la pila. Su size (que, como ya veremos, se corresponde con la devolución de la función nativa len()).
- VACIAR: Booleano. Devuelve True si la pila está vacía o False si la pila contiene un elemento o más. Se corresponde con empty.
CHUPONES DE RETAMA APROVECHANDO COMO SUSTRATO EL TRONCO CAÍDO DE UNA HIJA, LAURISILVA DE VALLE BROSQUE, MACIZO DE ANAGA, NORESTE DE TENERIFE. |
CONSTRUCCIÓN DE UNA PILA EN MODO LISTA:
La manera natural de construir (e imaginarnos, claro) una pila, stack, en modo lista es "volcando" la representación que hemos hecho hacia la derecha, pasando de una posición vertical a otra horizontal. Lo vemos gráficamente:
Veamos ahora un ejemplo sencillo de cómo utilizar una lista en modo pila (stack). Estos métodos de pila que hemos visto, como ya nos habremos dado cuenta, coinciden o tienen su correspondencia con los métodos append(), que añade un elemento a la lista y que, además, de manera predeterminada, lo hace al final de la lista, en su extremo derecho, lo que en el gráfico de arriba se corresponde con el tope (TOP) de la pila cuando la "volcamos"; y pop() para retirar el último elemento añadido, esto es, el TOP de la pila.
CUANDO NOS DETERMINAMOS A USAR UNA LISTA EN MODO PILA DENTRO DE NUESTRO PROPIO PROGRAMA, DEBEMOS TRATAR A LA LISTA EXCLUSIVAMENTE COMO SI FUERA UNA PILA, TAL CUAL, E "IGNORAR" QUE ESTAMOS OPERANDO CON UNA LISTA, RECURRIENDO A LOS MÉTODOS DE PILA QUE HEMOS VISTO MÁS ARRIBA ÚNICAMENTE, SIN HACER USO DE NINGÚN OTRO. DE AQUÍ QUE DEBAMOS SELECCIONAR BIEN CÓMO Y DONDE IMPLEMENTARLAS EN NUESTRO CÓDIGO. ¡UY!, ME LLAMAN PARA SALIR DE PASEO. ¡HASTA OTRA!
En 1. apilamos un único elemento cada vez. Si tenemos varios, igualmente, debemos ir apilándolos de uno en uno: no nos vale usar el método extend() o concatenar listas, dado que no estaríamos usando la lista como pila, lo que podría derivar en problemas, tanto en la ejecución como en la interpretación de las devoluciones de nuestro código.
En 2. desapilamos con el método pop(), que no lleva argumentos pues, por defecto, elimina (y muestra) el último elemento de la lista, el primero por la derecha, aquél que precisamente, ocupa el TOP (y está en el TOS) de la pila en el momento de aplicar el método. Es el método oportuno en lugar de remove() o popitem(), en tanto que ejecuta el desapilamiento de manera automática.
SÓLO Y TAN SÓLO EN OCASIONES MUY CROQUETAS... ¡AY! CONCRETAS... ¿EN QUÉ ESTARÍA YO PENSANDO? (QUE EN UN PROGRAMA CUALQUIERA SE DETERMINAN NORMALMENTE CON EL RECURSO Al CONDICIONAL if O while) PODEMOS VACIAR UNA LISTA "DE SOPETÓN" CON EL MÉTODO clear(). AÚN ASÍ, MUCHO, MUCHO CUIDADO CON SU USO CUANDO RECURRIMOS A APILAMIENTOS EN NUESTROS PROGRAMAS.
Veamos a continuación un segundo ejemplo en el que utilizaremos una función diseñada para crear una pila. En esta ocasión recurriremos (¡Ay, Dios mío!) a una CLASE (class) de Python. Este concepto de CLASE forma parte de la Programación Orientada a Objetos y que aún no hemos visto en este manual virtual del lenguaje (empezaremos con ello a partir de Enero/Febrero del 2019, si Dios quiere), pero lo que nos interesa ahora mismo es fijarnos en las funciones definidas (def) que sí conocemos y en su estructura.
En 1., lo primero de todo, es "ignorar" el self en la zona de argumentos de def __init__() dado que "no toca" así como de todos los selfs del código anterior allí donde se encuentren. Esta función def es, en realidad, un constructor o inicializador en terminología de la POO mediante la cual, resumiendo, configuramos las características iniciales con el que dotaremos a un objeto que creamos desde la clase Pila (a, como lo denominaremos en la ejecución que veremos más abajo).
En este caso nuestro objeto a será una lista, un objeto Python de tipo list, vamos, el cual no llevará ningún elemento en su interior en el momento de su creación: una lista vacía.
Una vez hemos construido la lista (de aquí que a la función def __init__() se la llame 'constructor', ¿capisci?) añadimos una serie de funciones, de 2. a 6. para escenificar los métodos que podemos aplicar a una lista cuando trabajamos en modo pila.
En 2. introducimos como argumento un elemento (item) que se agregará a la lista. Conforme a cada llamada a la función se irán apilando distintos elementos a la lista de uno en uno.
En 3. implementamos el método pop() de las listas para mostrar y eliminar el primer elemento a la derecha de la lista (el TOP de la lista, el tope o cima que se corresponde, recordemos, con el último elemento en ser añadido), también de uno en uno, como retiramos los platos limpios de una pila. Con try/except, como es posible que intentemos desapilar sobre una lista que se haya vaciado con el correr (run) del programa, el programa lanzaría una excepción.
Para capturarla y manejarla debidamente utilizaremos un bloque try/except (ver TRATAMIENTO DE EXCEPCIONES) con la excepción IndexError, que es la que se nos lanzaría por defecto si no existiera ningún elemento en la lista.
Podemos conocer en 3. el tamaño, el size, de nuestra lista/pila con la función nativa len(), que no tiene capacidad operativa alguna sino, como ya sabemos, sólo informativa, señalándonos el número de elementos con que cuenta nuestra lista/pila en el estado actual.
En 5. aportamos una función construida sobre un condicional if/else que, en consecuencia, nos proporcionará una bifurcación, imprimiendo en pantalla un mensaje u otro con la función print() según nuestra lista/pila esté vacía o no.
Finalmente, en 6., vaciamos de contenido la lista con el método clear() y la devolvemos a su estado original.
En este caso nuestro objeto a será una lista, un objeto Python de tipo list, vamos, el cual no llevará ningún elemento en su interior en el momento de su creación: una lista vacía.
Una vez hemos construido la lista (de aquí que a la función def __init__() se la llame 'constructor', ¿capisci?) añadimos una serie de funciones, de 2. a 6. para escenificar los métodos que podemos aplicar a una lista cuando trabajamos en modo pila.
En 2. introducimos como argumento un elemento (item) que se agregará a la lista. Conforme a cada llamada a la función se irán apilando distintos elementos a la lista de uno en uno.
En 3. implementamos el método pop() de las listas para mostrar y eliminar el primer elemento a la derecha de la lista (el TOP de la lista, el tope o cima que se corresponde, recordemos, con el último elemento en ser añadido), también de uno en uno, como retiramos los platos limpios de una pila. Con try/except, como es posible que intentemos desapilar sobre una lista que se haya vaciado con el correr (run) del programa, el programa lanzaría una excepción.
Para capturarla y manejarla debidamente utilizaremos un bloque try/except (ver TRATAMIENTO DE EXCEPCIONES) con la excepción IndexError, que es la que se nos lanzaría por defecto si no existiera ningún elemento en la lista.
Podemos conocer en 3. el tamaño, el size, de nuestra lista/pila con la función nativa len(), que no tiene capacidad operativa alguna sino, como ya sabemos, sólo informativa, señalándonos el número de elementos con que cuenta nuestra lista/pila en el estado actual.
En 5. aportamos una función construida sobre un condicional if/else que, en consecuencia, nos proporcionará una bifurcación, imprimiendo en pantalla un mensaje u otro con la función print() según nuestra lista/pila esté vacía o no.
Finalmente, en 6., vaciamos de contenido la lista con el método clear() y la devolvemos a su estado original.
CASERÍO RURAL ANTIGUO CERCA DE LA FUENTILLA, AL PIE DEL ROQUE DE JAMA, MUNICIPIO DE ARONA, AL SUROESTE DE TENERIFE. |
COLAS:
Una cola (queue) es una variante de las pilas y, como ella, es también un TAD (recordemos, Tipos Abstractos de Datos). Representa una estructura de datos FIFO, del inglés, First In, First Out, donde el primer elemento en entrar (First in) es también el primer elemento en salir (First Out), al contrario de lo que sucede con las pilas (stacks) que, como acabamos de ver, representa una estructura de datos LIFO, donde el último elemento en entrar es también el primero en salir.
Un buen ejemplo para entender el concepto de cola es el de la cola del supermercado, el ejemplo clásico, típico, por lo demás: imaginemos que estamos en el supermercado de nuestra preferencia, que ya hemos terminado de hacer nuestra compra, y nos dirigimos con la cesta (o el carrito) hacia el lineal de cajas para pagar. Como siempre, de los diez puntos de caja sólo hay dos funcionando. Y como es sábado, que es el día de compras por antonomasia aunque llueva, en ambas se han formado sendas hileras de compradores haciendo cola (queue), con sus cestas y/o carritos respectivos, esperando a ser atendidos por la cajera de turno.
Nos ponemos al final de una de ellas, como corresponde, a ser posible, en la que se nos antoje más corta por ver si salimos antes, con unas cinco personas delante nuestro. Obviamente, la cajera está atendiendo a la persona 1, que es la que en ese momento ocupa el primer lugar, colocando su compra sobre la cinta. hay cuatro personas más detrás (len(cola) = 5, es decir, la persona 1 más las cuatro de atrás) y nosotros nos añadimos (append()) al final. Ahora len(cola) = 5.
La persona 1 abona su compra y se va (popleft()) alegre y feliz como una lombriz, como solía decir el recordado Juan Antonio Cebrián, en dirección al parking. Esto hace que, "automáticamente", la siguiente persona, la persona 2, que estaba justo detrás la que está siendo atendida ahora mismo por la sonriente cajera. Ahora nuestra cola tiene un elemento menos: somos 5.
La cajera va atendiendo progresivamente a los clientes (persona 2, persona 3, persona 4) y éstos van saliéndose (popleft()) de la cola. Cuando sólo tenemos a la persona 5 delante de nosotros, se suma a la cola una dulce ancianita con su carro a rebosar de verduras. Ahora, tras el append(ancianita), nuestra cola tiene tres elementos: persona 5 que es quien, exactamente, nos precedía al incorporarnos nosotros; nosotros mismos, la persona 6; y la dulce ancianita que, al incorporarse, pasa a ser la persona 7 de una cola de 3 elementos.
La persona 5 paga, retira su compra, y sale de la cola (popleft()), y se suman otras dos personas a la cola. Por fin somos nosotros los primeros y la sufrida pero sonriente cajera nos regala un sonriente "Buenos días". Estamos ahora en una cola de cuatro elementos: nosotros, que ahora seríamos la persona 1, junto a la caja; la ancianita, persona 2; y las dos últimas incorporaciones, las personas 3 y 4 respectivamente.
La amable nos informa del costo de la compra, lo abonamos y nos salimos de la cola, popleft().
Así funcionan las colas. Y éste es el comportamiento que queremos reproducir en codificación: dada una secuencia de datos, que el primero de la secuencia sea el primero en salir.
Nos ponemos al final de una de ellas, como corresponde, a ser posible, en la que se nos antoje más corta por ver si salimos antes, con unas cinco personas delante nuestro. Obviamente, la cajera está atendiendo a la persona 1, que es la que en ese momento ocupa el primer lugar, colocando su compra sobre la cinta. hay cuatro personas más detrás (len(cola) = 5, es decir, la persona 1 más las cuatro de atrás) y nosotros nos añadimos (append()) al final. Ahora len(cola) = 5.
La persona 1 abona su compra y se va (popleft()) alegre y feliz como una lombriz, como solía decir el recordado Juan Antonio Cebrián, en dirección al parking. Esto hace que, "automáticamente", la siguiente persona, la persona 2, que estaba justo detrás la que está siendo atendida ahora mismo por la sonriente cajera. Ahora nuestra cola tiene un elemento menos: somos 5.
La cajera va atendiendo progresivamente a los clientes (persona 2, persona 3, persona 4) y éstos van saliéndose (popleft()) de la cola. Cuando sólo tenemos a la persona 5 delante de nosotros, se suma a la cola una dulce ancianita con su carro a rebosar de verduras. Ahora, tras el append(ancianita), nuestra cola tiene tres elementos: persona 5 que es quien, exactamente, nos precedía al incorporarnos nosotros; nosotros mismos, la persona 6; y la dulce ancianita que, al incorporarse, pasa a ser la persona 7 de una cola de 3 elementos.
La persona 5 paga, retira su compra, y sale de la cola (popleft()), y se suman otras dos personas a la cola. Por fin somos nosotros los primeros y la sufrida pero sonriente cajera nos regala un sonriente "Buenos días". Estamos ahora en una cola de cuatro elementos: nosotros, que ahora seríamos la persona 1, junto a la caja; la ancianita, persona 2; y las dos últimas incorporaciones, las personas 3 y 4 respectivamente.
La amable nos informa del costo de la compra, lo abonamos y nos salimos de la cola, popleft().
Así funcionan las colas. Y éste es el comportamiento que queremos reproducir en codificación: dada una secuencia de datos, que el primero de la secuencia sea el primero en salir.
RESULTA MUY ÚTIL POR EJEMPLO, CUANDO QUEREMOS CREAR UNA SECUENCIA CON UN NÚMERO FIJO DE ELEMENTOS, ES DECIR, QUE MANTENGA UN len(secuencia) CONSTANTE, ES DECIR, QUE NO PUEDA MODIFICARSE Y QUE, SIN EMBARGO, DE FORMA DINÁMICA PUEDA IR INCORPORANDO ELEMENTOS NUEVOS. UN EJEMPLO DE LO QUE DECIMOS PUEDE SER MANTENER EL CONTROL DE NÚMERO MÁXIMO DE PERSONAS QUE PUEDE ADMITIR UN ASCENSOR.
¿Qué podemos hacer con una cola? Pues, de manera elemental, las siguientes acciones, muy similares a los métodos de las pilas:
- CREAR: Mediante el constructor __init__() una cola vacía en el caso de que hubiéramos modelizado una clase.
- ENCOLAR: Añadir un nuevo elemento a la derecha de la cola, es decir, al final de la misma. Se usa el método clásico de adición a una lista, igual que en las pilas: append().
- DESENCOLAR: Eliminar de la lista el primer elemento, es decir, aquél que se encuentre a la izquierda de la cola. Por eso usamos la función popleft(), igual que pop() en las pilas, sin argumentos pues ya 'sabe' de manera predeterminada qué elemento tiene que sacar de la cola... pero por la izquierda. De aquí el añadido left.
A pesar de que nos movemos en una estructura de tipo lista, el recurso directo a éste, al contrario que en las pilas, no resulta eficiente: en el caso de las pilas, la acción de añadir y extraer elementos se ejecuta de manera plana ya que el resto de elementos de la secuencia conservan sus posiciones (indexado → cada elemento conserva su índice); pero en el caso de las colas, al extraer un elemento por el lado de la izquierda (siempre de uno en uno, por cierto, del mismo modo que concurre con las pilas), todos los demás elementos corren una posición a la izquierda, con lo que el índice de cada elemento disminuye en 1: de 5 pasa a 4; de 4 pasa a 3; de 3 a 2; etc.
Por ello Python se 'sale' del recurso lógico a una lista como en el caso de las pilas e implementa las colas a través de la clase deque del módulo collections de la librería estándar. Normalmente, se utiliza una importación relativa como en el ejemplo que mostramos a continuación.
1. La función deque() puede llevar un argumento opcional de lo más interesante: maxlen. Dicho argumento opcional determina una longitud o tamaño constante para la cola. Si, por ejemplo, quisiéramos que nuestra cola tuviera siempre 5 elementos, pasaríamos 5 como valor de maxlen:
cola = deque(iterable, 5)
Con este parámetro, la función se vacía de elementos por el lado izquierdo de manera automática, para conservar siempre su longitud predeterminada.
Veamos otro ejemplo más:
Como acabamos de decir, el uso más frecuente del parámetro opcional maxlen es cuando tenemos un iterable al que se van incorporando elementos de manera dinámica y queremos que tenga siempre un número fijo, exacto, de elementos. Por ejemplo, imaginemos que queremos construir un gráfico del total de ventas de un producto concreto de una empresa mes por mes: total de ventas en enero, febrero, marzo, etc... Sin embargo, sólo disponemos de espacio para mostrar un gráfico de barras con seis meses en el eje x. Si sumamos un séptimo mes, un octavo, ya se nos descuadra la gráfica, ¡leñe!, y termina por salirse del espacio que le hemos asignado. ¿Qué podemos hacer? Sencillo: creamos una lista donde almacenar los meses que va tomando mes a mes e una tabla de datos. En esa lista se pueden almacenar los primeros seis meses del año (porque hace seis meses que entregamos nuestro programa a la empresa y estos son los seis meses transcurridos que se han almacenado en esta tabla de datos). A esta lista a la que se irá agregando cada nuevo mes sin que nosotros tengamos que hacer nada → lista dinámica, le asignamos como parámetro, en el momento de crearla, claro, un maxlen = 6: listameses = [nombres_meses].
listado = deque(listameses, maxlen = 6)
Así, a 30 de junio nuestra listameses para añadir items al eje x de nuestro gráfico de barras tendrá el tope de elementos en 6:
listameses = ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio"]
¿Qué pasará entonces cuando llegue julio? Pues que sacará a "Enero" de listameses (ejecuta un popleft() automático) y añade tras el append(ítem) preceptivo por el lado derecho el mes de "Julio" al final de nuestra cola, como ya hemos dicho que debe ocurrir, con lo que siempre mantiene un maxlen de 6 ítems, procediendo de igual modo cuando toque añadir "Agosto", "Septiembre", etc.
Un puntazo, ¿verdad?
Veámoslo.
El recurso a la clase deque del módulo collections de la stdlib de Python nos proporciona un objeto deque que, en realidad, no es otra cosa que una secuencia de datos análoga al objeto lista que nos resulta ya tan familiar. Este objeto deque "copia" la lista que le pasamos como argumento y la convierte en una lista de tipo cola que tendrá sus propios métodos, casi todos similares a los métodos estándar de las listas, más algunos propios. Mostramos a continuación, subrayados en rojo, los nuevos métodos exclusivos de un objeto deque → lista de tipo cola.
Veamos algunos ejemplos de uso:
Veamos por curiosidad cómo conseguir un resultado similar a la inclusión de la propiedad maxlen y sin necesidad de recurrir a la clase deque, simplemente con código de andar por casa:
Para el caso de los diccionarios, la cosa ya no pinta tan bien: El objeto deque vendrá a ser un iterable con las claves pero no con los valores, que ya no podremos recuperar.
COSTA ACANTILADA Y ESCOLLERAS DE LOS REALEJOS CON PALMERAS CANARIAS (TAMARANES), DRAGOS Y TERRAZAS PARA EL CULTIVO DE PLÁTANOS AL FONDO, NORTE DE TENERIFE. |
PILAS DE LLAMADAS A FUNCIONES:
Pongamos un ejemplo: estamos en un bar y nos apetece ir al baño por aquello de las cervezas. Nos dirigimos bien ufanos hacia los servicios y nos encontramos con la puerta cerrada. "¿Estará cerrada porque sí o habrá alguien dentro?". Para salir de dudas, golpeamos la puerta (llamamos) tres veces: toc, toc, toc.
Si el acto de llamar a la puerta del lavabo fuera una función de Python, cada 'toc', tres en nuestro ejemplo, serían una devolución, un resultado. Como hemos golpeado tres veces obtendremos tres devoluciones como resultado de haber ejecutado sendas llamadas a la función "golpear_puerta".
Tenemos así una pila de tres llamadas a una misma función con su correspondiente pila de devoluciones ordenados temporalmente: primer "toc", segundo "toc" y tercer "toc".
Mmm... ¿Esto nos suena de algo? A ver, a ver,... ¡Sí! Las funciones recursivas (v. T2. FUNCIONES RECURSIVAS: PROBANDO, PROBANDO..., noviembre de 2017).
Sí, en efecto, por su propia naturaleza una función recursiva, que se llama a sí misma un número n de veces hasta que se cumpla una determinada condición (por eso todas deben llevar, al menos, una cláusula if/else o while), genera una pila de llamadas.
Tengamos en cuenta que no es lo mismo una función recursiva → RECURSIVIDAD o RECURSIÓN. que así se conoce el procedimiento a partir del cual la función se llama a sí misma de manera automática generando así un ciclo de llamadas que es perfectamente equiparable a una pila de llamadas a funciones, que una función recurrente (de uso recurrente, para ser exactos), esto es una función que no es recursiva ya que no cuenta con ningún mecanismo de autollamada, pero que podemos invocar varias veces: por ejemplo, siguiendo con nuestra analogía de los servicios del bar, podemos decir que tocar la puerta (toc-toc) es una función recursiva que se llamará a sí misma siete veces: toc-toc-toc-toc-toc-toc-toc (contador = 7) al cabo del cual se detendrá, se haya producido o no el caso base (o los casos base, si hay más de uno) que representa la condición que, en nuestro ejemplo, podría ser una respuesta por parte de la persona que se encuentra en su interior: "Ocupado" o, por ejemplo, que nadie responda a nuestras llamadas (string = "") y podamos acceder al servicio con toda tranquilidad, lo que detendrá el ciclo de llamadas. Podríamos adjuntar llamadas a funciones de uso recurrente a nuestro ejemplo, como preguntar "¿Hay alguien?" para los toques 2º y 5º si antes no hemos obtenido respuesta. Si la obtenemos, detenemos la ejecución de la función recursiva (el ciclo de autollamadas de la función recursiva) que tampoco hay que molestar. Un respetito, ¿eh? Pero si nadie contesta desde el otro lado de la puerta tendríamos un ciclo de llamadas completo de 7, (contador = 7), a la función recursiva y se ejecuta la acción asignada a este caso: tirar de la manija, y que se corresponde con una pila de siete llamadas, más otras dos llamadas a una función recurrente (porque nosotros hemos hecho uso de ellas llamándolas ex profeso) en los toques 2º y 5º y que se han saldado sin respuesta, por lo que el ciclo de llamadas recursivas no se detuvo y la función recursiva completó el límite de su contador (7). Así, para la función recurrente tenemos una pila de dos llamadas a funciones (2º y 5º).
SENDERO ESCALONADO EN LA LAURISILVA. |
Recordemos que los casos base son las condiciones que, cuando devienen True, detienen la ejecución de la función, la recursividad, impidiendo que la ejecución de la misma entre en un bucle infinito, mientras que el caso recursivo es, en sí, la propia autollamada a la función.
Cada vez que se llama a una función durante un proceso de escritura se aplica un nuevo elemento o componente a la pila de llamadas. Este componente recibe el nombre de registro de activación, y que podemos entender como un espacio en la memoria dedicado donde se almacenará información y también metadatos (argumentos de la función invocada si los lleva, variables locales más sus valores correspondientes, el punto dentro del flujo de ejecución del script desde donde fue llamada la función, etc.).
Cuando, sin salir de la función activa actual llamamos a otra, bien sea como función recursiva invocándose a sí misma, o bien anidada a distintos niveles (como veremos en el ejemplo final), o bien desde una función main(), toda la información que mencionamos arriba se almacena en su correspondiente registro de activación en la pila. Si efectuamos una nueva llamada a la función, se apilará un nuevo registro de activación, el que corresponde a esta nueva llamada en concreto, que se apilará sobre el anterior, y así sucesivamente.
Pero eso sí: en cuanto la función invocada acaba de ejecutar su tarea, su registro de activación (su espacio de memoria local asignado) se vacía de contenido. Tengamos en cuenta que este vaciado se produce en la parte superior de la pila, en su cima (TOS) para ser exactos lo que, subsecuentemente, nos devuelve el control de la función que efectuó la llamada, dejándonos sus datos.
Lo vemos con un ejemplo de código:
Finalizamos aquí este largo capítulo dedicado a las pilas y colas y os proponemos echar un vistazo, aprovechando el "viento de cola", a una nueva entrada: T4. CLOSURES: LA SONRISA DE LAS MATRIUSKAS, dedicada a las funciones anidadas.
CAUCE DEL BARRANCO DE CHAMORGA TRAS UN AGUACERO DE INVIERNO, MACIZO DE ANAGA, NORESTE DE TENERIFE. |
gracias por la información :) esta divertida :V
ResponderEliminarMuchas gracias. Si además de aprender cosas nuevas lo hacemos con alguna sonrisa, mucho mejor. Saludos.
Eliminarexcelente
ResponderEliminarMuchas gracias, Robert Roy. Esperamos haber servido de ayuda. Saludos.
EliminarMuchas gracias por el contenido, muy informativo y divertido!
ResponderEliminarMuchísimas gracias, Gonz. Un placer contribuir a que todos mejoremos nuestras competencias en Python. Y si además te resulta divertido, pues miel sobre hojuelas. Es lo que se pretende, además de compartir conocimientos: que no se nos haga tan aburrido. Saludos y adelante.
ResponderEliminarMe he quedado entusiasmado, es leer y entenderlo. Mis felicitaciones, eres un muy didáctico.
ResponderEliminarMuchísimas gracias por tu comentario, Rolandoors. Como siempre, un placer auténtico haber servido de ayuda. Saludos.
Eliminar