![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
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')
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
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.
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.
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.
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.
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.
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.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |