PrInf10: Bucles

De MateWiki
Revisión del 18:40 22 jul 2013 de Herraiz (Discusión | contribuciones) (Tercera versión: número arbitrario de repeticiones)

Saltar a: navegación, buscar
Práctica de Informática
Bucles con for
Práctica anterior Siguiente práctica
Este artículo es un guión de prácticas de Informática


Warning.png Este artículo está en versión beta. El autor de este artículo no lo ha terminado todavía, por favor no lo edites hasta que elimine este mensaje.


En esta práctica vamos a conocer una estructura de programación nueva, que no hemos usado hasta ahora. Se trata del concepto de bucles, que sirven para repetir un conjunto de comandos varias veces. Existen muchas situaciones en las que es útil repetir un conjunto de comandos sin tener que escribirlos varias veces. Por ejemplo, muchos métodos numéricos realizan aproximaciones sucesivas a la solución, repitiendo los mismo comandos, pero con valores que se van actualizando en cada aproximación. La implementación de este tipo de cálculos requiere el uso de bucles.

1 Requisitos previos

Es muy recomendable haber completado satisfactoriamente todas las prácticas anteriores antes de empezar con esta práctica.

El contenido de esta práctica se explica en el siguiente vídeo. Puede ser recomendable visualizar el vídeo antes de realizar esta práctica:

2 Comandos que se aprenderán en esta práctica

En esta práctica vamos a ver qué significan los siguientes comandos de MATLAB / Octave

for sqrt

3 Contenido de la práctica

Como ejemplo para esta práctica vamos a implementar un método numérico para aproximar raíces cuadradas. En Octave UPM ya tenemos un comando que calcula raíces cuadradas, el comando sqrt; nosotros usamos este método como ejemplo para ilustrar qué es un bucle porque es un método sencillo y muy ilustrativo. Obviamente, en situaciones reales, no usaríamos un método propio para calcular la raíz cuadrada, y usaríamos el comando sqrt, que probablemente funcione mucho mejor que el método que vamos a implementar.

3.1 Descripción del método numérico

Existen muchos métodos para aproximar raíces cuadradas. En esta práctica vamos a usar el método babilonio, que aproxima el valor de la raíz transformando sucesivamente un rectángulo en un cuadrado. Cuantas más aproximaciones hagamos, más cercano al cuadrado estaré el rectángulo, y más cercano estará el lado del cuadrado al valor de la raíz cuadrada. El proceso aparece ilustrado en la figura de la derecha.

Transformación sucesiva de un rectángulo en un cuadrado

Supongamos que queremos calcular el valor de la raíz cuadrada de [math]A[/math]. Inicialmente, dibujamos un rectángulo de área [math]A[/math], pero seleccionamos la altura de manera que sea igual a 1 (es decir, [math]W=1[/math] en la figura de la derecha). Por tanto, inicialmente tendremos que [math]L=A[/math], ya que [math]A = L\cdot W[/math].

Transformación sucesiva de un rectángulo en un cuadrado

El valor de [math]\sqrt{A}[/math] tiene que estar entre [math]1[/math] y [math]A[/math]. Por ejemplo, la raíz de [math]10[/math] es [math]3.16 \in [1,10][/math]. Por tanto, el valor [math]\sqrt{A}[/math] estará inicialmente entre [math]L[/math] y [math]W[/math].

En el siguiente paso, vamos a achatar el rectángulo, haciendo su forma más cuadrada usando un valor medio del lado. Es decir, [math]\displaystyle L_1=\frac{L+W}{2}[/math]. Como el área del rectángulo no puede cambiar, nos queda que [math]\displaystyle W_1=\frac{A}{L_1}[/math]. Si hubiéramos partido de este rectángulo inicialmente, tendríamos que el valor de la raíz cuadrada estaría entre los valores de los lados del rectángulo, igual que en el primer rectángulo que hemos usado. Es decir, [math]\sqrt{A} \in [W_1, L_1][/math]. Si repetimos este proceso varias veces, el rectángulo se convertirá cada vez más en un cuadrado, y los valores de [math]W_1[/math] y [math]L_1[/math] estarán más cercanos entre sí y más cercanos al valor de [math]\sqrt{A}[/math].

3.2 Primera versión del programa

Vamos a ver este proceso en código. Como todos los programas, vamos a dividirlo en tres partes: entrada, algoritmo y salida. En este caso, la entrada de datos consiste en el valor cuya raíz queremos calcular:

% Entrada de datos
A = input('Introduce el numero cuya raiz quieres calcular: ');

El siguiente paso será el algoritmo. Como hemos dicho, partimos de un rectángulo con [math]W=1[/math], y hacemos una aproximación tomando el valor medio:

