Bucles

../../../_images/gary-lopater-UaUaefoUmZ8-unsplash.jpg

Cuando queremos hacer algo más de una vez, necesitamos recurrir a un bucle. En esta sección veremos las distintas sentencias en Python que nos permiten repetir un bloque de código. 1

La sentencia while

El primer mecanismo que existe en Python para repetir instrucciones es usar la sentencia while. La semántica tras esta sentencia es: «Mientras se cumpla la condición haz algo».

Veamos un sencillo bucle que repite un saludo mientras así se desee:

>>> want_greet = 'S'  # importante dar un valor inicial

>>> while want_greet == 'S':
...     print('Hola qué tal!')
...     want_greet = input('¿Quiere otro saludo? [S/N] ')
... print('Que tenga un buen día')
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] N
Que tenga un buen día

Ejecución paso a paso a través de Python Tutor:

La condición del bucle se comprueba en cada nueva repetición. En este caso chequeamos que la variable want_greet sea igual a 'S'. Dentro del cuerpo del bucle estamos mostrando un mensaje y pidiendo la opción al usuario.

Romper un bucle while

Python ofrece la posibilidad de romper o finalizar un bucle antes de que se cumpla la condición de parada.

Supongamos que en el ejemplo anterior, establecemos un máximo de 4 saludos:

>>> MAX_GREETS = 4

>>> num_greets = 0
>>> want_greet = 'S'

>>> while want_greet == 'S':
...     print('Hola qué tal!')
...     num_greets += 1
...     if num_greets == MAX_GREETS:
...         print('Máximo número de saludos alcanzado')
...         break
...     want_greet = input('¿Quiere otro saludo? [S/N] ')
... print('Que tenga un buen día')
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
Máximo número de saludos alcanzado
Que tenga un buen día

Ejecución paso a paso a través de Python Tutor:

Como hemos visto en este ejemplo, break nos permite finalizar el bucle una vez que hemos llegado al máximo número de saludos. Pero si no hubiéramos llegado a dicho límite, el bucle habría seguido hasta que el usuario indicara que no quiere más saludos.

Otra forma de resolver este ejercicio sería incorporar una condición al bucle:

while want_greet == 'S' and num_questions < MAX_GREETS:
    ...

Comprobar la rotura

Nivel intermedio

Python nos ofrece la posibilidad de detectar si el bucle ha acabado de forma ordinaria, esto es, ha finalizado por no cumplirse la condición establecida. Para ello podemos hacer uso de la sentencia else como parte del propio bucle. Si el bucle while finaliza normalmente (sin llamada a break) el flujo de control pasa a la sentencia opcional else.

Veamos su comportamiento siguiendo con el ejemplo que venimos trabajando:

>>> MAX_GREETS = 4

>>> num_greets = 0
>>> want_greet = 'S'

>>> while want_greet == 'S':
...     print('Hola qué tal!')
...     num_greets += 1
...     if num_greets == MAX_GREETS:
...         print('Máximo número de saludos alcanzado')
...         break
...     want_greet = input('¿Quiere otro saludo? [S/N] ')
... else:
...     print('Usted no quiere más saludos')
... print('Que tenga un buen día')
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] N
Usted no quiere más saludos
Que tenga un buen día

Importante

Si hubiéramos agotado el número de saludos NO se habría ejecutado la cláusula else del bucle ya que habríamos roto el flujo con un break.

Ejecución paso a paso a través de Python Tutor:

Continuar un bucle

Nivel intermedio

Hay situaciones en las que, en vez de romper un bucle, nos interesa saltar adelante hacia la siguiente repetición. Para ello Python nos ofrece la sentencia continue que hace precisamente eso, descartar el resto del código del bucle y saltar a la siguiente iteración.

Continuamos con el ejemplo anterior y vamos a contar el número de respuestas válidas:

>>> want_greet = 'S'
>>> valid_options = 0

