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}