Ir al contenido principal

Bucle while y do-while

Introducción

Los bucles son estructuras fundamentales en programación que nos permiten ejecutar un bloque de código repetidamente mientras se cumpla una condición determinada. En JavaScript, los bucles while y do-while son dos de las estructuras de repetición más básicas y versátiles. Estos bucles nos ayudan a automatizar tareas repetitivas, procesar colecciones de datos y crear algoritmos eficientes sin necesidad de duplicar código. En este artículo, exploraremos cómo funcionan estos bucles, sus diferencias principales y los escenarios más adecuados para utilizar cada uno.

Sintaxis y funcionamiento del bucle while

El bucle while es una estructura de control que ejecuta un bloque de código mientras una condición especificada sea verdadera. Su sintaxis es la siguiente:

while (condicion) {
    // Código a ejecutar mientras la condición sea verdadera
}

El funcionamiento es sencillo:

  1. Primero se evalúa la condición
  2. Si la condición es verdadera (true), se ejecuta el bloque de código
  3. Una vez ejecutado el bloque, se vuelve a evaluar la condición
  4. Este proceso se repite hasta que la condición sea falsa (false)

Veamos un ejemplo básico:

let contador = 1;

while (contador <= 5) {
    console.log(`Número: ${contador}`);
    contador++; // Incrementamos el contador en cada iteración
}

La salida de este código sería:

Número: 1
Número: 2
Número: 3
Número: 4
Número: 5

En este ejemplo:

  1. Iniciamos con contador = 1
  2. En cada iteración comprobamos si contador <= 5
  3. Si es verdadero, mostramos el valor y aumentamos el contador
  4. Cuando contador llega a 6, la condición contador <= 5 se vuelve falsa y el bucle termina

Sintaxis y funcionamiento del bucle do-while

El bucle do-while es similar al while, pero con una diferencia crucial: el bloque de código se ejecuta al menos una vez, independientemente de si la condición es verdadera o falsa. Su sintaxis es:

do {
    // Código a ejecutar al menos una vez
} while (condicion);

El funcionamiento es el siguiente:

  1. Primero se ejecuta el bloque de código
  2. Después se evalúa la condición
  3. Si la condición es verdadera, se vuelve a ejecutar el bloque
  4. Este proceso se repite hasta que la condición sea falsa

Veamos el mismo ejemplo anterior pero utilizando do-while:

let contador = 1;

do {
    console.log(`Número: ${contador}`);
    contador++;
} while (contador <= 5);

La salida de este código sería idéntica a la del ejemplo anterior:

Número: 1
Número: 2
Número: 3
Número: 4
Número: 5

Diferencias entre while y do-while

La principal diferencia entre ambos bucles es el momento en que se evalúa la condición:

  1. Evaluación inicial vs. evaluación posterior:

    • En while, la condición se evalúa antes de ejecutar el bloque
    • En do-while, la condición se evalúa después de ejecutar el bloque
  2. Ejecución garantizada:

    • Con while, si la condición es falsa desde el principio, el bloque nunca se ejecuta
    • Con do-while, el bloque siempre se ejecuta al menos una vez

Para ilustrar esta diferencia, veamos un ejemplo con una condición falsa desde el inicio:

// Ejemplo con while
let contador = 10;

while (contador < 5) {
    console.log(`Número (while): ${contador}`);
    contador++;
}

// Ejemplo con do-while
contador = 10;

do {
    console.log(`Número (do-while): ${contador}`);
    contador++;
} while (contador < 5);

La salida de este código sería:

Número (do-while): 10

En este caso, el bucle while no ejecuta el bloque ni una sola vez porque la condición es falsa desde el principio (10 < 5 es falso). En cambio, el bucle do-while ejecuta el bloque una vez antes de evaluar la condición.

Condiciones de terminación

Una parte crucial al utilizar bucles es asegurarse de que eventualmente terminen. Para ello, es importante establecer condiciones de terminación adecuadas:

En bucles while

let contador = 1;
const limite = 5;

while (contador <= limite) {
    console.log(`Iteración ${contador}`);
    contador++; // Esta línea es fundamental para que el bucle termine
}

En este ejemplo, contador++ es la condición de terminación. Sin esta línea, contador siempre sería 1 y el bucle nunca terminaría.

En bucles do-while

let numero = 100;

do {
    console.log(`Número: ${numero}`);
    numero = Math.floor(numero / 2); // Reducimos el número a la mitad (redondeado)
} while (numero > 0);

En este caso, dividimos el número por 2 en cada iteración. Como eventualmente cualquier número positivo llegará a 0 mediante esta operación, el bucle terminará.

Prevención de bucles infinitos

Un bucle infinito ocurre cuando la condición de un bucle nunca se vuelve falsa. Estos bucles son problemáticos porque pueden bloquear el programa, consumir recursos y, en un navegador web, causar que la página deje de responder.

// Ejemplo de bucle infinito (¡NO EJECUTAR!)
while (true) {
    console.log("Este bucle nunca terminará");
}

Para evitar bucles infinitos, sigue estas recomendaciones:

  1. Asegúrate de que la condición pueda llegar a ser falsa: Debe existir algún camino para que la condición se evalúe como false.

  2. Modifica las variables de la condición dentro del bucle: Si la condición depende de una variable, esta debe modificarse dentro del bucle en dirección a hacer que la condición sea falsa.

  3. Considera usar una condición de escape: En bucles complejos, a veces es útil tener una condición adicional que permita salir del bucle.

// Bucle con condición de escape
let contador = 0;
const maximo = 1000; // Límite de seguridad

while (condicionCompleja()) {
    // Código del bucle
    
    contador++;
    if (contador >= maximo) {
        console.log("Posible bucle infinito detectado, saliendo...");
        break; // Forzamos la salida del bucle
    }
}

Casos de uso apropiados para cada tipo

Bucle while

El bucle while es apropiado cuando:

  1. No sabemos de antemano cuántas iteraciones necesitamos:
// Generación de número aleatorio hasta obtener un 6
let dado;

while (dado !== 6) {
    dado = Math.floor(Math.random() * 6) + 1;
    console.log(`Tirada: ${dado}`);
}

console.log("¡Has sacado un 6!");
  1. Procesamiento de datos hasta encontrar una condición:
// Búsqueda de un elemento en un array
const numeros = [4, 2, 8, 5, 1, 9, 3];
let indice = 0;
let encontrado = false;
const buscar = 5;

while (indice < numeros.length && !encontrado) {
    if (numeros[indice] === buscar) {
        encontrado = true;
        console.log(`Número ${buscar} encontrado en la posición ${indice}`);
    }
    indice++;
}

if (!encontrado) {
    console.log(`El número ${buscar} no está en el array`);
}

Bucle do-while

El bucle do-while es más adecuado cuando:

  1. Necesitamos ejecutar el código al menos una vez:
// Simulación de un menú interactivo
let opcion;

do {
    console.log("\nMenú de opciones:");
    console.log("1. Ver perfil");
    console.log("2. Editar configuración");
    console.log("3. Cerrar sesión");
    console.log("0. Salir");
    
    // En un entorno real, opcion vendría de una entrada del usuario
    opcion = prompt("Seleccione una opción (0-3):");
    
    // Procesamiento de la opción seleccionada
    switch (opcion) {
        case "1":
            console.log("Mostrando perfil...");
            break;
        case "2":
            console.log("Editando configuración...");
            break;
        case "3":
            console.log("Cerrando sesión...");
            break;
        case "0":
            console.log("Saliendo del programa...");
            break;
        default:
            console.log("Opción no válida, intente de nuevo.");
    }
} while (opcion !== "0");
  1. Validación de entrada de usuario:
// Solicitar un número dentro de un rango
let numero;

do {
    // En un entorno real, esto usaría prompt en el navegador
    numero = parseInt(prompt("Introduce un número entre 1 y 10:"));
    
    if (isNaN(numero)) {
        console.log("Por favor, introduce un número válido.");
    } else if (numero < 1 || numero > 10) {
        console.log("El número debe estar entre 1 y 10.");
    }
} while (isNaN(numero) || numero < 1 || numero > 10);

console.log(`Has introducido el número ${numero}`);

Ejemplos prácticos y patrones comunes