>>> while want_greet == 'S':
...     print('Hola qué tal!')
...     want_greet = input('¿Quiere otro saludo? [S/N] ')
...     if want_greet not in 'SN':
...         print('No le he entendido pero le saludo')
...         want_greet = 'S'
...         continue
...     valid_options += 1
... print(f'{valid_options} respuestas válidas')
... print('Que tenga un buen día')
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] A
No le he entendido pero le saludo
Hola qué tal!
¿Quiere otro saludo? [S/N] B
No le he entendido pero le saludo
Hola qué tal!
¿Quiere otro saludo? [S/N] N
2 respuestas válidas
Que tenga un buen día

Ejecución paso a paso a través de Python Tutor:

Bucle infinito

Si no establecemos correctamente la condición de parada o bien el valor de alguna variable está fuera de control, es posible que lleguemos a una situación de bucle infinito, del que nunca podamos salir. Veamos un ejemplo de esto:

>>> num = 1

>>> while num != 10:
...     num += 2
...
# CTRL-C
KeyboardInterrupt
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>

El problema que surje es que la variable num toma los valores 1, 3, 5, 7, 9, 11, ... por lo que nunca se cumple la condición de parada del bucle. Esto hace que repitamos «eternamente» la instrucción de incremento.

Ejecución paso a paso a través de Python Tutor:

Una posible solución a este error es reescribir la condición de parada en el bucle:

>>> num = 1

>>> while num < 10:
...     num += 2
...

Truco

Para abortar una situación de bucle infinito podemos pulsar en el teclado la combinación CTRL-C. Se puede ver reflejado en el intérprete de Python por KeyboardInterrupt.

Hay veces que un supuesto bucle «infinito» puede ayudarnos a resolver un problema. Imaginemos que queremos escribir un programa que ayude al profesorado a introducir las notas de un examen. Si la nota no está en el intervalo \([0, 10]\) mostramos un mensaje de error, en otro caso seguimos pidiendo valores:

>>> while True:
...     mark = float(input('Introduzca nueva nota: '))
...     if not(0 <= mark <= 10):
...         print('Nota fuera de rango')
...         break
...     print(mark)
...
Introduzca nueva nota: 5
5.0
Introduzca nueva nota: 3
3.0
Introduzca nueva nota: 11
Nota fuera de rango

El código anterior se podría enfocar haciendo uso del operador morsa:

>>> while 0 <= (mark := float(input('Introduzca una nueva nota: '))) <= 10:
...     print(mark)
... print('Nota fuera de rango')
Introduzca una nueva nota: 5
5.0
Introduzca una nueva nota: 3
3.0
Introduzca una nueva nota: 11
Nota fuera de rango

Ejercicio

Escriba un programa que encuentre todos los múltiplos de 5 menores que un valor dado:

Ejemplo
  • Entrada: 36

  • Salida: 5 10 15 20 25 30 35

Solución: limit5.py

La sentencia for

Python permite recorrer aquellos tipos de datos que sean iterables, es decir, que admitan iterar 2 sobre ellos. Algunos ejemplos de tipos y estructuras de datos que permiten ser iteradas (recorridas) son: cadenas de texto, listas, diccionarios, ficheros, etc. La sentencia for nos permite realizar esta acción.

A continuación se plantea un ejemplo en el que vamos a recorrer (iterar) una cadena de texto:

>>> word = 'Python'

>>> for letter in word:
...     print(letter)
...
P
y
t
h
o
n

La clave aquí está en darse cuenta que el bucle va tomando, en cada iteración, cada uno de los elementos de la variable que especifiquemos. En este caso concreto letter va tomando cada una de las letras que existen en word, porque una cadena de texto está formada por elementos que son caracteres.

Ejecución paso a paso a través de Python Tutor:

Importante

La variable que utilizamos en el bucle for para ir tomando los valores puede tener cualquier nombre. Al fin y al cabo es una variable que definimos según nuestras necesidades. Tener en cuenta que se suele usar un nombre en singular.

