Categorías
Python

Proyecto de Python para principiantes: Bitcoin Precio Notificaciones

 

Tabla de Contenidos

  • Estimado Pythonic Santa Claus … Funciones
  • recursivas en Python
  • mantener estructuras de Estado
  • recursiva de datos en Python
  • Naive La recursividad es ingenuo
  • molestos detalles
  • Fin
  • Referencias

reloj ahora Este tutorial tiene un curso de vídeo relacionado creado por el equipo del real Python. Mira que junto con el tutorial escrito para profundizar su comprensión: Pensando de forma recursiva en Python

“De todas las ideas que he introducido a los niños, la recursividad se destaca como la idea de que es particularmente capaz de provocar una respuesta emocionados”

Seymour Papert, Mindstorms

problemas (en la vida y también en la informática) a menudo pueden parecer grande y aterrador. Pero si seguimos saltando lejos en ellos, más a menudo que no podemos romperlos en trozos más pequeños lo suficientemente trivial para resolver. Esta es la esencia de pensar de forma recursiva, y mi objetivo en este artículo es proporcionar usted, mi querido lector, con las herramientas conceptuales necesarias para abordar los problemas desde este punto de vista recursiva.

Juntos, vamos a aprender a trabajar con la recursividad en nuestros programas de Python por el dominio de conceptos tales como funciones recursivas y estructuras de datos recursivas. También hablaremos de mantener el estado durante la recursividad y evitar el recálculo almacenamiento en caché de resultados. Esto va a ser muy divertido. ¡Adelante y hacia arriba!

Estimado Pythonic Santa Claus …

Soy consciente de que como compañero de Pythonistas estamos todos los adultos que consienten aquí, pero los niños parecen asimilar la belleza de la recursividad mejor. Así que vamos a no ser adultos aquí por un momento y hablar sobre cómo podemos utilizar la recursividad para ayudar a Santa Claus.

alguna vez se preguntó cómo se entregan los regalos de Navidad? Claro que tengo, y creo que Santa Claus tiene una lista de casas que recorre. Él va a una casa, cae fuera de los presentes, se come las galletas y la leche, y se mueve a la siguiente casa en la lista. Dado que este algoritmo para la entrega de regalos se basa en una construcción de bucle explícita, se llama un algoritmo iterativo.

El algoritmo para la presente entrega iterativo implementado en Python:

houses = ["Eric's house", "Kenny's house", "Kyle's house", "Stan's house"]

def deliver_presents_iteratively():
for house in houses:
print("Delivering presents to", house)
>>> deliver_presents_iteratively()
Delivering presents to Eric's house
Delivering presents to Kenny's house
Delivering presents to Kyle's house
Delivering presents to Stan's house

pero me siento para Santa. A su edad, no debería tener que entregar todos los regalos por él mismo. Propongo un algoritmo con el que se puede dividir el trabajo de entrega de regalos entre sus duendes:

  • > 1 Él es un director y puede nombrar dos elfos y dividir su trabajo entre ellos
  • = 1 Es un trabajador y tiene que ofrecer los regalos a la casa asignada a él

Esta es la estructura típica de un algoritmo recursivo. Si el problema actual representa un caso simple, resolverlo. Si no es así, dividirlo en subproblemas y aplicar la misma estrategia a ellos.

El algoritmo recursivo para la entrega presente implementado en Python: Funciones

houses = ["Eric's house", "Kenny's house", "Kyle's house", "Stan's house"]

# Each function call represents an elf doing his work
def deliver_presents_recursively(houses):
# Worker elf doing his work
if len(houses) == 1:
house = houses[0]
print("Delivering presents to", house)

# Manager elf doing his work
else:
mid = len(houses) // 2
first_half = houses[:mid]
second_half = houses[mid:]

# Divides his work among two elves
deliver_presents_recursively(first_half)
deliver_presents_recursively(second_half)
>>> deliver_presents_recursively(houses)
Delivering presents to Eric's house
Delivering presents to Kenny's house
Delivering presents to Kyle's house
Delivering presents to Stan's house

recursivas en Python

Ahora que tenemos un poco de intuición acerca de la recursividad, vamos a introducir la definición formal de una función recursiva. Una función recursiva es una función definida en términos de sí mismo a través de expresiones auto-referenciales.

Esto significa que la función continuará llamando a sí mismo y repetir su comportamiento hasta que se cumpla alguna condición para devolver un resultado. Todas las funciones recursivas comparten una estructura común formada por dos partes: caso base y caso recursivo.

Para demostrar esta estructura, vamos a escribir una función recursiva para calcular n !:

descomponer el problema original en los casos más sencillos de un mismo problema. Este es el caso recursivo:

