Ejercicios resueltos laboratorio 8 - Algoritmos iterativos - for, while, do-while

Asignatura: Fundamentos de Informática
Especialidad: Electrónica - UPV/EHU
Curso académico: 2013-2014
Profesor: Ismael Etxeberria Agiriano

Resolución 08-001

Este ejercicio es similar al ejercicio resuelto 07-001 del laboratorio anterior salvo que:

Partiendo de la solución anterior, se supone que vamos a querer ejecutar al menos una vez el programa anterior, o lo que es lo mismo, escribir al menos una vez n líneas con la palabra "Hola", por lo que podemos decir que se intuye una estructura do-while para hacerlo una o más veces. Cada vez que terminemos de mostrar el texto "Hola" las veces que sea preciso preguntaremos si el usuario desea volver a repetir el proceso. Este bloque se representa en gris claro en la Figura 8.1.

El bloque de color amarillo pálido es el mismo que vimos en la Figura 7.1 del laboratorio anterior, con la expresión condicional de verificación y el bloque while.

El diagrama de flujo de esta variante sería:


Figura 8.1. Variante utilizando while

La codificación correspondiente a la variante 1 podría ser:


/* 08-001-1.c (versión laboratorio)
 *
 * Leer un número positivo n y escribir la palabra "Hola" n veces.
 * Si el número no es positivo escribir mensaje de error.
 * Al finalizar preguntará si se quiere repetir la operación.
 *
 * Variante 1. Utilizando while.
 */
#include <stdio.h>
#include <stdlib.h>

int main (void)
{
  int i, n;
  char op;

  do {
    printf ("Introduce un número positivo: ");
    scanf ("%d", &n);
    if (n >= 0) {
      i = 1;
      while (i <= n) {
        printf ("Hola\n");
        i++;
      }
    }
    else
      printf ("%d no es un número positivo\n", n);
    printf ("¿Quieres repetir? (s/n): ");
    fflush (stdin); /* Vaciar la entrada, si no queda el carácter '\n' */
    scanf ("%c", &op);
  } while (op == 's' || op == 'S');

  system ("pause");
  return 0;
}

El segundo bloque de lectura (Lee op) para leer el carácter de la respuesta ('s' o 'S') se divide en tres instrucciones C que volvemos a reproducir a continuación:


    printf ("¿Quieres repetir? (s/n): ");
    fflush (stdin); /* Vaciar la entrada, si no queda el carácter '\n' */
    scanf ("%c", &op);

La novedad aquí es la introducción de la llamada fflush(). de stdio.h.

Suele suceder a menudo que tras utilizar la función scanf() queda pendiente de leer el fin de línea ('\n') que valida la entrada de datos. Para evitar que la siguiente llamada a scanf() en busca de un carácter lea ese fin de línea, lo típico es llamar a la función fflush(stdin) que vacía la entrada de datos del programa (stdin) y nos asegura que el carácter leído viene después de pedirlo y no antes, al leer la variable n.

Prueba a quitar la línea fflush() y comprobarás que el programa no se detiene a preguntar nada.

Resolución alternativa 07-001-2

Ya que vimos el ejercicio 07-001 del laboratorio anterior utilizando una estructura while vamos a ver primero este mismo ejercicio con una variante for.

El diagrama de flujo sería:


Figura 8.2. Variante del 07-001 anterior utilizando for

Su codificación correspondiente:


/* 07-001-2.c (versión laboratorio)
 *
 * Leer un número positivo n y escribir la palabra "Hola" n veces.
 * Si el número no es positivo escribir mensaje de error.
 *
 * Variante 2. Utilizando for.
 */
#include <stdio.h>
#include <stdlib.h>

int main (void)
{
  int i, n;

  printf ("Introduce un número positivo: ");
  scanf ("%d", &n);
  if (n >= 0)
    for (i = 1; i <= n; i++)
      printf ("Hola\n");
  else
    printf ("%d no es un número positivo\n", n);

  system ("pause");
  return 0;
}

Resolución 08-001-2

Si combinamos la resolución 08-001-1, que utiliza while con la resolución 07-001-2, que utiliza for obtenemos una variante más apropiada.

Se observa que prácticamente el diagrama de flujo es idéntico si ignoramos los colores. Mediante éstos se explicita que el bloque de inicialización y el bloque de actualización forman parte de sus respectivas cláusulas de la estructura for.

No es de extrañar la idoneidad del for para este ejercicio ya que sabemos de antemano cuántas veces hemos de escribir la palabra "Hola".

El diagrama de flujo sería:


