Cesar Systems

Herramientas Informaticas

Capítulo 6

Iteración

6.1. Asignación múltiple

Es posible que haya descubierto que es posible hacer mas de una asignación a una misma variable. El efecto de la nueva asignación es redirigir la variable de manera que deje de remitir al valor antiguo y empiece a remitir al valor nuevo.

   1: bruno = 5

   2: print bruno,

   3: bruno = 7

   4: print bruno

La salida del programa es 5 7, ya que la primera vez que imprimimos Bruno su valor es 5, y la segunda vez su valor es 7. La coma al final de la primera sentencia print impide que se imprima una nueva l³nea en ese punto, por eso ambos valores aparecen en la misma línea.

He aquí el aspecto de una asignacion multiple en un diagrama de estado:

Sin título

Cuando hay asignaciones multiples a una variable, es especialmente importante distinguir entre una sentencia de asignacion y una sentencia de igualdad. Puesto que Python usa el s³mbolo = para la asignacion, es tentador interpretar una sentencia como a = b como sentencia de igualdad. Pero no lo es.

Para empezar, la igualdad es commutativa, y la asignacion no lo es. Por ejemplo en matematicas si a = 7 entonces 7 = a. Pero en Python la sentencia a = 7 es legal, y 7 = a no lo es.

Y lo que es mas, en matematicas, una sentencia de igualdad es verdadera todo el tiempo. Si a = b ahora, entonces a siempre sera igual a b. En Python, una sentencia de asignacion puede hacer que dos variables sean iguales, pero no tienen por que quedarse así.

   1: a = 5

   2: b = a # a y b son ahora iguales

   3: a = 3 # a y b ya no son iguales

La tercera línea cambia el valor de a pero no cambia el valor de b, y por lo tanto ya dejan de ser iguales. En algunos lenguajes de programacion, se usa para la asignación un símbolo distinto, como <- o como :=, para evitar la confusion.

Aunque la asignacion multiple es util a menudo, debe usarla con cuidado. Si los valores de las variables van cambiando constantemente en distintas partes del programa, podría suceder que el codigo sea difícil de leer y mantener.

6.2. La sentencia while

Una de las tareas para las que los computadores se usan con frecuencia es la automatización de tareas repetitivas. Repetir tareas similares o idénticas es algo que los computadores hacen bien y las personas no hacen tan bien.

Hemos visto dos programas, nLineas y cuenta atrás, que usan la recursividad para llevar a cabo la repetición, que también se llama iteración.

Por ser la iteración tan habitual, Python proporciona como lenguaje varias características que la hacen mas fácil.

La primera característica que vamos a considerar es la sentencia while.

Este es el aspecto de cuenta atrás con una sentencia while:

   1: def cuenta_atras(n):

   2:     while n > 0:

   3:         print n

   4:         n = n-1

   5:         print "Despegando!"

Como eliminamos la llamada recursiva, esta funcion no es recursiva.
Casi podía leer una sentencia while como si fuera ingles (castellano “mientras”). Quiere decir que “Mientras n sea mayor que cero, continua mostrando el valor de n y despues restandole 1 al valor de n. Cuando llegues a cero, muestra la palabra “¡Despegando!”.

Mas formalmente, el flujo de ejecucion de una sentencia while es el siguiente:

  • Evaluar la condicion, devolviendo 0 o 1.
  • Si la condicion es falsa (0), salir de la sentencia while y continuar la ejecución en la siguiente sentencia.
  • Si la condicion es verdadera (1), ejecutar cada una de las sentencias en el cuerpo del bucle while, y luego volver al paso 1.

El cuerpo esta formado por todas las sentencias bajo el encabezado que tienen el mismo sangrado.

Este tipo de flujo de llama bucle porque el tercer paso vuelve de nuevo arriba.

Nótese que si la condicion es falsa la primera vez que se atraviesa el bucle, las sentencias del interior del bucle no se ejecutan nunca.

El cuerpo del bucle debe cambiar el valor de una o mas variables de manera que, llegado el momento, la condicion sea falsa y el bucle termine. En caso contrario, el bucle se repetira para siempre, que es lo que se llama bucle infinito. Una infinita fuente de diversion para los científicos informaticos es la observacion de que las instrucciones del champu lavar, aclarar, repetir”, son un bucle infinito.