Romper un bucle for

Una sentencia break dentro de un for rompe el bucle, igual que veíamos para los bucles while. Veamos un ejemplo con el código anterior. En este caso vamos a recorrer una cadena de texto y pararemos el bucle cuando encontremos una letra t minúscula:

>>> word = 'Python'

>>> for letter in word:
...     if letter == 't':
...         break
...     print(letter)
...
P
y

Ejecución paso a paso a través de Python Tutor:

Truco

Tanto la comprobación de rotura de un bucle como la continuación a la siguiente iteración se llevan a cabo del mismo modo que hemos visto con los bucles de tipo while.

Ejercicio

pycheck: count_vowels

Secuencias de números

Es muy habitual hacer uso de secuencias de números en bucles. Python no tiene una instrucción específica para ello. Lo que sí aporta es una función range() que devuelve un flujo de números en el rango especificado. Una de las grandes ventajas es que la «lista» generada no se construye explícitamente, sino que cada valor se genera bajo demanda. Esta técnica mejora el consumo de recursos, especialmente en términos de memoria.

La técnica para la generación de secuencias de números es muy similar a la utilizada en los «slices» de cadenas de texto. En este caso disponemos de la función range(start, stop, step):

  • start: Es opcional y tiene valor por defecto 0.

  • stop: es obligatorio (siempre se llega a 1 menos que este valor).

  • step: es opcional y tiene valor por defecto 1.

range() devuelve un objeto iterable, así que iremos obteniendo los valores paso a paso con una sentencia for ... in 3. Veamos diferentes ejemplos de uso:

Rango: \([0, 1, 2]\)
>>> for i in range(0, 3):
...     print(i)
...
0
1
2

>>> for i in range(3):  # No hace falta indicar el inicio si es 0
...     print(i)
...
0
1
2
Rango: \([1, 3, 5]\)
>>> for i in range(1, 6, 2):
...     print(i)
...
1
3
5
Rango: \([2, 1, 0]\)
>>> for i in range(2, -1, -1):
...     print(i)
...
2
1
0

Ejecución paso a paso a través de Python Tutor:

Truco

Se suelen utilizar nombres de variables i, j, k para lo que se denominan contadores. Este tipo de variables toman valores numéricos enteros como en los ejemplos anteriores. No conviene generalizar el uso de estas variables a situaciones en las que, claramente, tenemos la posibilidad de asignar un nombre semánticamente más significativo. Esto viene de tiempos antiguos en FORTRAN donde i era la primera letra que tenía valor entero por defecto.

Ejercicio

pycheck: prime

Usando el guión bajo

Nivel avanzado

Hay situaciones en las que no necesitamos usar la variable que toma valores en el rango, sino que únicamente queremos repetir una acción un número determinado de veces.

Para estos casos se suele recomendar usar el guión bajo _ como nombre de variable, que da a entender que no estamos usando esta variable de forma explícita:

>>> for _ in range(10):
...     print('Repeat me 10 times!')
...
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!

Ejercicio

pycheck: pow

Bucles anidados

Como ya vimos en las sentencias condicionales, el anidamiento es una técnica por la que incluimos distintos niveles de encapsulamiento de sentencias, unas dentro de otras, con mayor nivel de profundidad. En el caso de los bucles también es posible hacer anidamiento.

../../../_images/matrioskas.jpg

Muñecas rusas Matrioskas para ejemplificar el anidamiento 4

Veamos un ejemplo de 2 bucles anidados en el que generamos todas las tablas de multiplicar:

