31º agosto 2020

Bucles: while y for

Usualmente necesitamos repetir acciones.

Por ejemplo, mostrar los elementos de una lista uno tras otro o simplemente ejecutar el mismo código para cada número del 1 al 10.

Los Bucles son una forma de repetir el mismo código varias veces.

El bucle “while”

El bucle while (mientras) tiene la siguiente sintaxis:

while (condition) {
  // código
  // llamado "cuerpo del bucle"
}

Mientras que la condition (condición) sea true, el código del cuerpo del bucle será ejecutado.

Por ejemplo, el bucle debajo imprime i mientras que i < 3:

let i = 0;
while (i < 3) { // muestra 0, luego 1, luego 2
  alert( i );
  i++;
}

Una sola ejecución del cuerpo del bucle es llamada una iteración. El bucle en el ejemplo de arriba realiza 3 iteraciones.

Si i++ no estuviera en el ejemplo de arriba, el bucle sería repetido (en teoría) eternamente. En la practica, el navegador proporciona maneras de detener dichos bucles, y en el JavaScript del lado del servidor, podemos eliminar el proceso.

Cualquier expresión o variable puede ser una condición del bucle, no solo comparaciones: la condición será evaluada y transformada a un booleano por while.

Por ejemplo, una manera más corta de escribir while (i != 0) es while (i):

let i = 3;
while (i) { // cuando i sea 0, la condición será un valor falso, y el bucle se detendrá
  alert( i );
  i--;
}
Las llaves no son requeridas para un cuerpo de una sola línea

Si el cuerpo del bucle tiene una sola sentencia, podemos omitir las llaves {…}:

let i = 3;
while (i) alert(i--);

El bucle “do…while”

La comprobación de la condición puede ser movida debajo del cuerpo del bucle usando la sintaxis de do..while:

do {
  // cuerpo del bucle
} while (condition);

El bucle primero ejecuta el cuerpo, luego comprueba la condición, y, mientras sea un valor verdadero, la ejecuta una y otra vez.

Por ejemplo:

let i = 0;
do {
  alert( i );
  i++;
} while (i < 3);

Esta sintaxis solo debería ser usada cuando quieres que el cuerpo del bucle sea ejecutado al menos una vez sin importar que la condición sea verdadera. Usualmente, se prefiere la otra forma: while(…) {…}.

El bucle “for”

El bucle for es el bucle más comúnmente usado.

Se ve así:

for (begin; condition; step) {
  // ... cuerpo del bucle ...
}

Aprendamos el significado de cada parte con un ejemplo. El bucle debajo corre alert(i) para i desde 0 hasta (pero no incluyendo) 3:

for (let i = 0; i < 3; i++) { // muestra 0, luego 1, luego 2
  alert(i);
}

Vamos a examinar la declaración for parte por parte:

parte
comienzo i = 0 Se ejecuta una vez al comienzo del bucle.
condición i < 3 Comprobada antes de cada iteración del bucle. Si es falsa, el bucle se detiene.
paso i++ Se ejecuta después del cuerpo en cada iteración pero antes de la comprobación de la condición.
cuerpo alert(i) Se ejecuta una y otra vez mientras que la condición sea verdadera.

El algoritmo general del bucle funciona de esta forma:

Se ejecuta comenzar
→ (si condición → ejecutar cuerpo y ejecutar paso)
→ (si condición → ejecutar cuerpo y ejecutar paso)
→ (si condición → ejecutar cuerpo y ejecutar paso)
→ ...

Si eres nuevo en bucles, te podría ayudar regresar al ejemplo y reproducir cómo se ejecuta paso por paso en una pedazo de papel.

Esto es lo que sucede exactamente en nuestro caso:

// for (let i = 0; i < 3; i++) alert(i)

// se ejecuta comenzar
let i = 0
// si condición → ejecutar cuerpo y ejecutar paso
if (i < 3) { alert(i); i++ }
// si condición → ejecutar cuerpo y ejecutar paso
if (i < 3) { alert(i); i++ }
// si condición → ejecutar cuerpo y ejecutar paso
if (i < 3) { alert(i); i++ }
// ...finaliza, porque ahora i == 3
Declaración de variable en línea

Aquí, la variable “counter” i es declarada en el bucle. Esto es llamado una declaración de variable “en línea”. Dichas variables son visibles solo dentro del bucle.

for (let i = 0; i < 3; i++) {
  alert(i); // 0, 1, 2
}
alert(i); // error, no existe dicha variable

En vez de definir una variable, podemos usar una que ya exista:

let i = 0;