En el caso de cuenta atras, podemos probar que el bucle terminara porque sabemos que el valor de n es finito, y podemos ver que el valor de n disminuye cada vez que se atraviesa el bucle (cada iteracion), de manera que ea la larga tenemos que llegar a cero. En otros casos no es tan facil decirlo:

   1: def secuencia(n):

   2:     while n != 1:

   3:         print n,

   4:         if n%2 == 0: # n es par

   5:             n = 

   6:             n/2

   7:         else: # n es impar

   8:             n = n*3+1

   9:         

La condicion de este bucle es n != 1, de manera que el bucle continuara hasta que n sea 1, que hara que la condicion sea falsa.
En cada iteracion, el programa muestra como salida el valor de n y luego comprueba si es par o impar. Si es par, el valor de n se divide entre dos. Si es impar, el valor se sustituye por 3n+1. Por ejemplo, si el valor de comienzo (el argumento pasado a la secuencia) es 3, la secuencia resultante es 3, 10, 5, 16, 8, 4, 2, 1.

Puesto que n a veces aumenta y a veces disminuye, no hay una prueba obvia de que n alcance alguna vez el valor 1, o de que el programa vaya a terminar. Para algunos valores particulares de n, podemos probar la terminacion. Por ejemplo, si el valor de inicio es una potencia de dos, entonces el valor de n sera par cada vez que se pasa a traves del bucle, hasta que lleguemos a 1. El ejemplo anterior acaba con dicha secuencia, empezando por 16.

Dejando aparte valores particulares, la pregunta interesante es si podemos probar que este programa terminara para todos los valores de n.

Hasta la fecha, nadie ha sido capaz de probarlo o negarlo.

Como actividad, reescriba la funcion nLines de la seccion 4.9 utilizando iteracion en lugar de recursividad.

6.3. Tablas

Una de las cosas para las que resultan buenos los bucles es para generar datos tabulares. Antes de que los computadores estuvieran disponibles de forma masiva, la gente tenía que calcular a mano logaritmos, senos, cosenos y otras funciones matemáticas. Para facilitarlo, los libros de matemáticas contenían largas tablas donde aparecían los valores de estas funciones. Confeccionar estas tablas era una tarea lenta y pesada, y el resultado estaba lleno de erratas.

Cuando los computadores aparecieron en escena, una de las primeras reacciones fue ¡Que bueno! Podemos usar los computadores para generar las tablas, así no habrá errores”. Resulto cierto (casi), pero no se vio mas allá. Poco después los computadores y las calculadoras científicas se hicieron tan ubicuas que las tablas resultaron obsoletas.

Bueno, casi. Resulta que para ciertas operaciones, los computadores usan tablas para obtener un valor aproximado, y luego ejecutan cálculos para mejorar la aproximación. En algunos casos, ha habido errores en las tablas subyacentes; el mas famoso estaba en la tabla que el Pentium de Intel usaba para llevar a cabo la división de coma flotante.

Aunque una tabla logarítmica ya no es tan útil como lo fuera antaño, todavía constituye un buen ejemplo de iteración. El siguiente programa muestra una secuencia de valores en la columna izquierda y sus logaritmos en la columna derecha:

x = 1.0

while x < 10.0:

    print x, 't', math.log(x)

    x = x + 1.0

    

El nt representa un caracter de tabulacion.

Tal como se van mostrando en la pantalla caracteres y cadenas, un señalador invisible llamado cursor lleva la cuenta de donde ira el proximo caracter. Tras una sentencia print, el cursor va normalmente al principio de la línea siguiente.

El caracter de tabulacion hace que el cursor se desplace a la derecha hasta que alcance uno de los marcadores de tabulacion. Los tabuladores son utiles para alinear columnas de texto, como en la salida del programa anterior:

1.0 0.0
2.0 0.69314718056
3.0 1.09861228867
4.0 1.38629436112
5.0 1.60943791243
6.0 1.79175946923
7.0 1.94591014906
8.0 2.07944154168
9.0 2.19722457734

Si estos valores le parecen raros, recuerde que la funcion log usa como base e.

Debido a que las potencias de dos son muy importantes en las ciencias de la computación, generalmente querremos hallar los logaritmos en relacion con la base dos. Para llevarlo a cabo, podemos usar la siguiente formula:
log2 x =(logex/loge2)
(6.1)

