Next Up Previous Hi Index

Chapter 9

Tuplas

9.1 Mutabilidad y tuplas

Hasta ahora, ha visto dos 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:

>>> tupla = 'a', 'b', 'c', 'd', 'e'

Aunque no es necesario, la convención dice que hay que encerrar las tuplas entre paréntesis:

>>> tupla = ('a', 'b', 'c', 'd', 'e')

Para crear una tupla con un solo elemento, debemos incluir una coma final:

>>> t1 = ('a',)
>>> type(t1)
<type 'tuple'>

Sin la coma, Python trata (\'a') como una cadena entre paréntesis:

>>> t2 = ('a')
>>> type(t2)
<type 'string'>

Dejando a un lado las cuestiones de sintaxis, las operaciones sobre las tuplas son las mismas que sobre las listas. El operador índice selecciona un elemento de la tupla.

>>> tupla = ('a', 'b', 'c', 'd', 'e')
>>> tupla[0]
'a'

Y el operador de porción selecciona un intervalo de elementos.

>>> tupla[1:3]
('b', 'c')

Pero si intentamos modificar uno de los elementos de la tupla provocaremos un error:

>>> tupla[0] = 'A'
TypeError: object doesn't support item assignment

Por supuesto, incluso aunque no podamos modificar los elementos de una tupla, podemos sustituir una tupla por otra diferente:

>>> tupla = ('A',) + tupla[1:]
>>> tupla
('A', 'b', 'c', 'd', 'e')

9.2 Asignación de 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 a y b:

>>> temp = a
>>> a = b
>>> b = temp

Si tenemos que hacer esto a menudo, esta aproximación resulta aparatosa. Python proporciona una forma de asignación de tuplas que soluciona este problema elegantemente:

>>> a, b = b, a

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:

>>> a, b, c, d = 1, 2, 3
ValueError: unpack tuple of wrong size

9.3 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:

def intercambio(x, y):
  return y, x

Luego podemos asignar el valor de retorno a una tupla con dos variables:

a, b = intercambio(a, b)

En este caso, no hay ninguna ventaja en convertir intercambio en una función. De hecho, existe un peligro al intentar encapsular intercambio, y es el tentador error que sigue:

def intercambio(x, y):      # versión incorrecta
  x, y = y, x

Si llamamos a esta función así:

intercambio(a, b)

a y x son alias del mismo valor. Cambiar x dentro de intercambio hace que x se refiera a un valor diferente, pero no tiene efecto alguno sobre a en __main__. De forma similar, cambiar y no tiene efecto sobre 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.

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.

9.4 Números aleatorios

La mayor parte de los programas hacen lo mismo cada vez que los ejecutamos, por lo que se dice que son 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 pseudoaleatorios, que no son verdaderamente aleatorios en un sentido matemático, pero servirán para nuestros propósitos.

El módulo random contiene una función llamada random que devuelve un número en coma flotante entre 0,0 y 1,0. Cada vez que usted llama a random obtiene el siguiente número de una larga serie. Para ver un ejemplo, ejecute este bucle:

import random

for i in range(10):
  x = random.random()
  print x

Para generar un número aleatorio entre 0,0 y un límite superior como maximo, multiplique x por maximo.

Como ejercicio, genere un número aleatorio entre minimo y maximo.
Como ejercicio adicional, genere un número aleatorio entero entre minimo y maximo, incluyendo ambos extremos.

9.5 Lista de números aleatorios

El primer paso es generar una lista de valores aleatorios. listaAleatorios acepta un parámetro entero y devuelve una lista de números aleatorios de la longitud dada. Comienza con una lista de 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:

def listaAleatorios(n):
  s = [0] * n
  for i in range(n):
    s[i] = random.random()
  return s

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.

>>> listaAleatorios(8)
0.15156642489
0.498048560109
0.810894847068
0.360371157682
0.275119183077
0.328578797631
0.759199803101
0.800367163582

Se supone que los números generados por 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.

9.6 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 7.8 escribimos un programa que recorría una cadena de texto 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:

cuenta = 0
for car in fruta:
  if car == 'a':
    cuenta = cuenta + 1
print cuenta

El primer paso es sustituir fruta con lista y car con 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 num está entre los valores de minimo y maximo.

cuenta = 0
for num in lista
  if minimo < num < maximo:
    cuenta = cuenta + 1
print cuenta

El último paso es encapsular este código en una función llamada enElBalde. Los parámetros son la lista y los valores minimo y maximo.

def enElBalde(lista, minimo, maximo):
  cuenta = 0
  for num in lista:
    if minimo < num < maximo:
      cuenta = cuenta + 1
  return cuenta

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 coincidencia de esquemas. Si se encuentra trabajando en un problema que ya solucionó, reutilice la solución.

9.7 Muchos baldes

Tal como aumenta el número de baldes, enElBalde se hace un tanto difícil de manejar. Con dos baldes, no está mal:

bajo = enElBalde(a, 0.0, 0.5)
alto = enElBalde(a, 0.5, 1)

Pero con cuatro baldes ya es aparatoso.

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)

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 numBaldes, la anchura de cada balde es 1.0 / numBaldes.

Usaremos un bucle para calcular el intervalo de cada balde. La variable del bucle, i, cuenta de 1 a numBaldes-1:

anchuraBalde = 1.0 / numBaldes
for i in range(numBaldes):
  minimo = i * anchuraBalde
  maximo = minimo + anchuraBalde
  print minimo, "hasta", maximo

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 anchuraBalde.

Con numBaldes = 8, la salida es:

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

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 ahora 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 enElBalde y actualizar el i-ésimo elemento de la lista:

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

Con una lista de 1000 valores, este código genera esta lista de baldes:

[138, 124, 128, 118, 130, 117, 114, 131]

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.

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.

9.8 Una solución en una sola pasada

Aunque este programa funciona, no es tan eficiente como podría ser. Cada vez que llama a 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, i, y lo multiplicamos por la 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 anchuraBalde en lugar de multiplicar. La suposición es correcta.

Como anchuraBalde = 1.0 / numBaldes, dividir por anchuraBalde es lo mismo que multiplicar por numBaldes. Si multiplicamos un número del intervalo que va de 0,0 a 1,0 por numBaldes, obtenemos un número del intervalo entre 0,0 y numBaldes. Si redondeamos ese número al entero inferior obtendremos exactamente lo que estamos buscando, un índice de balde:

numBaldes = 8
baldes = [0] * numBaldes
for i in lista:
  indice = int(i * numBaldes)
  baldes[indice] = baldes[indice] + 1

Usamos la función 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 len(baldes)-1)?

Una lista como baldes que contiene conteos del número de valores en cada intervalo se llama histograma.

Como ejercicio, escriba una función llamada 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.

9.9 Glosario

tipo inmutable
Un tipo en el cual los elementos no se puede modificar. Las asignaciones de elementos o porciones de tipos inmutables provocan un error.
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.
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.
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.
determinista
Un programa que hace lo mismo todas las veces que se ejecuta.
pseudoaleatorio
Una secuencia de números que parece ser aleatoria pero que en realidad es el resultado de un cálculo determinista.
histograma
Una lista de enteros en la que cada elemento cuenta el número de veces que ocurre algo.
coincidencia de esquemas
Un plan de desarrollo de programas que implica la identificación de un esquema computacional conocido y el copiado de la solución para un problema similar.


Next Up Previous Hi Index