for (i = 0; i < 3; i++) { // usa una variable existente
  alert(i); // 0, 1, 2
}

alert(i); // 3, visible, porque fue declarada fuera del bucle

Saltando partes

Cualquier parte de for puede ser saltada.

Por ejemplo, podemos omitir comenzar si no necesitamos realizar nada al comienzo del bucle.

Como aquí:

let i = 0; // Ya tenemos i declarada y asignada

for (; i < 3; i++) { // no hay necesidad de "comenzar"
  alert( i ); // 0, 1, 2
}

También podemos eliminar la parte paso:

let i = 0;

for (; i < 3;) {
  alert( i++ );
}

Esto hace al bucle idéntico a while (i < 3).

En realidad podemos eliminar todo, creando un bucle infinito:

for (;;) {
  // se repite sin limites
}

Por favor, nota que los dos punto y coma ; del for deben estar presentes. De otra manera, habría un error de sintaxis.

Rompiendo el bucle

Normalmente, se sale de un bucle cuando la condición es falsa.

Pero podemos forzar una salida en cualquier momento usando la directiva especial break.

Por ejemplo, el bucle debajo le pide al usuario por una serie de números, “rompiendo” cuando un número no es ingresado:

let sum = 0;

while (true) {

  let value = +prompt("Ingresa un número", '');

  if (!value) break; // (*)

  sum += value;

}
alert( 'Suma: ' + sum );

La directiva break es activada en la línea (*) si el usuario ingresa una línea vacía o cancela la entrada. Detiene inmediatamente el bucle, pasando el control a la primera línea después de el bucle. En este caso, alert.

La combinación “bucle infinito + break según sea necesario” es ideal en situaciones donde la condición del bucle debe ser comprobada no al inicio o al final de el bucle, sino a la mitad o incluso en varias partes del cuerpo.

Continuar a la siguiente iteración

La directiva continue es una “versión más ligera” de break. No detiene todo el bucle. En su lugar, detiene la iteración actual y fuerza al bucle a comenzar una nueva (si la condición lo permite).

Podemos usarlo si hemos terminado con la iteración actual y nos gustaría movernos a la siguiente.

El bucle debajo usa continue para mostrar solo valores impares:

for (let i = 0; i < 10; i++) {

  // si es verdadero, saltar el resto del cuerpo
  if (i % 2 == 0) continue;

  alert(i); // 1, luego 3, 5, 7, 9
}

Para los valores pares de i, la directiva continue deja de ejecutar el cuerpo y pasa el control a la siguiente iteración de for (con el siguiente número). Así que el alert solo es llamado para valores impares.

La directiva continue ayuda a disminuir la anidación

Un bucle que muestra valores impares podría verse así:

for (let i = 0; i < 10; i++) {

  if (i % 2) {
    alert( i );
  }

}

Desde un punto de vista técnico, esto es idéntico al ejemplo de arriba. Claro, podemos simplemente envolver el código en un bloque if en vez de usar continue.

Pero como efecto secundario, esto crearía un nivel más de anidación (la llamada a alert dentro de las llaves). Si el código dentro de if posee varias líneas, eso podría reducir la legibilidad en general.

No break/continue a la derecha de ‘?’

Por favor, nota que las construcciones de sintaxis que no son expresiones no pueden user usadas con el operador terniario ?. En particular, directivas como break/continue no son permitidas aquí.

Por ejemplo, si tomamos este código:

if (i > 5) {
  alert(i);
} else {
  continue;
}

…y lo reescribimos usando un signo de interrogación:

(i > 5) ? alert(i) : continue; // continue no está permitida aquí

…deja de funcionar. Código como este generarán un error de sintaxis:

Esta es otra razón por la cual se recomienda no usar el operador de signo de interrogación ? en lugar de if.

Etiquetas para break/continue

A veces necesitamos salirnos de múltiples bucles anidados al mismo tiempo.

Por ejemplo, en el código debajo nosotros usamos un bucle sobre i y j, solicitando las coordenadas (i,j) de (0,0) a (3,3):

for (let i = 0; i < 3; i++) {

  for (let j = 0; j < 3; j++) {

    let input = prompt(`Valor en las coordenadas (${i},${j})`, '');

    // ¿Y si quiero salir de aquí hacia Listo (debajo)?

  }
}

alert('Listo!');

Necesitamos una manera de detener el proceso si el usuario cancela la entrada.

El break ordinario después de input solo nos sacaría del bucle interno. Eso no es suficiente–etiquetas, vengan al rescate!

Una etiqueta es un identificar con dos puntos antes de un bucle:

labelName: for (...) {
  ...
}