Figura 8.3. Variante de 07-001 utilizando for

Merece la pena prestar especial atención a la transformación que se produce en el código.

La codificación correspondiente a la variante 2 podría ser:


/* 08-001-2.c (versión laboratorio)
 *
 * Leer un número positivo n y escribir la palabra "Hola" n veces.
 * Si el número no es positivo escribir mensaje de error.
 * Al finalizar preguntará si se quiere repetir la operación.
 *
 * Variante 2. Utilizando for.
 */
#include <stdio.h>
#include <stdlib.h>

int main (void)
{
  int i, n;
  char op;

  do {
    printf ("Introduce un número positivo: ");
    scanf ("%d", &n);
    if (n >= 0)
      for (i = 1; i <= n; i++)
        printf ("Hola\n");
    else
      printf ("%d no es un número positivo\n", n);
    printf ("¿Quieres repetir? (s/n): ");
    fflush (stdin); /* Vaciar la entrada, si no queda el carácter '\n' */
    scanf ("%c", &op);
  } while (op == 's' || op == 'S');

  system ("pause");
  return 0;
}

Resolución 08-002-1

La estructura do-while para repetir el proceso una y otra vez ya la hemos visto en el ejemplo anterior por lo que no vamos a detallar su descripción.

Para determinar si un número es primo tenemos que buscar un divisor, es decir, un número que lo divida de manera exacta o, lo que es lo mismo, que la división entera por ese número dé 0. Si encontramos un divisor no será primo y si agotamos todas las posibilidades sin encontrar un divisor entonces sabremos que es primo.

Vamos a utilizar una variable booleana primo para memorizar si el número en cuestión es primo o no. En C no hay variables booleanas, pero eso no nos importa cuando estamos escribiendo el algoritmo.

Mientras no encontremos un divisor podemos decir que el número es primo, por lo que inicializamos primo a Cierto. Dicho de otra manera, un número es primo mientras no se demuestre lo contrario.

Iremos comprobando uno a uno todos los posibles divisores entre 2 y n-1. Si encontramos uno actualizaremos el valor de primo a Falso. En ese caso no queremos seguir, por lo que no sabemos a priori cuántas veces vamos a ejecutar el cuerpo del bucle. Por ello, como la mayoría de los algoritmos de búsqueda, la estructura iterativa más adecuada será el while.

El diagrama de flujo puede ser el mostrado en la Figura 8.4.


Figura 8.4. Variante utilizando while y primo

Se recomienda fijar la atención para distinguir las instrucciones condicionales de las iterativas ya que, en papel, no se distinguen los colores.