>>> for num_table in range(1, 10):
...     for mul_factor in range(1, 10):
...         result = num_table * mul_factor
...         print(f'{num_table} * {mul_factor} = {result}')
...
1 x 1 = 1
1 x 2 = 2
1 x 3 = 3
1 x 4 = 4
1 x 5 = 5
1 x 6 = 6
1 x 7 = 7
1 x 8 = 8
1 x 9 = 9
2 x 1 = 2
2 x 2 = 4
2 x 3 = 6
2 x 4 = 8
2 x 5 = 10
2 x 6 = 12
2 x 7 = 14
2 x 8 = 16
2 x 9 = 18
3 x 1 = 3
3 x 2 = 6
3 x 3 = 9
3 x 4 = 12
3 x 5 = 15
3 x 6 = 18
3 x 7 = 21
3 x 8 = 24
3 x 9 = 27
4 x 1 = 4
4 x 2 = 8
4 x 3 = 12
4 x 4 = 16
4 x 5 = 20
4 x 6 = 24
4 x 7 = 28
4 x 8 = 32
4 x 9 = 36
5 x 1 = 5
5 x 2 = 10
5 x 3 = 15
5 x 4 = 20
5 x 5 = 25
5 x 6 = 30
5 x 7 = 35
5 x 8 = 40
5 x 9 = 45
6 x 1 = 6
6 x 2 = 12
6 x 3 = 18
6 x 4 = 24
6 x 5 = 30
6 x 6 = 36
6 x 7 = 42
6 x 8 = 48
6 x 9 = 54
7 x 1 = 7
7 x 2 = 14
7 x 3 = 21
7 x 4 = 28
7 x 5 = 35
7 x 6 = 42
7 x 7 = 49
7 x 8 = 56
7 x 9 = 63
8 x 1 = 8
8 x 2 = 16
8 x 3 = 24
8 x 4 = 32
8 x 5 = 40
8 x 6 = 48
8 x 7 = 56
8 x 8 = 64
8 x 9 = 72
9 x 1 = 9
9 x 2 = 18
9 x 3 = 27
9 x 4 = 36
9 x 5 = 45
9 x 6 = 54
9 x 7 = 63
9 x 8 = 72
9 x 9 = 81

Lo que está ocurriendo en este código es que, para cada valor que toma la variable i, la otra variable j toma todos sus valores. Como resultado tenemos una combinación completa de los valores en el rango especificado.

Ejecución paso a paso a través de Python Tutor:

Nota

  • Podemos añadir todos los niveles de anidamiento que queramos. Eso sí, hay que tener en cuenta que cada nuevo nivel de anidamiento supone un importante aumento de la complejidad ciclomática de nuestro código, lo que se traduce en mayores tiempos de ejecución.

  • Los bucles anidados también se pueden aplicar en la sentencia while.

Ejercicio

Dado su tamaño, muestre por pantalla un mosaico donde la diagonal principal esté representada por X, la parte inferior por D y la parte superior por U.

Ejemplo
  • Entrada: 5

  • Salida:

    X U U U U
    D X U U U
    D D X U U
    D D D X U
    D D D D X
    

Solución: mosaic.py


EJERCICIOS DE REPASO

  1. Escriba un programa que encuentre la mínima secuencia de múltiplos de 3 (distintos) cuya suma sea igual o superior a un valor dado (solución).
    • Entrada: 45

    • Salida: 0, 3, 6, 9, 12, 15

  2. Escriba un programa que pida nombre y apellidos de una persona (usando un solo input) y repita la pregunta mientras el nombre no esté en formato título (solución).

¿Su nombre? ana torres blanco
Error. Debe escribirlo correctamente
¿Su nombre? Ana torres blanco
Error. Debe escribirlo correctamente
¿Su nombre? Ana Torres blanco
Error. Debe escribirlo correctamente
¿Su nombre? Ana Torres Blanco
  1. Escriba un programa en Python que realice las siguientes 9 multiplicaciones. ¿Nota algo raro en el resultado? (solución)

