Pensamiento Computacional · Unidad 3 — Estructuras de control
Ciclo for y range
for repite una vez por cada elemento de algo. range fabrica esos elementos, y arranca en 0 y termina antes del número que le des.
La explicación
Recorrer algo
print(letra)
Imprime s, o, l. La variable letra toma
cada valor, uno por vuelta. Su nombre lo elegís vos.
range fabrica números
range(2, 7) → 2, 3, 4, 5, 6
range(0, 10, 3) → 0, 3, 6, 9
range(5, 0, -1) → 5, 4, 3, 2, 1
range(5) no incluye el 5;
da cinco números, del 0 al 4. Por eso range(n) sirve tan bien para recorrer algo de
n elementos: los índices van justo de 0 a n−1.Y de ahí sale el nombre del error más famoso de programación: el off-by-one, equivocarse por uno. Si tu ciclo procesa un elemento de más o de menos, empezá mirando el range.
Dos formas de recorrer
print(c)
for i in range(len(palabra)): # las posiciones
print(i, palabra[i])
La primera es más limpia y es la que conviene casi siempre. La segunda sólo cuando necesitás saber en qué posición estás.
Acumular
El patrón más usado de todos: una variable afuera que se va sumando adentro.
for n in range(1, 6):
total = total + n # adentro, una vez por vuelta
print(total) # 15
Un range que no da vueltas
range(3, 3) está vacío: el ciclo no se ejecuta ni una vez. No es un error, y
explica esos casos raros donde "el for no hace nada".
Escribir una función que reciba un número n y devuelva la suma de todos los pares desde 1 hasta n.
Las tres decisiones. Qué recorro, qué acumulo, y cuándo sumo.
total = 0 # acumulador, afuera
for i in range(1, n + 1): # el n+1 es para que n entre
if i % 2 == 0: # sólo los pares
total = total + i
return total
Por qué n + 1. Como range termina antes del segundo número,
range(1, n) dejaría afuera justo a n. Con n + 1 el
último valor que entra es n. Es el off-by-one en su forma más común.
Probemos con n = 10: suma 2 + 4 + 6 + 8 + 10 = 30.
range, que arranca en 2 y va de a 2:
total = 0
for i in range(2, n + 1, 2):
total = total + i
return total
if. Cuando puedas expresar la condición
en el propio range, sale mejor.
Control: con n = 2 tiene que dar 2, y con n = 1 tiene que dar 0. Los casos chicos son los que descubren los off-by-one.
Escribir una función que reciba un número n y devuelva su cantidad de divisores, y otra que use la primera para decidir si n es primo. Probar con 12, 7, 1 y 97, y explicar por qué la versión directa es lenta.
La definición de divisor, traducida a código: i divide a n cuando el resto de la división es cero. Con eso, contar divisores es recorrer y contar:
cantidad = 0
for i in range(1, n + 1):
if n % i == 0:
cantidad = cantidad + 1
return cantidad
El n + 1 del range es para que n se cuente a sí mismo: todo número se divide por sí mismo, y si no entra, todos los resultados salen uno menos.
La función de primos, que se apoya en la anterior:
return n > 1 and divisores(n) == 2
La definición de primo es exactamente "tiene dos divisores": el 1 y él mismo. Escribirla así evita la discusión sobre el 1, porque el 1 tiene un solo divisor y queda afuera solo.
El n > 1 del principio es para los negativos y el cero, donde divisores devolvería 0 y la comparación no tendría sentido.
Las pruebas:
divisores(7) → 2 (1, 7)
divisores(1) → 1 (solo el 1)
divisores(36) → 9 (1,2,3,4,6,9,12,18,36)
es_primo(2) → True (el único primo par)
es_primo(9) → False (9 = 3·3, no se salva por ser impar)
es_primo(97) → True
El control fuerte: los primos hasta 30 tienen que ser 2, 3, 5, 7, 11, 13, 17, 19, 23 y 29. Son diez, y el programa devuelve esos diez ✓. Una lista conocida es la mejor prueba para una función de este tipo.
Ahora por qué es lenta. Para decidir si 1.000.003 es primo, el ciclo da un millón de vueltas. Y casi todas son inútiles.
La observación que arregla todo: los divisores vienen de a pares. Si i divide a n, entonces n/i también:
El par se "da vuelta" en la raíz cuadrada. Después de 6, los divisores que aparecen son los compañeros de los que ya vimos. Así que alcanza con buscar hasta √n y contar de a dos:
total = 0
i = 1
while i * i <= n:
if n % i == 0:
if i * i == n:
total = total + 1 # el del medio, una sola vez
else:
total = total + 2 # i y su companero n // i
i = i + 1
return total
El if i * i == n: cuando n es un cuadrado perfecto, el divisor del medio (el 6 de 36) es su propio compañero y se cuenta una sola vez. Sin esa línea, 36 daría 10 divisores en vez de 9.
Por qué i*i <= n y no i <= math.sqrt(n): multiplicar enteros es exacto; la raíz cuadrada es un número con coma y puede dar 5.999999 donde debería dar 6, y ahí se pierde un divisor.
La diferencia en la práctica: para 1.000.003 la primera versión da un millón de vueltas y la segunda da mil. Las dos versiones coinciden en todos los números del 1 al 400, así que la rápida no es "otra cosa": es la misma función, mil veces más rápida.
Dónde se pierde el punto: contestar que el 1 es primo, o escribir range(2, n) y perder el divisor n. Y en la versión rápida, olvidar el caso del cuadrado perfecto: es el único número que rompe el conteo de a pares.
Dónde se cae la mayoría
- Esperar que range(5) llegue hasta el 5. Llega hasta el 4.
- Poner el acumulador adentro del ciclo: se reinicia en cada vuelta y el resultado queda mal sin avisar.
- Usar range(len(x)) cuando alcanzaba con recorrer los elementos directo.
Hasta acá la explicación. La práctica es la otra mitad: 8 ejercicios de este tema, en cuatro niveles, que no te dicen sólo si está bien al final sino que te corrigen en cada paso y te explican por qué. Y uno para escribir el programa vos, que se prueba ejecutándolo en Python ahí mismo, en el navegador. Además trae una animación que muestra la idea en movimiento.
Practicar Ciclo for y range →Dónde entra este tema en el parcial
- Primer parcial — Trazar: for con range y for sobre un diccionario10 puntos · resuelto paso a paso
- Primer parcial — Completar: armar una lista nueva20 puntos · resuelto paso a paso
Los otros temas de la unidad 3
Estructuras de control