Bucles: repetir con y sin límite conocido

Enterate del próximo post
Un mail cuando publico algo nuevo. Sin spam y te podés dar de baja cuando quieras.

Un mail cuando publico algo nuevo. Sin spam y te podés dar de baja cuando quieras.
Regar las plantas de un balcón implica repetir el mismo paso -verter agua- una vez por cada maceta. Si hay seis macetas, ese paso se repite exactamente seis veces: el número se conoce antes de empezar, con solo contar las macetas. Amasar la masa de un pan es distinto: se repite el mismo paso -amasar- hasta que la masa queda lisa, y nadie sabe de antemano si eso va a tardar treinta amasadas o setenta. En los dos casos hay una repetición, pero solo en el primero se conoce el número de vueltas antes de dar la primera.
El post anterior mostró que un algoritmo puede elegir entre más de un camino según sus datos, pero cada paso seguía ejecutándose una sola vez. Acá deja de ser así: un paso, o un bloque entero de pasos, se repite más de una vez, y la cantidad de repeticiones puede conocerse de antemano o depender de una condición que solo se sabe al ejecutar.
Un bucle repite un paso o un bloque de pasos más de una vez. Hay dos formas de decidir cuándo parar, y la diferencia entre ellas es la misma que separa regar macetas de amasar pan.
Un bucle es definido cuando la cantidad de repeticiones se conoce antes de ejecutar la primera. Se escribe con un contador que recorre un rango de valores, uno por vuelta:
repetir contador desde inicio hasta fin:
pasosEl contador toma cada valor entero entre inicio y fin, los dos extremos incluidos, y se repite un paso por cada uno. Con inicio en 1 y fin en 6, hay exactamente seis vueltas, ni una más ni una menos, sin importar qué hagan los pasos de adentro.
Un bucle es indefinido cuando la cantidad de repeticiones no se conoce de antemano: se repite mientras una condición sea verdadera, y se detiene apenas pasa a ser falsa.
mientras condición:
pasosLa condición se evalúa antes de cada vuelta, incluida la primera. Si es falsa desde el principio, el bucle no se ejecuta ni una sola vez: eso no es un caso especial, es exactamente lo que tiene que pasar.
Un bucle mientras que nunca deja de cumplir su condición no termina nunca, y eso rompe la primera condición que el post 1 de esta serie pedía para cualquier algoritmo: ser finito. Por eso cada vuelta de un bucle indefinido tiene que acercar, de alguna manera, el momento en que la condición se vuelva falsa; si ningún paso de adentro cambia nada de lo que la condición mira, el bucle se repite para siempre.
El siguiente algoritmo suma los primeros números naturales hasta una cantidad dada, con un bucle definido.
algoritmo sumar_primeros(cantidad):
repetir contador desde 1 hasta cantidad:
sumar contador al total
devolver el totalCon cantidad en 4: el contador pasa por 1, 2, 3 y 4, una vuelta por cada valor. El total arranca en 0 antes de la primera vuelta -nada sumado todavía-, y va acumulando: 0 + 1 = 1, 1 + 2 = 3, 3 + 3 = 6, 6 + 4 = 10. El bucle termina exactamente después de la cuarta vuelta, porque el contador ya recorrió todo el rango, y devuelve 10.
El segundo ejemplo tiene el número de repeticiones desconocido de antemano: cuántos días hacen falta para juntar un monto ahorrando una cantidad fija por día.
algoritmo dias_para_juntar(monto_objetivo, ahorro_diario):
mientras el total ahorrado es menor a monto_objetivo:
sumar ahorro_diario al total ahorrado
sumar 1 a los días
devolver los díasCon monto_objetivo en 1000 y ahorro_diario en 300: antes de la primera vuelta el total ahorrado es 0, que es menor a 1000, así que el bucle entra. Día 1: total 300, todavía menor a 1000. Día 2: total 600, todavía menor. Día 3: total 900, todavía menor. Día 4: total 1200, ya no es menor a 1000, así que la condición se vuelve falsa y el bucle se detiene ahí. El resultado es 4 días. Nadie podía saber ese número con solo mirar el algoritmo, sin ejecutarlo con esos datos concretos: por eso es indefinido.
Calcular el producto de los primeros números naturales hasta una cantidad dada -lo que en matemática se llama factorial-, usando un bucle definido.
Pista: la estructura es la misma de sumar_primeros, cambiando la suma por la multiplicación. Pero hay un detalle que no se puede copiar igual: total arrancaba en 0 porque sumar 0 no cambia nada. ¿Con qué valor tiene que arrancar el producto para que la primera multiplicación no lo deje siempre en 0?
algoritmo producto_primeros(cantidad):
repetir contador desde 1 hasta cantidad:
multiplicar el producto por contador
devolver el productoCon cantidad en 4, y el producto arrancando en 1: 1 × 1 = 1, 1 × 2 = 2, 2 × 3 = 6, 6 × 4 = 24. El resultado es 24, que es el factorial de 4. Si el producto hubiera arrancado en 0 en vez de 1, la primera vuelta ya lo hubiera dejado en 0 -cero por cualquier cosa da cero-, y ninguna vuelta siguiente podría arreglarlo: el valor de arranque de un acumulador depende de la operación que hace, no es siempre el mismo.
Una hoja de papel tiene un grosor inicial, en milímetros. Cada vez que se dobla por la mitad, el grosor se duplica. Calcular cuántos dobleces hacen falta para que el grosor supere un valor objetivo.
Pista: no se sabe de antemano cuántos dobleces van a hacer falta, porque depende de la diferencia entre el grosor inicial y el objetivo, no de un número fijo conocido antes de empezar. Antes de escribir nada, conviene preguntarse qué condición tiene que dejar de cumplirse para poder parar.
algoritmo dobleces_necesarios(grosor_inicial, grosor_objetivo):
mientras grosor_inicial es menor a grosor_objetivo:
multiplicar grosor_inicial por 2
sumar 1 a los dobleces
devolver los doblecesCon un grosor inicial de 0.1 milímetros y un objetivo de 10: 0.2, 0.4, 0.8, 1.6, 3.2, 6.4 y recién en la séptima vuelta 12.8, que ya supera 10. El resultado son 7 dobleces. Cambiar cualquiera de los dos datos de entrada cambia cuántas vueltas hacen falta, sin que el algoritmo tenga que escribirse de nuevo: es exactamente la situación que un bucle definido no puede resolver, porque ahí la cantidad de vueltas se fija de antemano y acá depende de los datos.
Un bucle definido conoce su número de vueltas antes de la primera; un bucle indefinido las cuenta a medida que una condición sigue siendo verdadera, y puede terminar ejecutándose cero veces si esa condición ya es falsa desde el principio. La diferencia no es de sintaxis: es si hace falta ejecutar algo para saber cuándo parar, o si ese número ya se conocía de antemano. Un bucle indefinido que nunca deja de cumplir su condición deja de ser un algoritmo, porque deja de ser finito.
Hasta acá cada post de esta serie presentó una herramienta nueva: secuencia, condicionales, operadores lógicos, bucles. El próximo paso de la serie no agrega ninguna: repasa el control de flujo completo, combinando estas herramientas en situaciones donde ya no queda dicho cuál corresponde usar en cada caso.