En C no existen las constantes booleanas Cierto y Falso por lo que tendremos que definirlas. En el programa hemos utilizado macrodefiniciones (#define) para este fin. Así, toda ocurrencia de Cierto en el programa se susituye por 1 y toda ocurrencia de Falso por 0.

La codificación correspondiente a la variante 1 podría ser:


/* 08-002-1.c (versión laboratorio)
 *
 * Leer un número positivo n y decir si es primo.
 * Si el número no es positivo escribir mensaje de error.
 * Al finalizar preguntará si se quiere repetir la operación.
 *
 * Variante 1. Utilizando while. Variable booleana Cierto y Falso.
 */
#include <stdio.h>
#include <stdlib.h>

#define Cierto 1
#define Falso 0

int main (void)
{
  int i, n;
  int primo;
  char op;

  do {
    printf ("Introduce un número mayor de uno: ");
    scanf ("%d", &n);
    if (n > 1) {
      primo = Cierto;
      i = 2;
      while (i < n && primo) {
        if (n%i == 0)
          primo = Falso;
        i++;
      }
      if (primo) printf ("%d es primo\n", n);
      else       printf ("%d no es primo\n", n);
    }
    else
      printf ("%d no es un número positivo\n", n);
    printf ("¿Quieres repetir? (s/n): ");
    fflush (stdin); /* Vaciar la entrada, si no queda el carácter '\n' */
    scanf ("%c", &op);
  } while (op == 's' || op == 'S');

  system ("pause");
  return 0;
}

Resolución 08-002-2

En esta variante vamos a realizar tres alteraciones:

  1. No utilizamos Cierto y Falso sino 1 y 0
  2. Vamos a utilizar una estructura for con rupturas de secuencia
  3. Vamos a utilizar una sola instrucción de escritura

La utilización de estructuras for para algoritmos de búsqueda como éste tiene partidarios y detractores por lo que vamos a darles igual validez a unos y a otros.

Desde el punto de vista de la programación estructurada no es correcto ya que no se corresponde con el esquema teórico de conocer de antemano el número de iteraciones. Se recomienda no utilizarlo pero siempre nos encontraremos quien lo haga.

La idea de utilizar un estructura for, como se verá en el diagrama de flujo, consiste en suponer que vamos a ejecutar el algoritmo un número máximo de veces n, y si encontramos lo que queremos saldremos directamente mediante una orden break.

Los puristas alegan que las rupturas del flujo de control (break, continue, goto y return indiscriminados) complican la comprensión del programa.

En el diagrama resultante de la figura 8.2 se observa que ya no tenemos que mirar en la condición iterativa si ha dejado de ser primo ya que en ese caso salimos directamente mediante una ruptura de secuencia break.

El diagrama de flujo propuesto es:


Figura 8.5. Variante utilizando for

La utilización de una sola instrucción de escritura está ligada al lenguaje. Se calcula si vamos a añadir la cadena "no " si de da que el número no es primo. Analícese el código correspondiente.

La codificación correspondiente a la variante 2 podría ser:


/* 08-002-2.c (versión laboratorio)
 *
 * Leer un número positivo n y decir si es primo.
 * Si el número no es positivo escribir mensaje de error.
 * Al finalizar preguntará si se quiere repetir la operación.
 *
 * Variante 2. Utilizando for y break. Variable booleana 0 y 1.
 */
#include <stdio.h>
#include <stdlib.h>

int main (void)
{
  int i, n;
  int primo;
  char op;

  do {
    printf ("Introduce un número mayor de uno: ");
    scanf ("%d", &n);
    if (n > 1) {
      primo = 1;
      for (i = 2; i < n; i++) {
        if (n%i == 0) {
          primo = 0;
          break;
        }
      }
      printf ("%d %ses primo\n", n, primo? "":"no ");
    }
    else
      printf ("%d no es un número positivo\n", n);
    printf ("¿Quieres repetir? (s/n): ");
    fflush (stdin); /* Vaciar la entrada, si no queda el carácter '\n' */
    scanf ("%c", &op);
  } while (op == 's' || op == 'S');

  system ("pause");
  return 0;
}

Resolución 08-002-3

En esta última resolución completa vamos a prescindir de la variable booleana primo. En realidad podemos saber si el número es primo mirando cómo hemos salido del bucle. Si el contador ha llegado a su valor máximo quiere decir que hemos agotado todas las posibilidades, es decir, que el número es primo.


Figura 8.6. Variante utilizando for

Se ha querido explicitar que la condición de salida determina si es primo o no en el algoritmo a la hora de escribir el resultado, mostrándose una estructura condicional con dos escrituras. Veremos que en el código hemos utilizado una única instrucción de escritura para simplificar el programa.


/* 08-002-3.c (versión laboratorio)
 *
 * Leer un número positivo n y decir si es primo.
 * Si el número no es positivo escribir mensaje de error.
 * Al finalizar preguntará si se quiere repetir la operación.
 *
 * Variante 3. Utilizando for y break. Sin variable booleana.
 */
#include <stdio.h>
#include <stdlib.h>

int main (void)
{
  int i, n;
  char op;

  do {
    printf ("Introduce un número mayor de uno: ");
    scanf ("%d", &n);
    if (n > 1) {
      for (i = 2; i < n; i++)
        if (n%i == 0)
          break;
      printf ("%d %ses primo\n", n, i==n? "":"no ");
    }
    else
      printf ("%d no es un número positivo\n", n);
    printf ("¿Quieres repetir? (s/n): ");
    fflush (stdin); /* Vaciar la entrada, si no queda el carácter '\n' */
    scanf ("%c", &op);
  } while (op == 's' || op == 'S');

  system ("pause");
  return 0;
}

Resoluciones 08-002-4 y 08-002-5

Podemos añadir dos nuevas variantes sin utilizar rupturas de secuencia. La primera con while que no utiliza variables booleaas:


/* 08-002-4.c (versión laboratorio)
  ...
    if (n > 1) {
      i = 2;
      while (i < n && n%i)
        i++;
      printf ("%d %ses primo\n", n, i==n? "":"no ");
    }
    else
  ...

La segunda con for, pero en un modo falso ya que tiene función while, en cuanto a que no sabemos de antemano el número de iteraciones.

El código será (cuidado, que no tiene cuerpo):


/* 08-002-5.c
  ...
    if (n > 1) {
      for (i = 2; i < n && n%i; i++) ; /* Ojo: sin cuerpo */
      printf ("%d %ses primo\n", n, i==n? "":"no ");
    }
    else
  ...