% Algoritmo
% Rectangulo inicial
W = 1;
L = A;
% Primera aproximacion
L1 = (L+W)/2;
W1 = A/L1;

Una vez que hemos realizado la aproximación, podemos mostrar ya el resultado en pantalla. En este caso, tomamos [math]L_1[/math] como el valor aproximado de la raíz:

% Salida de datos
fprintf('La raiz de %.4f es %.4f\n', A, L1);

Es decir, el programa completo quedaría como sigue

% Entrada de datos
A = input('Introduce el numero cuya raiz quieres calcular: ');

% Algoritmo
% Rectangulo inicial
W = 1;
L = A;
% Primera aproximacion
L1 = (L+W)/2;
W1 = A/L1; 

% Salida de datos
fprintf('La raiz de %.4f es %.4f\n', A, L1);

Podemos probar este programa para calcular [math]\sqrt{10}[/math], que hemos visto que era igual a [math]3.16[/math]

Introduce el numero cuya raiz quieres calcular: 10
La raiz de 10.0000 es 5.5000
Lapiz.png Tarea: Escribe el programa anterior en Octave UPM y comprueba que obtienes el mismo resultado al calcular la raíz de 10


Como podemos ver, la aproximación es muy mala. Podemos remediarlo escribiendo varias aproximaciones seguidas. Además, ya que los valores intermedios de la aproximación [math]L_1[/math] y [math]W_1[/math] solo se usan al final, podemos reutilizar continuamente las mismas variables. Si hacemos cuatro aproximaciones, el programa quedaría como sigue:

% Entrada de datos
A = input('Introduce el numero cuya raiz quieres calcular: ');
% Algoritmo
% Rectangulo inicial
W = 1;L = A;
% Cuatro aproximaciones
L = (L+W)/2;
W = A/L;
L = (L+W)/2;
W = A/L;
L = (L+W)/2;
W = A/L; 
L = (L+W)/2;
W = A/L;

% Salida de datos
fprintf('La raiz de %.4f es %.4f\n', A, L);

Si volvemos a probar el programa, ahora la aproximación es bastante mejor

Introduce el numero cuya raiz quieres calcular: 10
La raiz de 10.0000 es 3.1625

Sin embargo, si probamos a calcular [math]\sqrt{1000}[/math], cuyo valor es [math]31.62[/math], obtenemos el siguiente resultado

Introduce el numero cuya raiz quieres calcular: 1000
La raiz de 1000.0000 es 67.7253
Lapiz.png Tarea: Escribe el programa anterior en Octave UPM y comprueba los resultados para calcular ambas raíces


¿Cómo podemos obtener una solución mejor? Ejecutando más aproximaciones. Sin embargo, esto requiere escribir muchas veces los mismos comandos. Si quisiéramos cambiar el número de veces que se ejecutan las aproximaciones, deberíamos modificar el programa para añadir o eliminar comandos. Esta solución no es admisible en programación. En cualquier situación donde nos veamos realizando una tarea repetitiva, y donde hubiera que modificar el programa para cambiar el número de veces que se ejecutan unos comandos, tenemos que tener claro que existe una alternativa de programación para que esas tareas las realice el ordenador. Un ordenador es muy eficiente realizando tareas repetitivas, y no puede pensar. Las personas funcionamos al contrario: somos capaces de pensar, pero somos muy malos realizando tareas muy repetitivas.

Para realizar tareas repetitivas en programación se usa una estructura conocida como bucle. Existen dos tipos principales de bucles:

  • Bucles for
  • Bucles while

En un bucle for tenemos que saber a priori cuántas veces hay que repetir un conjunto de comandos. Se suelen emplear, como veremos más adelante, para recorrer vectores y matrices, cuyas dimensiones son conocidas inicialmente.

Los bucles while se usan cuando conocemos la condición que tiene que cumplirse para repetir un conjunto de comandos. Mientras esa condición se cumpla, se repetirán los comandos. En muchas situaciones no podemos saber a priori cuántas veces hay que ejecutar un conjunto de comandos, pero sí podemos saber qué condición se cumple para que se sigan ejecutando. Por ejemplo, repetir unos comandos mientras el usuario no introduzca un número determinado por teclado. Sabemos que mientras no acierte el número tenemos que repetir los comandos, pero es imposible saber qué números va a introducir el usuario, y por tanto cuántas veces se ejecutará el bucle.

En esta práctica vamos a ver los bucles for, y en la siguiente veremos los bucles while.

3.3 Segunda versión del programa: ahora usamos bucles

