Next Up Previous Hi Index

Chapter 9

Tuplas

9.1 Mutabilidad y tuplas

Hasta ahora, ha visto dos tipos de tipos compuestos: cadenas, que están hechas de caracteres, y listas, que están hechas de elementos de cualquier tipo. Una de las diferencias que señalamos es que los elementos de una lista se pueden modificar, pero los caracteres de una cadena no. En otras palabras, las cadenas son inmutables y las listas son mutables. En Python hay otro tipo llamado tupla que es similar a una lista salvo en que es inmutable. Sintácticamente, una tupla es una lista de valores separados por comas: \begin{verbatim >>> tupla = 'a', 'b', 'c', 'd', 'e' \end{verbatim} \afterverb % Aunque no es necesario, la convención dice que hay que encerrar las tuplas entre paréntesis: \beforeverb \begin{verbatim} >>> tupla = ('a', 'b', 'c', 'd', 'e') \end{verbatim} \afterverb % Para crear una tupla con un solo elemento, debemos incluir una coma final: \beforeverb \begin{verbatim} >>> t1 = ('a',) >>> type(t1) \end{verbatim} \afterverb % Sin la coma, Python trata {\tt ('a')} como una cadena entre paréntesis: \beforeverb \begin{verbatim} >>> t2 = ('a') >>> type(t2) \end{verbatim} \afterverb % Dejando a un lado la sintaxis, las operaciones sobre las tuplas son las mismas que sobre las listas. El operador índice selecciona un elemento de la tupla. \beforeverb \begin{verbatim} >>> tupla = ('a', 'b', 'c', 'd', 'e') >>> tupla[0] 'a' \end{verbatim} \afterverb % Y el operador de corte selecciona un intervalo de elementos. \beforeverb \begin{verbatim} >>> tupla[1:3] ('b', 'c') \end{verbatim} \afterverb % Pero si intentamos modificar uno de los elementos de la tupla provocaremos un error: \index{runtime error} \beforeverb \begin{verbatim} >>> tupla[0] = 'A' TypeError: object doesn't support item assignment \end{verbatim} \afterverb % Por supuesto, incluso aunque no podamos modificar los elementos de una tupla, podemos sustituir una tupla con otra diferente: \beforeverb \begin{verbatim} >>> tupla = ('A',) + tupla[1:] >>> tupla ('A', 'b', 'c', 'd', 'e') \end{verbatim} \afterverb % \section{Asignación de tuplas} \label{asignación de tuplas} \index{asignación de tuplas} \index{asignación!tuplas} De vez en cuando, es útil intercambiar los valores de dos variables. Para hacerlo con sentencias de asignación convencionales debemos usar una variable temporal. Por ejemplo, para intercambiar {\tt a} y {\tt b}: \beforeverb \begin{verbatim} >>> temp = a >>> a = b >>> b = temp \end{verbatim} \afterverb % Si tenemos que hacer esto a menudo, esta aproximación resulta aparatosa. Python proporciona una forma de {\bf asignación de tuplas} que soluciona este problema elegantemente: \beforeverb \begin{verbatim} >>> a, b = b, a \end{verbatim} \afterverb % El lado izquierdo es una tupla de variables, el lado derecho es una tupla de valores. Cada valor se asigna a su respectiva variable. Todas las expresiones del lado derecho se evalúan antes de las asignaciones. Esta característica hace de la asignación de tuplas algo muy versátil. Naturalmente, el número de variables a la izquierda y el número de valores a la derecha deben ser iguales: \beforeverb \begin{verbatim} >>> a, b, c, d = 1, 2, 3 ValueError: unpack tuple of wrong size \end{verbatim} \afterverb % \section{Tuplas como valor de retorno} \index{tuplas} \index{valores!tuplas} \index{valores de retorno!tuplas} \index{funciones!tuplas como valor de retorno} Las funciones pueden devolver tuplas como valor de retorno. Por ejemplo, podríamos escribir una función que intercambie dos parámetros: \beforeverb \begin{verbatim} def intercambio(x, y): return y, x \end{verbatim} \afterverb % Luego podemos asignar el valor de retorno a una tupla con dos variables: \beforeverb \begin{verbatim} a, b = intercambio(a, b) \end{verbatim} \afterverb % En este caso, no hay ninguna ventaja en convertir {\tt intercambio} en una función. De hecho, existe un peligro al intentar encapsular {\tt intercambio}, y es el tentador error que sigue: \beforeverb \begin{verbatim} def intercambio(x, y): # versión incorrecta x, y = y, x \end{verbatim} \afterverb % Si llamamos a esta función así: \beforeverb \begin{verbatim} intercambio(a, b) \end{verbatim} \afterverb % {\tt a} y {\tt x} son alias del mismo valor. Cambiar {\tt x} dentro de {\tt intercambio} hace que {\tt x} se refiera a un valor diferente, pero no tiene efecto alguno sobre {\tt a} en {\tt \_\_main\_\_}. De forma similar, cambiar {\tt y} no tiene efecto sobre {\tt b}. Esta función se ejecuta sin generar un mensaje de error, pero no hace lo que intentamos. Este es un ejemplo de error semántico. \index{error semántico} \begin{quote} {\em A modo de ejercicio, dibuje un diagrama de estados para esta función de manera que pueda ver por qué no trabaja como usted quiere.} \end{quote} \section{Números aleatorios} \index{número aleatorio} \index{número!aleatorio} La mayor parte de los programas hacen lo mismo cada vez que los ejecutamos, por lo que se dice que son {\bf deterministas}. Normalmente el determinismo es una cosa buena, ya que esperamos que un cálculo nos dé siempre el mismo resultado. Para algunas aplicaciones, sin embargo, queremos que el computador sea impredecible. El ejemplo obvio son los juegos, pero hay más. Hacer que un programa sea realmente no determinista resulta no ser tan sencillo, pero hay formas de que al menos parezca no determinista. Una de ellas es generar números aleatorios y usarlos para determinar el resultado del programa. Python proporciona una función interna que genera números {\bf pseudoaleatorios}, que no son verdaderamente aleatorios en un sentido matemático, pero servirán para nuestros propósitos. El módulo {\tt random} contiene una función llamada {\tt random} que devuelve un número en coma flotante entre 0,0 y 1,0. Cada vez que usted llama a {\tt random} obtiene el siguiente número de una larga serie. Para ver un ejemplo, ejecute este bucle: \beforeverb \begin{verbatim} import random for i in range(10): x = random.random() print x \end{verbatim} \afterverb % Para generar un número aleatorio entre 0,0 y un límite superior como {\tt maximo}, multiplique {\tt x} por {\tt maximo}. \begin{quote} {\em Como ejercicio, genere un número aleatorio entre {\tt minimo} y {\tt maximo}.} \end{quote} \begin{quote} {\em Como ejercicio adicional, genere un número aleatorio {\em entero} entre {\tt minimo} y {\tt maximo}, incluyendo ambos extremos.} \end{quote} \section{Lista de números aleatorios} El primer paso es generar una lista de valores aleatorios. {\tt listaAleatorios} acepta un parámetro entero y devuelve una lista de números aleatorios de la longitud dada. Comienza con una lista de {\tt n} ceros. Cada vez que ejecuta el bucle, sustituye uno de los elementos con un número aleatorio. El valor de retorno es una referencia a la lista completa: \beforeverb \begin{verbatim} def listaAleatorios(n): s = [0] * n for i in range(n): s[i] = random.random() return s \end{verbatim} \afterverb % Vamos a probar esta función con una lista de ocho elementos. A la hora de depurar es una buena idea empezar con algo pequeño. \adjustpage{-2} \pagebreak \beforeverb \begin{verbatim} >>> listaAleatorios(8) 0.15156642489 0.498048560109 0.810894847068 0.360371157682 0.275119183077 0.328578797631 0.759199803101 0.800367163582 \end{verbatim} \afterverb % Se supone que los números generados por {\tt random} están distribuidos uniformemente, lo que significa que cada valor es igualmente probable. Si dividimos el intervalo de valores posibles en "baldes" de igual tamaño y contamos el número de veces que un valor cae en cada balde, deberíamos tener más o menos el mismo número en todos. Podemos contrastar esta teoría escribiendo un programa que divida el intervalo en baldes y contando el número de valores en cada uno. \section{Conteo} \index{conteo} Un buen enfoque sobre problemas como éste es dividir el problema en subproblemas que encajen en un esquema computacional que hayamos visto antes. En este caso, queremos recorrer una lista de números y contar el número de veces que un valor cae en un intervalo dado. Eso nos suena. En la Sección~\ref{contador} escribimos un programa que recorría y contaba el número de veces que aparecía una letra determinada. Así, podemos hacerlo copiando el programa viejo y adaptándolo al problema actual. El programa original era: \beforeverb \begin{verbatim} cuenta = 0 for car in fruta: if car == 'a': cuenta = cuenta + 1 print cuenta \end{verbatim} \afterverb % El primer paso es sustituir {\tt fruta} con {\tt lista} y {\tt car} con {\tt num}. Esto no cambia el programa, sólo lo hace más legible. El segundo paso es cambiar la comprobación. No estamos interesados en encontrar letras. Queremos ver si {\tt num} está entre los valores de {\tt minimo} y {\tt maximo}. \beforeverb \begin{verbatim} cuenta = 0 for num in lista if minimo < num < maximo: cuenta = cuenta + 1 print cuenta \end{verbatim} \afterverb % El último paso es encapsular este códico en una función llamada {\tt enElBalde}. Los parámetros son la lista y los valores {\tt minimo} y {\tt maximo}. \beforeverb \begin{verbatim} def enElBalde(lista, minimo, maximo): cuenta = 0 for num in lista: if minimo < num < maximo: cuenta = cuenta + 1 return cuenta \end{verbatim} \afterverb % Copiar y modificar un programa existente nos facilita escribir esta función rápidamente y nos ahorra un montón de tiempo de depuración. Este plan de desarrollo se llama {\bf coincidencia de esquemas}. Si se encuentra trabajando en un problema que ya solucionó, reutilice la solución. \section{Muchos baldes} Tal como aumenta el número de baldes, {\tt enElBalde} se hace un tanto difícil de manejar. Con dos baldes, no está mal: \beforeverb \begin{verbatim} bajo = enElBalde(a, 0.0, 0.5) alto = enElBalde(a, 0.5, 1) \end{verbatim} \afterverb % Pero con cuatro baldes ya es aparatoso. \beforeverb \begin{verbatim} balde1 = enElBalde(a, 0.0, 0.25) balde2 = enElBalde(a, 0.25, 0.5) balde3 = enElBalde(a, 0.5, 0.75) balde4 = enElBalde(a, 0.75, 1.0) \end{verbatim} \afterverb % Hay dos problemas. Uno es que tenemos que inventar nuevos nombres de variables para cada resultado. El otro es que tenemos que calcular el intervalo de cada balde. Empezaremos por solucionar el segundo problema. Si el número de baldes es {\tt numBaldes}, la anchura de cada balde es {\tt 1.0 / numBaldes}. Usaremos un bucle para calcular el intervalo de cada balde. La variable del bucle, {\tt i}, cuenta de 1 a {\tt numBaldes-1}: \beforeverb \begin{verbatim} anchuraBalde = 1.0 / numBaldes for i in range(numBaldes): minimo = i * anchuraBalde maximo = minimo + anchuraBalde print minimo, "hasta", maximo \end{verbatim} \afterverb % Para calcular el límite inferior de cada balde, multiplicamos la variable de bucle por la anchura de balde. El límite superior está a tan sólo una {\tt anchuraBalde}. Con {\tt numBaldes = 8}, la salida es: \beforeverb \begin{verbatim} 0.0 hasta 0.125 0.125 hasta 0.25 0.25 hasta 0.375 0.375 hasta 0.5 0.5 hasta 0.625 0.625 hasta 0.75 0.75 hasta 0.875 0.875 hasta 1.0 \end{verbatim} \afterverb % Puede confirmar que todos los bucles tienen la misma anchura, que no se solapan y que cubren todo el intervalo entre 0,0 y 1,0. Volvamos ahroa al primer problema. Necesitamos un modo de almacenar ocho enteros, usando la variable de bucle para señalarlos uno por uno. En estos momentos debería usted estar pensando "¡Lista!". Debemos crear la lista de baldes fuera del bucle, porque sólo queremos hacerlo una vez. Dentro del bucle, podemos llamar repetidamente a {\tt enElBalde} y actualizar el {\tt i}-ésimo elemento de la lista: \beforeverb \begin{verbatim} numBaldes = 8 baldes = [0] * numBaldes anchuraBalde = 1.0 / numBaldes for i in range(numBaldes): minimo = i * anchuraBalde maximo = minimo + anchuraBalde baldes[i] = enElBalde(lista, minimo, maximo) print baldes \end{verbatim} \afterverb % Con una lista de 1000 valores, este código genera esta lista de baldes: \beforeverb \begin{verbatim} [138, 124, 128, 118, 130, 117, 114, 131] \end{verbatim} \afterverb % Estos números son razonablemente próximos a 125, que es lo que esperábamos Por lo menos, están lo bastante cerca como para que podamos pensar que el generador de números aleatorios funciona. \begin{quote} {\em Como ejercicio, compruebe esta función con listas más largas, y vea si el número de valores en cada balde tiende a equilibrarse.} \end{quote} \section{Una solución en una sola pasada} \index{histograma} Aunque este programa funciona, no es tan eficiente como podría ser. Cada vez que llama a {\tt enElBalde} recorre la lista entera. Con el aumento del número de baldes, llega a ser un montón de recorridos. Sería mejor hacer una sola pasada por la lista y calcular para cada valor el índice del balde en el que cae. Luego podemos incrementar el contador apropiado. En la sección anterior tomamos un índice, {\tt i}, y lo multiplicamos por la {\tt anchuraBalde} para hallar el límite inferior de un balde dado. Ahora queremos tomar un valor del intervalo 0,0 a 1,0 y hallar el índice del balde en el que cae. Como el problema es el inverso del anterior, podemos suponer que deberíamos dividir por {\tt anchuraBalde} en lugar de multiplicar. La suposición es correcta. Como {\tt anchuraBalde = 1.0 / numBaldes}, dividir por {\tt anchuraBalde} es lo mismo que multiplicar por {\tt numBaldes}. Si multiplicamos un número del intervalo que va de 0,0 a 1,0 por {\tt numBaldes}, obtenemos un número del intervalo entre 0,0 y {\tt numBaldes}. Si redondeamos ese número al entero inferior obtendremos exactamente lo que etamos buscando: un índice de balde: \beforeverb \begin{verbatim} numBaldes = 8 baldes = [0] * numBaldes for i in list: indice = int(i * numBaldes) baldes[indice] = baldes[indice] + 1 \end{verbatim} \afterverb % Usamos la función {\tt int} para convertir un número en coma flotante en un entero. ¿Es posible que este cálculo genere un índice que esté fuera del intervalo (tanto negativo como mayor que {\tt len(baldes)-1})? Una lista como {\tt baldes} que contiene conteos del número de valores en cada intervalo se llama {\bf histograma}. \begin{quote} {\em Como ejercicio, escriba una función llamada {\tt histograma} que tome como parámetros una lista y un número de baldes y devuelva un histograma con el número dado de baldes.} \end{quote} \adjustpage{-2} \pagebreak \section{Glosario} \begin{description} \item[tipo inmutable:] Un tipo en el cual los elementos no se puede modificar. Las asignaciones de elementos o cortes de tipos inmutables provocan un error. \item[tipo mutable:] Un tipo de datos en el cual los elementos pueden ser modificados. Todos los tipos mutables son compuestos. Las listas y diccionarios son tipos de datos mutables, las cadenas y las tuplas no. \item[tupla:] Un tipo de secuencia que es similar a una lista excepto en que es inmutable. Las tuplas se pueden usar donde quiera que se necesite un tipo inmutable, como puede ser la clave de un diccionario. \item[asignación de tuplas:] Una asignación de todos los elementos de una tupla usando una única sentencia de asignación. La asignación de tuplas sucede más bien en paralelo que secuencialmente, haciéndola útil para intercambiar valores. \item[determinista:] Un programa que hace lo mismo todas las veces que se ejecuta. \item[pseudoaleatorio:] Una secuencia de números que parece ser aleatoria pero que en realidad es el resultado de un cálculo determinista. \item[histograma:] Una lista de enteros en la que cada elemento cuenta el número de veces que ocurre algo. \item[coincidencia de esquemas:] Un plan de desarrollo de programas que implica la identificación de un esquma computacional conocido y el copiado de la solcuión para un problema similar. \index{tipo mutable} \index{tipo inmutable} \index{tupla} \index{asignación de tuplas} \index{asignmación!tupla} \index{determinista} \index{pseudoaleatorio} \index{histograma} \index{coincidencia de esquemas} \end{description}


Next Up Previous Hi Index