La declaración break <labelName> en el bucle debajo nos saca hacia la etiqueta:

outer: for (let i = 0; i < 3; i++) {

  for (let j = 0; j < 3; j++) {

    let input = prompt(`Value at coords (${i},${j})`, '');

    // Si es una cadena de texto vacía o se canceló, entonces salir de ambos bucles
    if (!input) break outer; // (*)

    // hacer algo con el valor...
  }
}
alert('Listo!');

En el código de arriba, break outer mira hacia arriba por la etiquieta llamada outer y nos saca de dicho bucle.

Así que el control va directamente de (*) a alert('Listo!').

También podemos mover la etiqueta a una línea separada:

outer:
for (let i = 0; i < 3; i++) { ... }

La directiva continue también puede usar usada con una etiqueta. En este caso, la ejecución del código salta a la siguiente iteración del bucle etiquetado.

Las etiquetas no son “goto”

Las etiquetas no nos permiten saltar a un lugar arbitrario en el código.

Por ejemplo, es imposible hacer esto:

break label;  // saltar a label? No.

label: for (...)

Una llamada a break/continue solo es posible desde el interior del bucle y la etiqueta debe estar en alguna parte arriba de la directiva.

Resumen

Cubrimos 3 tipos de bucles:

  • while – La condición es comprobada antes de cada iteración.
  • do..while – La condición es comprobada después de cada iteración.
  • for (;;) – La condición es comprobada antes de cada iteración, con ajustes adicionales disponibles.

Para crear un bucle “infinito”, usualmente se usa while(true). Un bucle como este, tal y como cualquier otro, puede ser detenido con la directiva break.

Si no queremos hacer nada con la iteración actual y queremos adelantarnos a la siguiente, podemos usar la directiva continue.

break/continue soportan etiquetas antes del bucle. Una etiqueta es la única forma de usar break/continue para escapar de un bucle anidado para ir a uno exterior.

Tareas

importancia: 3

¿Cuál es el último valor mostrado en alerta por este código? ¿Por qué?

let i = 3;

while (i) {
  alert( i-- );
}

La respuesta: 1.

let i = 3;

while (i) {
  alert( i-- );
}

Cada iteración del bucle disminuye i en 1. La comprobación while(i) detiene el bucle cuando i = 0.

Por consiguiente, los pasos del bucle forman la siguiente secuencia (“bucle desenrollado”).

let i = 3;

alert(i--); // muestra 3, disminuye i a 2

alert(i--) // muestra 2, disminuye i a 1

alert(i--) // muestra 1, disminuye i a 0

// listo, while(i) comprueba y detiene el bucle
importancia: 4

Para cada iteración del bucle, escribe qué valor será impreso y luego compáralo con la solución.

Ambos bucles ¿alertan los mismos valores?

  1. La forma de prefijo ++i:

    let i = 0;
    while (++i < 5) alert( i );
  2. La forma de sufijo i++

    let i = 0;
    while (i++ < 5) alert( i );

La tarea demuestra cómo las formas de sufijo y prefijo pueden llevar a diferentes resultados cuando son usadas en comparaciones.

  1. Del 1 al 4

    let i = 0;
    while (++i < 5) alert( i );

    El primer valor es i = 1, porque ++i primero incrementa i y luego retorna el valor nuevo. Así que la primera comparación es 1 < 5 y el alert muestra 1.

    Entonces siguen 2, 3, 4… – los valores son mostrados uno tras otro. La comparación siempre usa el valor incrementado, porque ++ está antes de la variable.

    Finalmente, i = 4 es incrementada a 5, la comparación while(5 < 5) falla, y el bucle se detiene. Así que 5 no es mostrado.

  2. Del 1 al 5

    let i = 0;
    while (i++ < 5) alert( i );

    El primer valor es de nuevo i = 1. La forma del sufijo de i++ incrementa i y luego retorna el valor viejo, así que la comparación i++ < 5 usará i = 0 (contrario a ++i < 5).

    Pero la llamada a alert está separada. Es otra declaración, la cual se ejecuta luego del incremento y la comparación. Así que obtiene el i = 1 actual.

    Luego siguen 2, 3, 4…

    Detengámonos en i = 4. La forma del prefijo ++i lo incrementaría y usaría 5 en la comparación. Pero aquí tenemos la forma del sufijo i++. Así que incrementa i a 5, pero retorna el valor viejo. Por lo tanto, la comparación es en realidad while(4 < 5) – verdadero, y el control sigue a alert.

    El valor i = 5 es el último, porque el siguiente paso while(5 < 5) es falso.

importancia: 4