En este caso vamos a realizar una implementación que ejecute el conjunto de comandos un número fijo de veces. Por ejemplo, 10 veces. Vamos a ver cómo evitar tener que escribir el conjunto de comandos 10 veces, usando for. La única parte del programa que vamos a cambiar es el algoritmo. La entrada y salida de datos permanecen exactamente igual. El algoritmo se transformaría de esta manera:

% Algoritmo
L = A;
W = 1;
for k=1:10
 L = (L+W)/2;
 W = A/L;
end
Lapiz.png Tarea: Sustituye en el programa completo anterior la parte del algoritmo por este bucle, y comprueba la raíz de 1000 es ahora mucho más cercana al valor teórico


El bucle for tiene dos elementos:

  • La variable que hace de contador, en este caso k, que va desde 1 hasta 10.
  • El cuerpo del bucle, que son los comandos que se repiten. Aunque no es obligatorio, el cuerpo del bucle se suele escribir indentado para facilitar la lectura del código.

En general, la estructura de un bucle for es

for [CONTADOR]=[VECTOR]
  [CUERPO DEL BUCLE]
end

donde [CONTADOR] es un nombre de variable, que recibirá los valores que están dentro de [VECTOR], uno por uno. El [CUERPO DEL BUCLE] se repetirá tantas veces como elementos haya en [VECTOR], y en cada repetición el valor de [CONTADOR] será el elemento correspondiente dentro de [VECTOR].

Si comparamos la estructura genérica con el código que hemos puesto en nuestro programa, vemos que tenemos las siguientes equivalencias:

Elemento Código Comentarios
[CONTADOR]
k
[VECTOR]
1:10
Vector con 10 elementos, desde 1 hasta 10 separados de uno en uno
[CUERPO DEL BUCLE]
 L = (L+W)/2; 
 W = A/L;

En general, la estructura más habitual de un bucle es

for k=1:N
  % Comandos
end

donde N es el número de veces que se repetirá el bucle. Pero es importante entender que esto funciona porque 1:N es un vector, y que en cada iteración k va tomando los valores 1, 2, 3, ... hasta N'.

3.4 Tercera versión: número arbitrario de repeticiones

En la versión anterior hemos evitado tener que escribir el comando 10 veces para hacer 10 aproximaciones. Sin embargo, si queremos cambiar el número de aproximaciones que se realizan tenemos que cambiar el código fuente. ¿Existe el modo de ejecutar el bucle un número arbitrario de veces? Hasta ahora hemos visto que los bucles for requieren que se conozca a priori el número de veces que se va a ejecutar el bucle. Esto parece indicar que no es posible ejecutarlo un número arbitrario de veces. El término a priori se refiere al momento justamente anterior al comienzo del bucle, no al comienzo del programa. Es decir, si sabemos el número de veces que se va a ejecutar el bulce en el momento en el que vaya a comenzar, podemos usar un bucle for Por ejemplo:

nPasos = 10;

% Algoritmo
L = A;
W = 1;
for k=1:nPasos
 L = (L+W)/2;
 W = A/L;
end

Justo antes de comenzar el bucle sabemos que la variable nPasos vale 10, por lo que el bucle se ejecutará 10 veces. No obstante, esta versión todavía requiere que el usuario modifique el código fuente de nuestro programa para cambiar el número de aproximaciones. Este comportamiento no es admisible en un programa que destinemos a un usuario final, por lo que es necesario remediarlo.

¿Cómo podemos hacer que el usuario controle el número de veces que se ejecuta el bucle? Preguntemos al usuario el número de veces directamente:

nPasos = input('Introduce el num. de aproximaciones en el calculo: ');

% Algoritmo
L = A;
W = 1;
for k=1:nPasos
 L = (L+W)/2;
 W = A/L;
end

4 Comprobación de la práctica

Puedes comprobar si tu programa es correcto usando este programa: CompruebaPr09.m

Dale al botón derecho y elige guardar como. Cópialo en el mismo directorio donde hayas guardado la solución de la práctica. Luego puedes comprobar si tu programa es correcto escribiendo

CompruebaPr09('miSolucion');

En este caso, la solución de la práctica está guardada en un fichero llamado miSolucion.m. Cambia el nombre al llamar a CompruebaPr09 si tu programa se llama diferente.

El programa no comprueba ninguna parte del programa desarrollada en alguna práctica anterior, solo comprueba si el mensaje se muestra cuando la temperatura está entre 0ºC y 100 ºC, o si se muestran los mensajes correspondientes cuando la temperatura está por debajo de 0 ºC o por encima de 100 ºC. Se probarán diferentes temperaturas, y se mostrará si el resultado es el esperado (mensaje que empieza por OK). Si en algún caso no es el esperado, mostrará un mensaje que empieza por WARN o por ERR.

5 Ejercicio post-práctica