Cambiar la sentencia de salida a:

   1: print x, 't', math.log(x)/math.log(2.0)

devuelve

1.0 0.0
2.0 1.0
3.0 1.58496250072
4.0 2.0
5.0 2.32192809489
6.0 2.58496250072
7.0 2.80735492206
8.0 3.0
9.0 3.16992500144

Podemos ver que 1, 2, 4 y 8 son potencias de dos, porque sus logaritomos de base 2 son numeros enteros. Si quisieramos encontrar los logaritmos de otras potencias de dos, podr³amos modificar el programa de la siguiente manera:

   1: x = 1.0

   2: while x < 100.0:

   3:     print x, 't', math.log(x)/math.log(2.0)

   4:     x = x * 2.0

Ahora, en lugar de añadir algo a x cada vez que atravesamos el bucle, que
devuelve una secuencia aritmetica, multiplicamos x por algo, devolviendo una
secuencia geometrica. El resultado es:
1.0 0.0
2.0 1.0
4.0 2.0
8.0 3.0
16.0 4.0
32.0 5.0
64.0 6.0

Debido a que usamos caracteres de tabulacion entre las columnas, la posicion de la segunda columna no depende del numero de dígitos de la primera columna.

Las tablas logarítmicas quizás ya no sean utiles, pero conocer las potencias de dos no ha dejado de serlo para los científicos informaticos.

Como actividad, modifique el programa para que muestre las potencias de dos hasta 65536 (es decir, 216). Imprímala y memorícela.

El caracter de barra invertida en ‘t’ indica el principio de una secuencia de escape. Las secuencias de escape se usan para representar caracteres invisibles como tabulaciones y retornos de carro. La secuencia n representa un retorno de carro.

Una sentencia de escape puede aparecer en cualquier lugar de una cadena; en el ejemplo, la secuencia de escape del tabulador es la unica de la cadena.
¿Como cree que puede representar una barra invertida en una cadena?

Como ejercicio, escriba un unica cadena que presente esta salida.

6.4. Tablas de dos dimensiones

Una tabla de dos dimensiones es una tabla en la que Usted elige una fila y una columna y lee el valor de la intersección. Un buen ejemplo es una tabla de multiplicar. Supongamos que desea imprimir una tabla de multiplicar para los valores del 1 al 6.

Una buena manera de comenzar es escribir un bucle sencillo que imprima los múltiplos de 2, todos en una l³nea.

   1: i = 1

   2: while i <= 6:

   3:     print 2*i, 't',

   4:     i = i + 1

   5: print

La primera línea inicializa una variable lllamada i, que actuara como contador, o variable de bucle. Conforme se ejecuta el bucle, el valor de i se incrementa de 1 a 6. Cuando i vale 7, el bucle termina. Cada vez que se atraviesa el bucle,

imprimimos el valor 2*i seguido por tres espacios. De nuevo, la coma de la sentencia print suprime el salto de línea. Despues de completar el bucle, la segunda sentencia print crea una línea nueva.

La salida de este programa es:

2     4     6     8     10     12

Hasta ahora, bien. El siguiente paso es encapsular y generalizar.

6.5. Encapsulado y generalización

Por “encapsulado” generalmente se entiende tomar una pieza de código y envolverla en una función, permitiéndole obtener las ventajas de todo aquello para lo que valen las funciones. Hemos visto dos ejemplos de encapsulado, cuando escribimos imprimeParidad en la Sección 4.5 y esDivisible en la Sección 5.4.

Por “generalización” entendemos tomar algo específico, como imprimir los múltiplos de 2, y hacerlo mas general, como imprimir los múltiplos de cualquier entero.

He aquí una función que encapsula el bucle de la sección anterior y la generaliza para imprimir múltiplos de n.
def imprimeMultiplos(n):

   1: i = 1

   2: while i <= 6:

   3:     print n*i, 't',

   4:     i = i + 1

   5: print

Para encapsular, todo lo que hubimos de hacer fue añadir la primera línea, que declara el nombre de la función y la lista de parametros. Para generalizar, todo lo que tuvimos que hacer fue sustituir el valor 2 por el parametro n.

Si llamamos a esta funcion con el argumento 2, obtenemos la misma salida que antes. Con el argumento 3, la salida es:

   1: 3     6     9     12     15     18

y con argumento 4, la salida es

   1: 4     8     12     16     20     24