Para cada bucle, anota qué valores mostrará y luego compara las respuestas.

Ambos bucles, ¿muestran en alert los mismos valores?

  1. La forma del sufijo:

    for (let i = 0; i < 5; i++) alert( i );
  2. La forma del prefijo:

    for (let i = 0; i < 5; ++i) alert( i );

La respuesta: de 0a 4 en ambos casos.

for (let i = 0; i < 5; ++i) alert( i );

for (let i = 0; i < 5; i++) alert( i );

Eso puede ser fácilmente deducido del algoritmo de for:

  1. Ejecutar i = 0 una vez antes de todo (comienzo).
  2. Comprobar la condición i < 5.
  3. Si true – ejecutar el cuerpo del bucle alert(i) y luego i++.

El incremento i++ es separado de la comprobación de la condición (2). Es simplemente otra declaración.

El valor retornado por el incremento no es usado aquí, así que no hay diferencia entre i++ y ++i.

importancia: 5

Usa el bucle for para mostrar números pares del 2 al 10.

Ejecutar el demo

for (let i = 2; i <= 10; i++) {
  if (i % 2 == 0) {
    alert( i );
  }
}

Usamos el operador “modulo” % para conseguir el resto y comprobar la paridad.

importancia: 5

Reescribe el código cambiando el bucle for a while sin alterar su comportamiento (la salida debería ser la misma).

for (let i = 0; i < 3; i++) {
  alert( `número ${i}!` );
}
let i = 0;
while (i < 3) {
  alert( `número ${i}!` );
  i++;
}
importancia: 5

Escribe un bucle que solicite un número mayor que 100. Si el usuario ingresa otro número – pídele que ingrese un valor de nuevo.

El bucle debe pedir un número hasta que el usuario ingrese un número mayor que 100 o bien cancele la entrada/ingrese una linea vacía.

Aquí podemos asumir que el usuario solo ingresará números. No hay necesidad de implementar un manejo especial para entradas no numéricas en esta tarea.

Ejecutar el demo

let num;

do {
  num = prompt("Ingresa un número mayor a 100", 0);
} while (num <= 100 && num);

El bucle do..while se repite mientras ambas condiciones sean verdaderas:

  1. La condición num <= 100 – eso es, el valor ingresado aún no es mayor que 100.
  2. La condición && num – es falsa cuando num es null o una cadena de texto vaciá. Entonces el bucle while se detiene.

PD. Si num es null entonces num <= 100 es true, así que sin la segunda condición el bucle no se detendría si el usuario hace click en CANCELAR. Ambas comprobaciones son requeridas.

importancia: 3

Un número entero mayor que 1 es llamado primo si no puede ser dividido sin un resto por ningún número excepto 1 y él mismo.

En otras palabras, n > 1 es un primo si no puede ser divido exactamente por ningún número excepto 1 y n.

Por ejemplo, 5 es un primo, porque no puede ser divido exactamente por 2, 3 y 4.

Escribe el código que muestre números primos en el intervalo de 2 a n.

Para n = 10 el resultado será 2, 3, 5, 7.

PD. El código debería funcionar para cualquier n, no debe estar programado para valores fijos.

Hay muchos algoritmos para esta tarea.

Usemos un bucle anidado.

Por cada i en el intervalo {
  comprobar si i tiene un divisor en 1..i
  si tiene => el valor no es un primo
  si no => el valor es un primo, mostrarlo
}

El código usando una etiqueta:

let n = 10;

nextPrime:
for (let i = 2; i <= n; i++) { // por cada i...

  for (let j = 2; j < i; j++) { // buscar un divisor..
    if (i % j == 0) continue nextPrime; // no es primo, ir al próximo i
  }

  alert( i ); // primo
}

Hay mucho lugar para la mejora. Por ejemplo, podríamos buscar por divisores desde 2 hasta la raíz cuadrada de i. Pero de todas formas, si queremos ser realmente eficientes para intervalos grandes, necesitamos cambiar el enfoque y confiar en matemáticas avanzadas y algoritmos complejos como Criba cuadrática, Criba general del cuerpo de números etc.

Mapa del Tutorial

Comentarios

lea esto antes de comentar…
  • Si tiene sugerencias sobre qué mejorar, por favor enviar una propuesta de GitHub o una solicitud de extracción en lugar de comentar.
  • Si no puede entender algo en el artículo, por favor explique.
  • Para insertar algunas palabras de código, use la etiqueta <code>, para varias líneas – envolverlas en la etiqueta <pre>, para más de 10 líneas – utilice una entorno controlado (sandbox) (plnkr, jsbin, codepen…)