\[\begin{split}1 &\cdot 1\\ 11 &\cdot 11\\ 111 &\cdot 111\\ &\vdots\\ 111111111 &\cdot 111111111\end{split}\]
  1. Escriba un programa en Python que acepte dos valores enteros (\(x\) e \(y\)) que representarán un punto (objetivo) en el plano. El programa simulará el movimiento de un «caballo» de ajedrez moviéndose de forma alterna: 2 posiciones en \(x\) + 1 posición en \(y\). El siguiente movimiento que toque sería para moverse 1 posición en \(x\) + 2 posiciones en \(y\). El programa deberá ir mostrando los puntos por los que va pasando el «caballo» hasta llegar al punto objetivo (solución).
    • Entrada: objetivo_x=7; objetivo_y=8;

    • Salida: (0, 0) (1, 2) (3, 3) (4, 5) (6, 6) (7, 8)

  2. Escriba un programa que muestre por pantalla todas las fichas del dominó. La ficha «en blanco» se puede representar con un 0 (solución).

../../../_images/domino.jpg
0|0 0|1 0|2 0|3 0|4 0|5 0|6
1|1 1|2 1|3 1|4 1|5 1|6
2|2 2|3 2|4 2|5 2|6
3|3 3|4 3|5 3|6
4|4 4|5 4|6
5|5 5|6
6|6
  1. Escriba un programa que calcule el valor de \(x\) para el que la función \(f(x) = x^2 - 6x + 3\) obtiene su menor resultado. Centre la búsqueda en el rango \([-9, 9]\) sólo con valores enteros (solución).

El resultado es: \(x = 3\) y \(f(x) = -6\)

../../../_images/fmin.png
  1. Escriba un programa que muestre (por filas) la Tabla ASCII, empezando con el código 33 y terminando con el 127 (solución):

033 !   034 "   035 #   036 $   037 %
038 &   039 '   040 (   041 )   042 *
043 +   044 ,   045 -   046 .   047 /
048 0   049 1   050 2   051 3   052 4
053 5   054 6   055 7   056 8   057 9
058 :   059 ;   060 <   061 =   062 >
063 ?   064 @   065 A   066 B   067 C
068 D   069 E   070 F   071 G   072 H
073 I   074 J   075 K   076 L   077 M
078 N   079 O   080 P   081 Q   082 R
083 S   084 T   085 U   086 V   087 W
088 X   089 Y   090 Z   091 [   092 \
093 ]   094 ^   095 _   096 `   097 a
098 b   099 c   100 d   101 e   102 f
103 g   104 h   105 i   106 j   107 k
108 l   109 m   110 n   111 o   112 p
113 q   114 r   115 s   116 t   117 u
118 v   119 w   120 x   121 y   122 z
123 {   124 |   125 }   126 ~   127
  1. Escriba un programa que permita al usuario adivinar un número. Indicar si el número buscado es menor o mayor que el que se está preguntando y mostrar igualmente el número de intentos hasta encontrar el número objetivo (solución):

Introduzca número: 50
Mayor
Introduzca número: 100
Menor
Introduzca número: 90
Menor
Introduzca número: 87
✅ ¡Enhorabuena! Has encontrado el número en 4 intentos
  1. pycheck: gcd

  2. pycheck: hamming

  3. pycheck: sprod_cart

  4. pycheck: cumsq_prod

  5. pycheck: isalphabetic

  6. pycheck: tennis_game

  7. pycheck: tennis_set

  8. pycheck: kpower

  9. pycheck: fibonacci

EJERCICIOS EXTERNOS

  1. Summation

  2. Find nearest square number

  3. Bin to decimal

  4. altERnaTIng cAsE

  5. Fake binary

  6. Correct the mistakes of the character recognition software

  7. String cleaning

  8. Sum of multiples

  9. ASCII Total

  10. Collatz Conjecture (3n+1)

AMPLIAR CONOCIMIENTOS

1

Foto original de portada por Gary Lopater en Unsplash.

2

Realizar cierta acción varias veces. En este caso la acción es tomar cada elemento.

3

O convertir el objeto a una secuencia como una lista.

4

Foto de Matrioskas por Marina Yufereva`_` en Escáner Cultural.