A estas alturas es probable que haya adivinado como vamos a imprimir una tabla de multiplicacion: llamaremos a imprimeMultiplos repetidamente con diferentes argumentos. De hecho, podemos a usar otro bucle:

   1: i = 1

   2: while i <= 6:

   3:     imprimeMultiplos(i)

   4:     i = i + 1

Observe hasta que punto este bucle es similar al que hay en el interior de imprimeMultiplos. Todo lo que hicimos fue sustituir la sentencia print por una llamada a una función.

La salida de este programa es una tabla de multiplicación:

   1: 1 2 3 4 5 6

   2: 2 4 6 8 10 12

   3: 3 6 9 12 15 18

   4: 4 8 12 16 20 24

   5: 5 10 15 20 25 30 

   6: 6 12 18 24 30 36

6.6. Más encapsulación

Para dar mas ejemplos de encapsulación, tomaremos el código del final de la Sección 6.5 y lo envolveremos en una función:

   1: def imprimeTablaMult():

   2:     i = 1

   3:     while i <= 6:

   4:         imprimeMultiplos(i)

   5:         i = i + 1

   6:         

El proceso que mostramos aquí es un plan de desarrollo habitual. Se desarrolla gradualmente el codigo añadiendo líneas fuera de cualquier funcion o en el interprete. Cuando conseguimos que funcionen, se extraen y se envuelven en una funcion.

Este plan de desarrollo es especialmente si, al comenzar a escribir, no sabe como dividir el programa en funciones. Este enfoque le permite diseñarlo sobre la marcha.

6.7. Variables locales

Quizá se este preguntando como podemos usar la misma variable tanto en imprimeMultiplos como en imprimeTablaMult.

¿No habrá problemas cuando una de las funciones cambie los valores de la variable?

La respuesta es no, ya que la variable i en imprimeMultiplos y la variable i in imprimeTablaMult no son la misma variable.

Las variables creadas dentro de una función son locales. No puede acceder a una variable local fuera de su función “huésped”. Eso significa que es posible tener múltiples variables con el mismo nombre, siempre que no estén en la misma función.

El diagrama de pila de esta función muestra claramente que las dos variables llamadas i no son la misma variable. Pueden referirse a diferentes valores, y cambiar uno no afecta al otro.

Sin título

El valor de i en imprimeTablaMult va desde 1 hasta 6. En el diagrama, resulta ser 3. El próximo recorrido del bucle será 4. En cada recorrido del bucle,imprimeTablaMult llama a imprimeMultiplos con el valor actual de i como argumento. Ese valor se asigna al parámetro n.

Dentro de imprimeMultiplos, el valor de i va desde 1 hasta 6. En el diagrama, resulta ser 2. Los cambios en esta variable no tienen ningún efecto sobre el valor de i en imprimeTablaMult.

Es habitual y perfectamente legal tener diferentes variables locales con el mismo nombre. En especial, los nombres i, j y k se suelen usar como variables de bucle. Si evita usarlas en una función porque las utilizo en algún otro lugar, probablemente consiga que el programa sea mas difícil de leer.

6.8. Más generalización

Como otro ejemplo de generalización, imagine que desea un programa que imprima una tabla de multiplicación de cualquier tamaño, y no solo la tabla de 6×6. Podría añadir un parámetro a imprimeTablaMult:

   1: def imprimeTablaMult(mayor):

   2: i = 1

   3: while i <= 

   4: mayor:

   5: imprimeMultiplos(i)

   6: i = i + 1

Hemos sustituido el valor 6 con el parametro mayor. Si ahora se llama a imprimeTablaMult con el argumento 7, obtenemos:

   1: 1 2 3 4 5 6

   2: 2 4 6 8 10 12

   3: 3 6 9 12 15 18

   4: 4 8 12 16 20 24

   5: 5 10 15 20 25 30

   6: 6 12 18 24 30 36

   7: 7 14 21 28 35 42

lo que es correcto, excepto por el hecho de que seguramente queremos que la tabla este cuadrada, con el mismo numero de filas que de columnas. Para hacerlo, añadimos otro parametro a imprimeMultiplos, a fin de especificar cuantas columnas tendría que tener la tabla.

Solo para fastidiar, llamaremos tambien a este parametro mayor, para demostrar que diferentes funciones pueden tener parametros con el mismo nombre (al igual que las variables locales). Aquí tenemos el programa completo:

   1: def imprimeMultiplos(n, mayor):

   2:     int i = 1

   3:     while i <= mayor:

   4:         print 

   5:         n*i, 't',

   6:         i = i + 1

   7:         print

   8:     def imprimeTablaMult(mayor):

   9:         int i = 

  10:         1

  11:         while i <= mayor:

  12:         imprimeMultiplos(i, mayor)

  13:         i = i + 1

  14:         

Nótese que al añadir un nuevo parametro, tuvimos que cambiar la primera línea de la funcion (el encabezado de la funcion), y tuvimos tambien que cambiar el lugar donde se llama a la función en imprimeTablaMult.

Según lo esperado, este programa genera una tabla cuadrada de 7×7:

   1: 1 2 3 4 5 6 7

   2: 2 4 6 8 10 12 14

   3: 3 6 9 12 15 18 21

   4: 4 8 12 16 20 24 

   5: 28 5 10 15 20 25 30 35

   6: 6 12 18 24 30 36 42

   7: 7 14 21 28 35 42 49

Cuando generaliza correctamente una funcion, a menudo se encuentra con que el programa resultante tiene capacidades que Usted no pensaba. Por ejemplo, quizá observe que la tabla de multiplicacion es simetrica, porque ab = ba, de manera que todas las entradas de la tabla aparecen dos veces. Puede ahorrar tinta imprimiendo solo la mitad de la tabla. Para hacerlo, solo tiene que cambiar una línea de imprimeTablaMult. Cambie imprimeMultiplos(i, mayor) por imprimeMultiplos(i, i) y obtendra

   1: 1

   2: 2 4

   3: 3 6 9

   4: 4 8 12 16

   5: 5 10 15 20 25

   6: 6 12 18 24 30 36

   7: 7 14 21 28 35 42 49

Como actividad, siga o trace la ejecucion de esta nueva version de imprimeTablaMult para hacerse una idea de como funciona.

6.9. Funciones

Hasta el momento hemos mencionado en alguna ocasión “todas las cosas para las que sirven las funciones”. Puede que ya se este preguntando que cosas son exactamente. He aqu³ algunas de las razones por las que las funciones son útiles:

  • Al dar un nombre a una secuencia de sentencias, hace que su programa sea mas fácil de leer y depurar.
  • Dividir un programa largo en funciones le permite separar partes del programa, depurarlas aisladamente, y luego recomponerlas en un todo.
  • Las funciones facilitan tanto la recursividad como la iteración.
  • Las funciones bien dise~nadas son generalmente útiles para mas de un programa. Una vez escritas y depuradas, pueden reutilizarse.

6.10. Glosario

asignación múltiple: Hacer mas de una asignación a la misma variable durante la ejecución de un programa.

iteración: La ejecución repetida de un conjunto de sentencias por medio de una llamada recursiva a una función o un bucle.

bucle: Sentencia o grupo de sentencias que se ejecutan repetidamente hasta que se cumple una condición de terminación.

bucle infinito: Bucle cuya condición de terminación nunca se cumple.

cuerpo: Las sentencias que hay dentro de un bucle.

variable de bucle: Variable que se usa para determinar la condición de terminación de un bucle.

tabulador: Carácter especial que hace que el cursor se mueva hasta la siguiente marca de tabulación en la línea actual.

nueva línea: Un carácter especial que hace que le cursor se mueva al inicio de la siguiente línea.

cursor: Un marcador invisible que sigue el rastro de donde se imprimirá el siguiente carácter.

secuencia de escape: Carácter de escape (n) seguido por uno o mas caracteres imprimibles, que se usan para designar un carácter no imprimible.

encapsular: Dividir un programa largo y complejo en componentes (como las funciones) y aislar los componentes unos de otros (por ejemplo usando variables locales).

generalizar: Sustituir algo innecesariamente específico (como es un valor constante) con algo convenientemente general (como es una variable o parámetro). La generalización hace el código mas versátil, mas apto para reutilizarse y algunas veces incluso mas fácil de escribir.

plan de desarrollo: Proceso para desarrollar un programa. En este capítulo, hemos mostrado un estilo de desarrollo basado en desarrollar código para hacer cosas simples y específicas, y luego encapsularlas y generalizarlas.

Página 112 de 143

Creado con WordPress & Tema de Anders Norén