Header Ads Widget

Ticker

6/recent/ticker-posts

Cómo escribir un bucle en Python: while y for

 A medida que esta serie crece, a menudo me encuentro revisando los fundamentos. Por ejemplo, hoy aprenderemos a escribir un bucle en Python. Afortunadamente para ti, también hay material adicional sobre la recursividad.

En resumen, hay dos formas principales de escribir un bucle whileforSi está buscando un bucle tradicional, opte por el whilebucle. Mientras tanto, si tiene alguna secuencia o iterable para atravesar, opte por el forbucle. Si encuentra un escenario que se vuelve complicado con un bucle (por ejemplo, un recorrido de árbol), no tenga miedo de recurrir a la recursividad.

Descripción del problema

Cuando te inicias en la programación, a menudo pasas por una progresión de diferentes piezas de sintaxis. Por ejemplo, puede aprender sobre la impresión y las variables. Luego, puede expandir su conocimiento a expresiones aritméticas y booleanas. Si todo va bien, es posible que incluso aprenda sobre los condicionales.

A medida que pasa el tiempo, es posible que se pregunte "pero, ¿y si quiero hacer algo repetidamente?" Afortunadamente, la mayoría de los lenguajes de programación imperativos tienen una sintaxis para esto llamada bucle. Esencialmente, repetimos una tarea hasta que satisfacemos alguna condición.

Por supuesto, si viene de otro lenguaje de programación, ya sabe todo sobre los bucles (o al menos la recursividad). El problema es acostumbrarse a la nueva sintaxis. Afortunadamente, tenemos varias soluciones diferentes que veremos en la siguiente sección.

Soluciones

En esta sección, veremos tres formas diferentes de escribir un bucle en Python. Primero, veremos la recursividad, una técnica funcional. Luego, nos sumergiremos en las dos técnicas iterativas whilefor.

Recursividad

Antes de profundizar en la sintaxis de varios bucles en Python, creo que es importante mencionar la recursividad como concepto. Después de todo, en realidad no necesitamos bucles. Podemos alejarnos de escribir funciones que hacen referencia a sí mismas:

1
2
def recurse():
    recurse()

En este ejemplo, hemos escrito una función llamada recurse()que se llama a sí misma. Sin embargo, si lo ejecutamos, obtendremos un error:

01
02
03
04
05
06
07
08
09
10
11
12
>>> recurse()
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    recurse()
  File "<pyshell#1>", line 2, in recurse
    recurse()
  File "<pyshell#1>", line 2, in recurse
    recurse()
  File "<pyshell#1>", line 2, in recurse
    recurse()
  [Previous line repeated 991 more times]
RecursionError: maximum recursion depth exceeded

Por supuesto, esto tiene sentido. Después de todo, si una función se llama a sí misma, entonces se llamará a sí misma, luego se llamará a sí misma, luego se llamará a sí misma… bien, mi cabeza da vueltas.

Afortunadamente, esto es bastante fácil de solucionar. Solo necesitamos agregar una condición que solo llame a la función bajo ciertas condiciones (por ejemplo, mientras la entrada es mayor que cero):

1
2
3
def recurse(i):
    if i > 0:
        recurse(i - 1)

Ahora, si podemos esta función con algún número, no fallaremos:

1
>>> recurse(5)

Pero, ¿qué está haciendo esto realmente? Bueno, intentemos imprimir algo:

1
2
3
4
def recurse(i):
    print(f'Input is {i}')
    if i > 0:
        recurse(i - 1)

Aquí, usamos un f-string (aprenda más sobre ellos aquí ) para mostrar la entrada cada vez que se llama a esta función:

1
2
3
4
5
6
7
>>> recurse(5)
Input is 5
Input is 4
Input is 3
Input is 2
Input is 1
Input is 0

¡Mira eso! Logramos crear una función que se ejecuta 6 veces cuando ingresamos un 5. Como probablemente puedas imaginar, este mecanismo puede usarse para hacer muchas cosas interesantes. Si está interesado en aprender más sobre la recursividad, escribí un artículo al respecto .

Mientras bucle

Con la recursividad fuera del camino, hablemos de bucles. En Python, hay dos mecanismos principales de bucle: whileforPor lo general, los cursos cubren whileprimero porque es más simple. Si está familiarizado con las declaraciones if, un whilebucle se ve casi exactamente igual:

1
2
while condition:
    do_thing()

Si la condición es verdadera, el cuerpo del bucle se ejecuta como una instrucción if. Sin embargo, después de que el cuerpo se ejecuta, se vuelve a verificar la condición. Si la condición sigue siendo cierta, volvemos a entrar en el cuerpo del bucle.

Naturalmente, podemos escribir un bucle que se comporte de manera similar a nuestro ejemplo de recursividad. Todo lo que tenemos que hacer es crear una variable de contador y contar hacia atrás en cada iteración:

1
2
3
4
i = 5
while i >= 0:
    print(f'Input is {i}')
    i -= 1

En este ejemplo, creamos una variable llamada iy le damos un valor de 5. Luego, iniciamos el ciclo comprobando si ies mayor o igual que 0. Dado que lo es, caemos en el ciclo donde imprimimos “La entrada es 5 ”y decremento iLuego, el proceso se repite. Por supuesto, ahora ies 4 en lugar de 5. El tiempo total idisminuirá hasta que sea -1 y la condición de bucle fallará.

En Python, whilese puede utilizar para implementar cualquier bucle indefinido. En otras palabras, use un whilebucle cuando no sepa cuántas iteraciones tendrá de antemano. Por ejemplo, los whilebucles son perfectos para leer archivos o solicitar información de un usuario. En la siguiente sección, veremos un ejemplo de un bucle definido.

En bucle

En muchos lenguajes imperativos como Java, C y Python, hay más de una forma de escribir un bucle. Por ejemplo, en Java, hay al menos cuatro sintaxis de bucle diferentes que yo sepa (por ejemplo whileforfor eachdo while). Dado que Python intenta mantener las cosas simples, la cantidad de sintaxis de bucle es limitada. Hasta donde yo sé, solo hay dos: forwhile.

Ahora, los forbucles en Python no son como los forbucles en otros lenguajes. En lugar de proporcionar un espacio para rastrear un índice, operan más como for eachbucles en otros idiomas. En otras palabras, necesitamos algo sobre lo que iterar como una lista. Intentemos recrear nuestro whilebucle desde arriba:

1
2
3
indices = [5, 4, 3, 2, 1, 0]
for i in indices:
    print(f'Input is {i}')

Para que este ciclo funcione, tuvimos que crear una lista para iterar. Claramente, esto no es tan conveniente como la solución anterior. Afortunadamente, Python tiene una forma de generar este tipo de iterables:

1
2
for i in range(5, -1, -1):
    print(f'Input is {i}')

Aquí, hemos creado un ciclo que contará hacia atrás de 5 a 0 al igual que todos nuestros otros ciclos. Para hacer eso, usamos la range()función que genera una estructura similar a una lista a partir de las entradas proporcionadas. En este caso, 5 representa el valor inicial inclusivo, el primer -1 representa el valor final exclusivo y el segundo -1 representa el paso (es decir, cuántos valores omitir y en qué dirección).

En general, los forbucles son más útiles para iterar sobre secuencias como listas, cadenas o generadores. En otras palabras, no funcionan exactamente como los forbucles en otros idiomas, no sin utilizar una función especial como range().

Actuación

En este punto, no estoy seguro de que tenga sentido comparar el desempeño de estos tres constructos, pero ya escribí tres soluciones que hacen lo mismo. En otras palabras, solo estamos pidiendo una comparación. Para comenzar, almacenemos nuestras tres soluciones en cadenas:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
dieciséis
17
18
19
20
21
22
setup = """
i = 5
def recurse(i):
    # Removed print for sanity
    if i > 0:
        recurse(i - 1)
"""
 
recursion = """
recurse(5)
"""
 
while_loop = """
while i >= 0:
    # Removed print for sanity
    i -= 1
"""
 
for_loop = """
for i in range(5, -1, -1):
    pass  # Removed print for sanity
"""

Entonces, podemos ejecutar la prueba de la siguiente manera:

1
2
3
4
5
6
7
>>> import timeit
>>> min(timeit.repeat(setup=setup, stmt=recursion))
0.7848201999999986
>>> min(timeit.repeat(setup=setup, stmt=while_loop))
0.040824499999999375
>>> min(timeit.repeat(setup=setup, stmt=for_loop))
0.34835850000000335

Una cosa que encontré realmente interesante fue el rendimiento del whilebucle. Entonces, me di cuenta de que mi prueba era un poco inexacta. Específicamente, coloqué la iconfiguración en, por lo que se convirtió en cero después de la primera iteración. En otras palabras, el whilebucle se convirtió en una declaración if glorificada. Cuando actualicé mi cadena de configuración, aquí estaban los resultados:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
>>> setup = """
def recurse(i):
    # Removed print for sanity
    if i > 0:
        recurse(i - 1)
"""
>>> while_loop = """
i = 5
while i >= 0:
    # Removed print for sanity
    i -= 1
"""
>>> min(timeit.repeat(setup=setup, stmt=while_loop))
0.3415355000000204

Ahora, eso es casi idéntico al forbucle, lo que tiene sentido para mí. Dicho esto, estaba leyendo algunas discusiones sobre rendimiento en StackOverflow , y el forciclo debería ser más rápido en general. Naturalmente, tuve que investigar, así que actualicé ambas soluciones para grandes números:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
>>> for_loop = """
for i in range(100, -1, -1):
    pass  # Removed print for sanity
"""
>>> min(timeit.repeat(setup=setup, stmt=for_loop))
1.2956954000001133
>>> while_loop = """
i = 100
while i >= 0:
    # Removed print for sanity
    i -= 1
"""
>>> min(timeit.repeat(setup=setup, stmt=while_loop))
4.765163399999892

Resulta que 100 era todo lo que estaba dispuesto a esperar. De lo contrario, esta prueba puede haber durado todo el día. Dicho esto, incluso en un número tan pequeño, hay una diferencia obvia en el rendimiento. No dude en consultar la discusión anterior para obtener una explicación más detallada de por qué.

Desafío

Ahora que sabemos cómo escribir un bucle, intentemos algo interesante. Imaginemos que tenemos una lista de listas (también conocida como matriz):

1
2
3
4
5
my_matrix = [
    [3, 5, 2, 4],
    [5, 9, 4, 2],
    [1, 8, 4, 3]
]

Y queremos sumar cada fila (lista interna) y determinar el promedio de todas las filas. Usando el ejemplo anterior, obtendríamos los siguientes totales de fila:

1
2
3
4
5
my_matrix = [
    [3, 5, 2, 4],  # 14
    [5, 9, 4, 2],  # 20
    [1, 8, 4, 3]   # 16
]

Luego, promediaríamos los totales:

1
(14 + 20 + 16) / 3  # 16.666666666666668

Cuando terminemos, informaremos el resultado al usuario.

Si bien esto parece una tarea bastante sencilla para nosotros, ¿cómo entrenaríamos a la computadora para que lo haga? En otras palabras, ¿cómo usaríamos las distintas sintaxis de bucles para hacer esto (pista: es posible que desee anidar dos bucles)?

Si se le ocurre una solución, déjela caer en los comentarios. Naturalmente, arrojaré mi propia solución allí para comenzar.

Un pequeño resumen

Con todo eso fuera del camino, revisemos nuestras soluciones una vez más:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
dieciséis
# Recursion
def recurse(i):
    print(f'Input is {i}')
    if i > 0:
        recurse(i - 1)
recurse(5)
 
# While loop
i = 5
while i >= 0:
    print(f'Input is {i}')
    i -= 1
 
# For loop
for i in range(5, -1, -1):
    print(f'Input is {i}')

Publicar un comentario

0 Comentarios