n! = n x (n−1) x (n−2) x (n−3) ⋅⋅⋅⋅ x 3 x 2 x 1
n! = n x (n−1)!

A medida que el gran problema se descompone en los sucesivamente menos complejos, esos subproblemas deben eventualmente se vuelven tan simples que se pueden resolver sin una mayor subdivisión. Este es el caso base:

n! = n x (n−1)!
n! = n x (n−1) x (n−2)!
n! = n x (n−1) x (n−2) x (n−3)!


n! = n x (n−1) x (n−2) x (n−3) ⋅⋅⋅⋅ x 3!
n! = n x (n−1) x (n−2) x (n−3) ⋅⋅⋅⋅ x 3 x 2!
n! = n x (n−1) x (n−2) x (n−3) ⋅⋅⋅⋅ x 3 x 2 x 1!

Aquí, 1! es nuestro caso base, y es igual a 1. Función

recursiva para calcular n! implementado en Python:

def factorial_recursive(n):
# Base case: 1! = 1
if n == 1:
return 1

# Recursive case: n! = n * (n-1)!
else:
return n * factorial_recursive(n-1)
>>> factorial_recursive(5)
120

Detrás de las escenas, cada llamada recursiva agrega un marco de pila (que contiene su contexto de ejecución) a la pila de llamadas hasta llegar al caso base. A continuación, la pila empieza a relajarse, ya que cada llamada devuelve sus resultados:

Manteniendo el estado

Cuando se trata de las funciones recursivas, tenga en cuenta que cada llamada recursiva tiene su propio contexto de ejecución, por lo que para mantener el estado durante la recursividad tiene ya sea a:

  • Pase el estado a través de cada llamada recursiva para que el estado actual es parte del contexto de ejecución de la llamada actual
  • Mantener el estado en el ámbito global

Una demostración debe hacer las cosas más claras. Vamos a calcular 1 + 2 + 3 + 10 ⋅⋅⋅⋅ utilizando la recursividad. El estado que tenemos que mantener es (número actual estamos añadiendo, suma acumulada hasta ahora) .

Así es como se hace eso por roscado a través de cada llamada recursiva (es decir, pasando el actual estado actualizado para cada llamada recursiva como argumentos):

def sum_recursive(current_number, accumulated_sum):
# Base case
# Return the final state
if current_number == 11:
return accumulated_sum

# Recursive case
# Thread the state through the recursive call
else:
return sum_recursive(current_number + 1, accumulated_sum + current_number)
# Pass the initial state
>>> sum_recursive(1, 0)
55

Así es como se mantiene el estado manteniéndolo en el ámbito global:

# Global mutable state
current_number = 1
accumulated_sum = 0

def sum_recursive():
global current_number
global accumulated_sum
# Base case
if current_number == 11:
return accumulated_sum
# Recursive case
else:
accumulated_sum = accumulated_sum + current_number
current_number = current_number + 1
return sum_recursive()
>>> sum_recursive()
55

prefiero enhebrar el estado a través de cada llamada recursiva porque encuentro estado mutable mundial a ser malo, pero eso es una discusión para otro momento. Estructuras

recursiva de datos en la estructura de datos de Python

A es recursivo si puede ser definido en términos de una versión más pequeña de sí mismo. Una lista es un ejemplo de una estructura de datos recursiva. Permítanme demostrar. Suponga que tiene solamente una lista vacía a su disposición, y la única operación que se puede realizar en la misma es la siguiente:

# Return a new list that is the result of
# adding element to the head (i.e. front) of input_list
def attach_head(element, input_list):
return [element] + input_list

Uso de la lista vacía y la operación attach_head, puede generar cualquier lista. Por ejemplo, vamos a generar [1, 46, -31, «hola»]:

attach_head(1, # Will return [1, 46, -31, "hello"]
attach_head(46, # Will return [46, -31, "hello"]
attach_head(-31, # Will return [-31, "hello"]
attach_head("hello", [])))) # Will return ["hello"]
[1, 46, -31, 'hello']

A partir de una lista vacía, se puede generar cualquier lista mediante la aplicación recursiva de la función attach_head, y por lo tanto la estructura de datos de lista pueden definirse de forma recursiva como:

+---- attach_head(element, smaller list)
list = +
+---- empty list

recursión también puede ser visto como composición de la función auto-referencial. Aplicamos una función a un argumento, a continuación, pasar a ese resultado en como argumento para una segunda aplicación de la misma función, y así sucesivamente. Repetidamente componer attach_head consigo mismo es el mismo que attach_head hace llamar repetidamente.

Lista

no es la estructura de datos única recursiva. Otros ejemplos incluyen la serie, árbol, diccionario, etc. estructuras de datos recursivas