Ejemplo 1: Cálculo de factorial

function calcularFactorial(n) {
    if (n < 0) return "No existe el factorial de números negativos";
    if (n === 0) return 1;
    
    let factorial = 1;
    let i = 1;
    
    while (i <= n) {
        factorial *= i;
        i++;
    }
    
    return factorial;
}

console.log(calcularFactorial(5)); // Muestra: 120

Ejemplo 2: Generador de secuencia Fibonacci

function generarFibonacci(n) {
    if (n <= 0) return [];
    if (n === 1) return [0];
    if (n === 2) return [0, 1];
    
    const secuencia = [0, 1];
    let i = 2;
    
    while (i < n) {
        const siguienteNumero = secuencia[i - 1] + secuencia[i - 2];
        secuencia.push(siguienteNumero);
        i++;
    }
    
    return secuencia;
}

console.log(generarFibonacci(10)); 
// Muestra: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Ejemplo 3: Juego de adivinanza con do-while

function jugarAdivinanza() {
    const numeroSecreto = Math.floor(Math.random() * 100) + 1;
    let intentos = 0;
    let adivinado = false;
    let intento;
    
    console.log("¡Bienvenido al juego de adivinanzas!");
    console.log("Intenta adivinar un número entre 1 y 100.");
    
    do {
        // En un entorno real, intento vendría de una entrada del usuario
        intento = parseInt(prompt(`Intento #${intentos + 1}. Introduce un número:`));
        intentos++;
        
        if (isNaN(intento)) {
            console.log("Por favor, introduce un número válido.");
        } else if (intento < numeroSecreto) {
            console.log("El número es mayor. Intenta de nuevo.");
        } else if (intento > numeroSecreto) {
            console.log("El número es menor. Intenta de nuevo.");
        } else {
            adivinado = true;
            console.log(`¡Felicidades! Has adivinado el número ${numeroSecreto} en ${intentos} intentos.`);
        }
    } while (!adivinado);
    
    return intentos;
}

Ejemplo 4: Procesamiento de array con while

function encontrarValoresUnicos(array) {
    if (!Array.isArray(array) || array.length === 0) {
        return [];
    }
    
    const valoresUnicos = [];
    let i = 0;
    
    while (i < array.length) {
        const elemento = array[i];
        
        // Verificar si el elemento ya está en valoresUnicos
        let j = 0;
        let existe = false;
        
        while (j < valoresUnicos.length && !existe) {
            if (valoresUnicos[j] === elemento) {
                existe = true;
            }
            j++;
        }
        
        // Si no existe, añadirlo a valoresUnicos
        if (!existe) {
            valoresUnicos.push(elemento);
        }
        
        i++;
    }
    
    return valoresUnicos;
}

const numeros = [1, 2, 3, 2, 4, 1, 5, 3, 6];
console.log(encontrarValoresUnicos(numeros)); 
// Muestra: [1, 2, 3, 4, 5, 6]

Resumen

Los bucles while y do-while son estructuras fundamentales para crear código que debe repetirse bajo ciertas condiciones. Aunque ambos son similares, su principal diferencia radica en el momento de evaluación de la condición:

  • while: Evalúa la condición antes de ejecutar el bloque. Si la condición es falsa desde el principio, el bloque nunca se ejecuta.
  • do-while: Evalúa la condición después de ejecutar el bloque. Garantiza que el bloque se ejecute al menos una vez.

La elección entre uno u otro dependerá del contexto específico:

  • Usa while cuando no sepas de antemano si necesitas ejecutar el código ni una sola vez
  • Usa do-while cuando necesites garantizar que el código se ejecute al menos una vez

En ambos casos, es crucial establecer condiciones de terminación claras para evitar bucles infinitos, asegurándote de que las variables involucradas en la condición se modifiquen dentro del bucle en la dirección correcta.

Estos bucles son especialmente útiles en situaciones donde el número de iteraciones no es conocido de antemano, como procesamiento de datos hasta encontrar un valor específico, interacción con el usuario, o algoritmos recursivos. Dominar estos bucles te permitirá escribir código más eficiente y elegante para resolver una amplia variedad de problemas de programación.