Programar Juegos Arcade
con Python y Pygame

Chapter 19: Recursividad

La pequeña niña no podía dormir, entonces, su mamá le contó la historia de una ranita,
  que no podía dormir, entonces, la mamá de la ranita le contó la historia de un osito,
    que no podía dormir, entonces, la mamá del osito le contó la historia de una pequeña comadreja...
      que se durmió.
    ...y el pequeño osito se durmió;
  ...y la ranita se durmió;
...y la niña se durmió.
(Fuente: http://everything2.com/title/recursion)

La recursividad es un objeto o proceso que se define en términos de sí mismo. Ciertos patrones matemáticos como los factoriales y la sucesión de Fibonacci son recursivos. Documentos que contienen documentos que a su vez contienen otros documentos, son recursivos. Las imágenes fractales y ciertos procesos biológicos son recursivos en su forma de producirse.

19.1 ¿Dónde Utilizamos la Recursividad?

Ciertos documentos, como las páginas webs, son en esencia recursivos. Por ejemplo, la Figura 19.1 nos muestra un documento web.

fig.webpage1
Figure 19.1: Página Web

Ese documento puede estar dentro de una “caja,” la cual ayuda a estructurar el contenido de la página, tal como se ve en la Figura 19.2.

fig.webpage2
Figure 19.2: Página Web con tablas

Esto funciona recursivamente. Cada caja contiene una página web, que puede tener una caja, la cual podría contener otra página web, tal como vemos en la Figura 19.3.

fig.webpage3
Figure 19.3: Página Web con recursividad

A menudo empleamos funciones recursivas para búsquedas avanzadas y algoritmos de ordenamiento. Veremos algunas de ellas aquí, y si en algún momento sigues un curso sobre “estructura de datos”, verás seguramente muchas más.

Aún en el caso de que una persona no se convierta en programadora, comprender el concepto de los sistemas recursivos es importante. Si en un determinado proyecto surgiera la necesidad de trabajar con tablas, documentos, o cosas por el estilo, recursivos, es importante saber cómo explicar esto a la persona que vaya a encargarse de la programación.

Supongamos, por ejemplo, que alguien quisiera que su web de recetas tuviese la capacidad de mantener una lista con ingredientes e instrucciones. Ahora, imaginemos una segunda persona familiarizada con la recursividad, la cual podría plantear que, en lugar de hacer listas, tengamos la capacidad de combinar ingredientes, con lo que obtendríamos recetas. Este segundo sistema es evidentemente más efectivo.

19.2 ¿De Qué Forma Codificamos la Recursividad?

En anteriores capítulos hemos usado funciones que llamaban a otras funciones. Por ejemplo:

def f():
    g()
    print("f")

def g():
    print("g")

f()

También es posible que una función se llame a sí misma. Es decir, haciendo uso de un concepto llamado recursividad. Por ejemplo:

def f():
    print("Hola")
    f()

f()

El ejemplo anterior imprimirá Hola y luego llamará otra vez a la función f(). Esto provocará que se imprima otro Hola y que vuelva a llamarse a f(). Esto seguirá haciéndose hasta que el ordenador se quede sin algo llamado stack space (espacio de pila). Cuando sucede esto, Python mostrará un largo error que finaliza con:
RuntimeError: maximum recursion depth exceeded

El ordenador te está diciendo, a ti programador, que has ido demasiado lejos en la madriguera.

19.3 Controlar la Profundidad de la Recursividad

Para usar eficientemente la recursividad, tiene que existir una forma que evite que la función se llame a sí misma una y otra vez. El siguiente ejemplo cuenta las veces se ha llamado, y usa una declaración if para salir cuando la función se llama a sí misma diez veces.

def f(nivel):
    # Imprime el nivel en el que nos encontramos
    print("Llamada de recursividad, nivel",nivel)
    # Si aún no hemos llegado al nivel ten...
    if nivel < 10:
        # Llama otra vez a la función
        # y suma uno al nivel
        f(nivel+1)

# Comienza las llamadas recursivas en el nivel 1
f(1)
Output:
Llamada de recursividad, nivel 1
Llamada de recursividad, nivel 2
Llamada de recursividad, nivel 3
Llamada de recursividad, nivel 4
Llamada de recursividad, nivel 5
Llamada de recursividad, nivel 6
Llamada de recursividad, nivel 7
Llamada de recursividad, nivel 8
Llamada de recursividad, nivel 9
Llamada de recursividad, nivel 10

19.4 Cálculo Factorial Usando Recursividad

Cualquier código que pueda hacerse con recursividad, también lo puede hacer sin ella. Algunos programadores piensan que el código recursivo es más fácil de entender.

Calcular el factorial de un número es un ejemplo clásico del uso de la recursividad. Los factoriales son muy útiles en probabilidad y estadística. Por ejemplo:
$10! = 10 \cdot 9 \cdot 8 \cdot 7 \cdot 6 \cdot 5 \cdot 4 \cdot 3 \cdot 2 \cdot 1$

Recursivamente puede describirse así:
$n! = \begin{cases} 1 & \text{if } n = 0, \\ n \cdot (n-1)! & \text{if } n > 0. \end{cases}$

Aquí debajo hay dos ejemplos de funciones que calculan $n!$. La primera no emplea la recursividad, la segunda sí.

# Este programa calcula el factorial de un número
# SIN emplear recursividad
def factorial_norecursivo(n):
    respuesta = 1
    for i in range(2, n+1):
        respuesta = respuesta * i
    return respuesta
# Este programa calcula el factorial de un número
# USANDO recursividad
def factorial_recursivo(n):
    if(n == 1):
        return n
    else:
        return n * factorial_recursivo(n-1)

Por sí solas estas funciones no hacen nada. Debajo hay un ejemplo donde unimos todo. En él hemos añadido algunas sentencias print a la función, lo que nos permitirá ver que está sucediendo.

# Este programa calcula el factorial de un número
# SIN emplear recursividad

def factorial_norecursivo(n):
    respuesta = 1
    for i in range(2, n+1):
        print(i,"*",respuesta,"=", i*respuesta)
        respuesta = respuesta * i
    return respuesta

print("Puedo calcular un factorial!")
entrada_usuario = input ("Introduce un número: ")
n = int(entrada_usuario)
respuesta = factorial_norecursivo(n)
print(respuesta)

# Este programa calcula el factorial de un número
# USANDO recursividad

def factorial_recursivo(n):
    if( n == 1 ):
        return n
    else:
        x = factorial_recursivo(n-1)
        print( n, "*", x, "=", n * x )
        return n * x

print("Puedo calcular un factorial!")
entrada_usuario = input ("Introduce un número:")
n = int(entrada_usuario)
respuesta = factorial_recursivo(n)
print(respuesta)
Output:
Puedo calcular un factorial!
Introduce un número:7
2 * 1 = 2
3 * 2 = 6
4 * 6 = 24
5 * 24 = 120
6 * 120 = 720
7 * 720 = 5040
5040
Puedo calcular un factorial!
Introduce un número:7
2 * 1 = 2
3 * 2 = 6
4 * 6 = 24
5 * 24 = 120
6 * 120 = 720
7 * 720 = 5040
5040

19.5 Rectángulos Recursivos

La recursividad es fantástica para trabajar con documentos estructurados que a su vez son recursivos. Por ejemplo, un documento web que tiene una tabla dividida en filas y columnas para ayudar en el diseño. Una fila podría ser el encabezado, otra el cuerpo principal, y finalmente, otra para el pie. Dentro de una de las celdas podría ir otra tabla. Y, dentro de ésta, una tabla más.

Otro ejemplo son los e-mail. Es posible adjuntar el e-mail de otra persona al tuyo. Pero este e-mail podría tener otro e-mail adjunto, y así sucesivamente.

¿Podemos observar en acción la recursividad en alguno de nuestros programas Pygame? Sí! La Figura 19.4muestra un ejemplo que dibuja un rectángulo, y recursivamente, continúa dibujando rectángulos dentro de éste. Cada nuevo rectángulo es un 20% más pequeño que el anterior. Observa el código. Presta especial atención a llamada de recursividad en la función recursive_draw.

fig.recursive_rectangles
Figure 19.4: Rectángulos Recursivos
""" 
 Dibujaremos rectángulos de forma recursiva.
 
 Sample Python/Pygame Programs
 Simpson College Computer Science
 http://programarcadegames.com/
 http://simpson.edu/computer-science/

"""
import pygame
 
# Definimos algunos colores
NEGRO = (0, 0, 0)
BLANCO = (255, 255, 255)
 
def dibujo_recursivo(x, y, largo, alto):
    """ Función para dibujar recursivamente rectángulos. """
    pygame.draw.rect(pantalla, NEGRO, 
                     [x,y,largo,alto],
                     1)

    # Es el rectángulo lo bastante largo como para volver a dibujarlo?
    if(largo > 14):
        # Reducimos
        x += largo * .1
        y += alto * .1
        largo *= .8
        alto *= .8
        # Lo dibujamos otra vez recursivamente
        dibujo_recursivo(x, y, largo, alto)
    
pygame.init()
  
# Establecemos el alto y largo de la pantalla
dimensiones = [700, 500]
pantalla = pygame.display.set_mode(dimensiones)
 
pygame.display.set_caption("Mi Juego")
 
# Iteramos hasta que el usuario haga click sobre el botón de cerrar
hecho = False
 
# Usado para gestionar cuán rápido se actualiza la pantalla
reloj = pygame.time.Clock()
 
# -------- Bucle Principal del Programa  -----------
while not hecho:
    for evento in pygame.event.get(): 
        if evento.type == pygame.QUIT:
            hecho = True  
            
    # Limpia la pantalla y establece su color de fondo
    pantalla.fill(BLANCO)
 
    # TODO EL CÓDIGO DE DIBUJO DEBERÍA IR DEBAJO DE ESTE COMENTARIO
    dibujo_recursivo(0, 0, 700, 500)
    # TODO EL CÓDIGO DE DIBUJO DEBERÍA IR ENCIMA DE ESTE COMENTARIO
     
    # # Avancemos y actualicemos la pantalla con lo que hemos dibujado.
    pygame.display.flip()

    # Limitamos a 60 fotogramas por segundo
    reloj.tick(60)
     
# Pórtate bien con el IDLE. Si nos olvidamos de esta línea, el programa se 'colgará'
# en la salida.
pygame.quit()

19.6 Fractales

Los fractales son definidos recursivamente. Aquí puedes observar un fractal muy básico, que nos muestra cómo cambia, en función de cuan “profunda” sea la recursividad.

fig.fractal_0
Figure 19.5: Fractal Recursivo nivel 0
fig.fractal_1
Figure 19.6: Fractal Recursivo nivel 1
fig.fractal_2
Figure 19.7: Fractal Recursivo nivel 2
fig.fractal_3
Figure 19.8: Fractal Recursivo nivel 3
""" 
 Ejemplo de Fractales usando recursividad
 
 Sample Python/Pygame Programs
 Simpson College Computer Science
 http://programarcadegames.com/
 http://simpson.edu/computer-science/
""" 

import pygame

# Definimos algunos colores
NEGRO = (0, 0, 0)
BLANCO = (255, 255, 255)

 
def dibujo_recursivo(x, y, largo, alto, cuenta):
    # Dibujamos el rectángulo
    # pygame.draw.rect(pantalla,NEGRO,[x,y,largo,alto],1)
    pygame.draw.line(pantalla,
                     NEGRO,
                     [x + largo*.25,alto//2+y],
                     [x + largo*.75,alto//2+y],
                     3)
    pygame.draw.line(pantalla,
                     NEGRO,
                     [x+largo*.25,(alto*.5)//2+y],
                     [x+largo*.25,(alto*1.5)//2+y],
                     3)
    pygame.draw.line(pantalla,
                     NEGRO,
                     [x + largo*.75,(alto*.5)//2+y],
                     [x + largo*.75,(alto*1.5)//2+y],
                     3)

    if cuenta > 0:
        cuenta -= 1
        # Arriba izquierda
        dibujo_recursivo(x, y, largo // 2, alto // 2, cuenta)
        # Arriba derecha
        dibujo_recursivo(x + largo // 2, y, largo // 2, alto // 2, cuenta)
        # Abajo izquierda
        dibujo_recursivo(x, y + largo // 2, largo // 2, alto // 2, cuenta)
        # Abajo derecha
        dibujo_recursivo(x + largo // 2, y + largo // 2, largo // 2, alto // 2, cuenta)
    
    
pygame.init()

# Establecemos el alto y largo de la pantalla
dimensiones = [700, 500]
pantalla = pygame.display.set_mode(dimensiones)
 
pygame.display.set_caption("Mi Juego")
 
#Iteramos hasta que el usuario haga click sobre el botón de cerrar
hecho = False
 
# Usado para gestionar cuán rápido se actualiza la pantalla
reloj = pygame.time.Clock()

# -------- Bucle Principal del Programa  -----------
while not hecho:
    for evento in pygame.event.get():  # El usuario hizo algo
        if evento.type == pygame.QUIT: # Si el usuario hace click sobre cerrar
            hecho = True               # Marca que ya lo hemos hecho, de forma que abandonamos el bucle
            
            
    # Limpia la pantalla y establece su color de fondo
    pantalla.fill(BLANCO)
 
    # TODO EL CÓDIGO DE DIBUJO DEBERÍA IR DEBAJO DE ESTE COMENTARIO
    nivel_fractal = 3
    dibujo_recursivo(0, 0, 700, 700, nivel_fractal)
    # TODO EL CÓDIGO DE DIBUJO DEBERÍA IR ENCIMA DE ESTE COMENTARIO
     
    # # Avancemos y actualicemos la pantalla con lo que hemos dibujado.
    pygame.display.flip()

    # Limitamos a 20 fotogramas por segundo
    reloj.tick(20)
     
# Pórtate bien con el IDLE. Si nos olvidamos de esta línea, el programa se 'colgará'
# en la salida.
pygame.quit()

19.7 Búsqueda Binaria Recursiva

También podemos usar la recursividad para hacer búsquedas binarias. Aquí debajo hay una búsqueda binaria no recursiva del Capítulo 17:

def busqueda_binaria_norecursiva(lista_busqueda, clave):
    limite_inferior = 0
    limite_superior = len(lista_busqueda) - 1
    encontrado = False
    while limite_inferior < limite_superior and encontrado == False:
        pos_intermedia = (limite_inferior + limite_superior) // 2
        if lista_busqueda[pos_intermedia] < clave:
            limite_inferior = pos_intermedia + 1
        elif list[pos_intermedia] > clave:
            limite_superior = pos_intermedia
        else:
            encontrado = True

    if encontrado:
        print("El nombre se encuentra en la posición",pos_intermedia)
    else:
        print("El nombre no estaba en la lista.")

busqueda_binaria_norecursiva(nombre_en_lista,"Morgiana la Arpía")

La misma búsqueda binaria pero ahora usando recursividad:

def busqueda_binaria_recursiva(lista_busqueda,clave, limite_inferior, limite_superior):
    pos_intermedia = (limite_inferior + limite_superior) // 2
    if lista_busqueda[pos_intermedia] < clave:
        busqueda_binaria_recursiva(lista_busqueda,
                                clave,
                                pos_intermedia + 1,
                                limite_superior)
    elif lista_busqueda[pos_intermedia] > clave:
        busqueda_binaria_recursiva(lista_busqueda,
                                clave,
                                limite_inferior,
                                pos_intermedia )
    else:
        print("Encontrado en la posición", pos_intermedia)

limite_inferior = 0
limite_superior = len(nombre_en_lista) - 1
busqueda_binaria_recursiva(nombre_en_lista,
                        "Morgiana la Arpía",
                        limite_inferior,
                        limite_superior)

19.8 Repaso

19.8.1 Test

Haz click para ir a los Ejercicios.


You are not logged in. Log in here and track your progress.