y funciones recursivas van juntos como el pan y la mantequilla. La estructura de la función recursiva a menudo puede ser modelada después de la definición de la estructura de datos recursiva que toma como entrada. Permítanme demostrar esto calculando la suma de todos los elementos de una lista recursiva:

def list_sum_recursive(input_list):
# Base case
if input_list == []:
return 0

# Recursive case
# Decompose the original problem into simpler instances of the same problem
# by making use of the fact that the input is a recursive data structure
# and can be defined in terms of a smaller version of itself
else:
head = input_list[0]
smaller_list = input_list[1:]
return head + list_sum_recursive(smaller_list)
>>> list_sum_recursive([1, 2, 3])
6

recursividad Naive es un número

El Fibonacci Naive fueron originalmente definida por el Fibonacci matemático italiano en el siglo XIII para modelar el crecimiento de las poblaciones de conejo. Fibonacci conjeturó que el número de pares de conejos nacidos en un año dado es igual al número de pares de conejos nacidos en cada uno de los dos años anteriores, a partir de un par de conejos en el primer año.

para contar el número de conejos nacidos en el año enésimo, que de fi ne la relación de recurrencia:

Fn = Fn-1 + Fn-2

Los casos base son: escritura de

F0 = 0 and F1 = 1

Let una función recursiva para calcular el enésimo número de Fibonacci:

def fibonacci_recursive(n):
print("Calculating F", "(", n, ")", sep="", end=", ")

# Base case
if n == 0:
return 0
elif n == 1:
return 1

# Recursive case
else:
return fibonacci_recursive(n-1) + fibonacci_recursive(n-2)
>>> fibonacci_recursive(5)
Calculating F(5), Calculating F(4), Calculating F(3), Calculating F(2), Calculating F(1),
Calculating F(0), Calculating F(1), Calculating F(2), Calculating F(1), Calculating F(0),
Calculating F(3), Calculating F(2), Calculating F(1), Calculating F(0), Calculating F(1),

5

Ingenuamente después de la recursivas definición del enésimo número de Fibonacci era bastante ineficiente. Como se puede ver en la salida anterior, estamos recalcular los valores innecesariamente. Vamos a tratar de mejorar fibonacci_recursive almacenamiento en caché de los resultados de cada cálculo de Fibonacci Fk:

from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci_recursive(n):
print("Calculating F", "(", n, ")", sep="", end=", ")

# Base case
if n == 0:
return 0
elif n == 1:
return 1

# Recursive case
else:
return fibonacci_recursive(n-1) + fibonacci_recursive(n-2)
>>> fibonacci_recursive(5)
Calculating F(5), Calculating F(4), Calculating F(3), Calculating F(2), Calculating F(1), Calculating F(0),

5

lru_cache es un decorador que almacena en caché los resultados. De esta manera, evitamos recálculo comprobando de forma explícita por el valor antes de intentar calcularlo. Una cosa a tener en cuenta sobre lru_cache es que ya que utiliza un diccionario para resultados de caché, los argumentos posicionales y de palabras clave (que sirven como claves en ese diccionario) a la función debe ser hashable.

molestos detalles

Python no tiene soporte para la eliminación de llamada final. Como resultado, puede causar un desbordamiento de pila si al final el uso de marcos de pila más que la profundidad de la pila de llamadas predeterminado:

>>> import sys
>>> sys.getrecursionlimit()
3000

Tenga en cuenta esta limitación si tiene un programa que requiere la repetición de profundidad.

Además, las estructuras de datos mutables de Python no admiten el intercambio estructural, por lo que tratarlos como estructuras de datos inmutables va a afectar negativamente a su espacio y GC (recolección de basura) la eficiencia, ya que van a terminar innecesariamente copiar una gran cantidad de objetos mutables . Por ejemplo, he utilizado este modelo para descomponer listas y recursivo sobre ellos:

>>> input_list = [1, 2, 3]
>>> head = input_list[0]
>>> tail = input_list[1:]
>>> print("head --", head)
head -- 1
>>> print("tail --", tail)
tail -- [2, 3]

lo hice para simplificar las cosas por el bien de la claridad. Tenga en cuenta que la cola se crea copiando. Recursiva haciendo que más grandes listas pueden afectar negativamente a su espacio y la eficiencia GC.

Fin

Una vez me preguntaron para explicar la recursividad en una entrevista. Tomé una hoja de papel y escribí favor vuelca en ambos lados. El entrevistador no tuvo la broma, pero ahora que ha leído este artículo, esperamos que pueda hacer feliz Pythoning!

Referencias

Mira ahora Este tutorial tiene un vídeo relacionado curso creado por el equipo del Real Python. Mira que junto con el tutorial escrito para profundizar su comprensión: Pensamiento de forma recursiva en Python

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *