![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Hasta ahora, has visto dos tipos compuestos: las cadenas, que están hechas con caracteres, y las listas, que están constituidas por elementos de cualquier tipo. Una de las diferencias que comentamos es que los elementos de una lista se pueden modificar, mientras que los caracteres de una cadena no. Dicho de otro modo, las cadenas son inmutables y las listas son mutables.
Existe otro tipo en Python denominado tupla que es parecido a una lista pero es inmutable. Sintácticamente, una tupla es una lista de valores separados por comas:
>>> tupla = 'a', 'b', 'c', 'd', 'e'
Aunque no sea necesario, es habitual encerrar las tuplas entre paréntesis:
>>> tupla = ('a', 'b', 'c', 'd', 'e')
Para crear una tupla con un solo elemento, tenemos que incluir la coma final:
>>> t1 = ('a',)
>>> type(t1)
<type 'tupla'>
Sin la coma, Python considera ('a') como una cadena entre paréntesis:
>>> t2 = ('a')
>>> type(t2)
<type 'cadena'>
Dejando las cuestiones sintácticas a un lado, las operaciones en las tuplas son iguales que las operaciones en las listas. El operador de índices selecciona un elemento de una tupla.
>>> tupla = ('a', 'b', 'c', 'd', 'e')
>>> tupla[0]
'a'
Y el operador de rango selecciona varios elementos.
>>> tupla[1:3]
('b', 'c')
Pero si intentamos modificar uno de los elementos de una tupla, obtenemos un error.
>>> tupla[0] = 'A'
TypeError: object doesn't support item assignment
Por supuesto, incluso si no podemos modificar los elementos de una tupla, podemos reemplazarla con una tupla diferente:
>>> tupla = ('A',) + tupla[1:]
>>> tupla
('A', 'b', 'c', 'd', 'e')
De vez en cuando, es útil intercambiar los valores de dos variables. Con las instrucciones de asignación convencionales, tenemos que utilizar una variable temporal. Por ejemplo, para intercambiar a por b:
>>> temp = a
>>> a = b
>>> b = temp
Si tenemos que hacer esto a menudo, este método resulta engorroso. Por eso, Python ofrece una forma de asignación de tuplas que resuelve este problema de manera clara:
>>> a, b = b, a
El lado izquierdo es una tupla de variables y el lado derecho es una tupla de valores. Cada valor es asignado a su variable correspondiente. Todas las expresiones del lado derecho se evaluan antes que cualquiera de las asignaciones. Esta característica hace que la asignación de tuplas sea bastante flexible.
Naturalmente, el número de variables de la izquierda y el número de valores de la derecha tiene que ser el mismo:
>>> a, b, c, d = 1, 2, 3
ValueError: unpack tuple of wrong size
Las funciones pueden devolver tuplas como valores de retorno. Por ejemplo, podríamos escribir una función que intercambie dos parámetros:
def intercambiar(x, y):
return y, x
Luego podemos asignar el valor de retorno a una tupla con dos variables:
a, b = intercambiar(a, b)
En este caso, no hay demasiadas ventajas en hacer una función intercambiar. De hecho, es peligroso intentar encapsular intercambiar, que es el siguiente error que a veces nos tienta:
def intercambiar(x, y): # versión incorrecta
x, y = y, x
Si llamamos la función de esta manera:
intercambiar(a, b)
entonces a y x son alias para los mismos valores. Cambiar x dentro de intercambiar hace que x se refiera a un valor diferente, pero no tiene efecto en a en __main__. Igualmente, cambiar y no surte efecto en b
Esta función se ejecuta sin producir un mensaje de error, pero no hace lo que buscábamos. Este es un ejemplo de un error semántico.
Como ejercicio, haz un diagrama de estados para esta función de manera que puedas ver por qué no funciona.Comentarios
La mayor parte de los programas informáticos hacen lo mismo cada vez que se ejecutan, por tanto se dice que son determinísticos. El determinismo normalmente es bueno ya que esperamos que la misma operación produzca el mismo resultado. Sin embargo, nos interesa que algunas aplicaciones sean impredecibles. Los juegos son un claro ejemplo, pero existen otros.
La creación de un programa verdaderamente no determinístico no resulta tan fácil, pero hay maneras de que, al menos, parezca no determinístico. Una de ellas es generar números aleatorios y utilizarlos para determinar el resultado del programa. Python ofrece una función incorporada que genera números seudoaleatorios, que no son números aleatorios reales en el sentido matemático, pero que nos servirán para nuestro propósito.
El módulo aleatorio contiene una función llamada aleatoria que nos da como resultado un número en coma flotante entre 0.0 y 1.0. Cada vez que llames la función aleatoria obtendrás el número siguiente en una larga serie. Para ver un ejemplo, ejecuta este bucle:
import aleatoria
for i in serie(10):
x = random.random()
print x
Para generar un número aleatorio entre 0.0 y un límite superior como alto, multiplica x por alto.
Como ejercicio, genera un número aleatorio entre bajo y alto.
Como ejercicio adicional, genera un número entero aleatorio entre bajo y alto, incluyendo ambos límites.Comentarios
El primer paso es generar una lista de valores aleatorios. La listaAleatoria toma un parámetro de número entero y da como resultado una lista de números aleatorios con una longitud determinada. Empieza con una lista de n ceros. Cada vez que pasa a través del bucle, reemplaza uno de los elementos con un número aleatorio. El valor de retorno es una referencia para la lista completa:
def listaAleatoria(n):
s = [0] * n
for i in serie(n):
s[i] = random.random()
return s
Probaremos esta función con una lista de ocho elementos. Con el propósito de depurar, es una buena idea empezar poco a poco.
>>> listaAleatoria(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 la función aleatoria están distribuidos de manera uniforme, lo que significa que cada valor tiene la misma probabilidad.
Si dividimos la serie de valores posibles en "cubetas" del mismo tamaño, y contamos el número de veces que un valor aleatorio cae en cada cubeta, deberíamos obtener aproximadamente el mismo número en cada una.
Podemos comprobar esta teoría creando un programa para dividir la serie en cubetas y para contar el número de valores en cada una.
Comentarios
Un buen enfoque para problemas como éste, consiste en dividir el problema en subproblemas y buscar subproblemas que encajen en el patrón computacional que has visto anteriormente.
En este caso, queremos pasar a través de una lista de números y contar el número de veces que un valor cae dentro de una serie determinada. Seguro que esto te resulta familiar. En la Sección 7.8, creamos un programa que pasaba a través de una cadena y contaba las veces que aparecía una determinada letra.
Así pues, podemos seguir copiando el programa antiguo y adaptarlo al problema actual. El programa original era:
contar = 0
for char in fruit:
if char == 'a':
contar = contar + 1
print contar
El primer paso es reemplazar fruit por list y char por núm. Esto no cambia el programa, solo lo hace más legible.
El segundo paso es cambiar la prueba porque no estamos interesados en encontrar letras. Queremos ver si núm está entre los valores bajo y alto.
contar = 0
for núm in la lista
if bajo < núm < alto:
contar = contar + 1
print contar
El último paso es encapsular este código en una función llamada enCubeta. Los parámetros son la lista y los valores bajo y alto.
def enCubeta(lista, bajo, alto):
contar = 0
for núm in lista.
if bajo< núm < alto:
contar = contar + 1
return contar
Mediante la copia y modificación de un programa existente, fuimos capaces de crear rápidamente estas funciones y ahorrar mucho tiempo de depuración. Este plan de desarrollo se llama coincidencia de patrones. Si estás trabajando en un problema que ya has solucionado antes, vuelve a utilizar la solución.
Comentarios
A medida que el número de cubetas aumenta, la función enCubeta se vuelve un poco difícil de manejar. Con dos cubetas no hay problema:
bajo =enCubeta(a, 0.0, 0.5)
alto =enCubeta(a, 0.5, 1)
Pero con cuatro cubetas se vuelve engorroso.
cubeta1 = enCubeta(a, 0.0, 0.25)
cubeta2 = enCubetat(a, 0.25, 0.5)
cubetat3 = enCubeta(a, 0.5, 0.75)
cubeta4 =enCubeta(a, 0.75, 1.0)
Aparecen dos problemas. Uno es que tenemos que inventar nuevos nombres de variables para cada resultado y otro es que tenemos que calcular la serie para cada cubeta.
En primer lugar resolveremos el segundo problema. Si el número de cubetas es númCubetas, entonces el ancho de cada cubeta es 1.0 / númCubetas.
Utilizaremos un bucle para calcular la serie de cada cubeta. La variable de bucle, i, cuenta desde 1 hasta númCubetas-1:
anchuraCubeta = 1.0 / númCubetas
for i in serie(númCubetas):
bajo = i * anchuraCubeta
alto = bajo + anchuraCubeta
print bajo, "to", alto
Para calcular el extremo inferior de cada cubeta, multiplicamos la variable de bucle por el ancho de la cubeta. El extremo superior está tan solo a una anchuraCubeta.
Con númCubetas = 8, el resultado es:
de 0.0 a 0.125
de 0.125 a 0.25
de 0.25 a 0.375
de 0.375 a 0.5
de 0.5 a 0.625
de 0.625 a 0.75
de 0.75 a 0.875
de 0.875 a 1.0
Puedes confirmar que cada cubeta tenga la misma anchura, que no se superpongan y que cubran toda la serie, desde 0.0 hasta 1.0.
Ahora regresemos al primer problema. Necesitamos una manera de almacenar ocho números enteros utilizando la variable de bucle para señalar uno a la vez. A estas alturas debes estar pensando "¡una lista!"
Tenemos que crear una lista de cubetas fuera del bucle ya que solo lo queremos hacer una vez. Dentro del bucle llamaremos repetidamente la función enCubeta y actualizaremos el elemento i de la lista:
númCubetas = 8
cubetas = [0] * númCubetas
anchuraCubeta= 1.0 / númCubetas
for i in serie(númCubetas):
bajo = i * anchuraCubeta
alto = bajo + anchuraCubeta
cubetas[i] = enCubetat(lista, bajo, alto)
print cubetas
En una lista de 1000 valores, este código produce una lista de cubetas:
[138, 124, 128, 118, 130, 117, 114, 131]
Estos números están bastante próximos a 125, tal como esperábamos. Al menos, están lo suficientemiente próximos para que sepamos que el generador de números aleatorios funciona.
Como ejercicio, prueba esta función con listas más largas, y observa si el número de valores de cada cubeta tiende a nivelarse.Comentarios
Aunque este programa funciona, no es tan eficaz como podría serlo. Cada vez que llama la función enCubeta, pasa a través de toda la lista. A medida que aumenta el número de cubetas, se convierte en un montón de recorridos transversales.
Sería mejor hacer una sola pasada a través de la lista y calcular para cada valor el índice de la cubeta en la que cae. Luego podemos aumentar el contador adecuado.
En la sección anterior tomamos un índice, i, y lo multiplicamos por la función anchuraCubeta para hallar el extremo inferior de una determinada cubeta. Ahora queremos tomar un valor en la serie de 0.0 a 1.0 y encontrar el índice de la cubeta donde cae.
Puesto que este problema es el inverso al anterior, podemos deducir que deberíamos dividir entre la función anchuraCubeta en vez de multiplicar y esta deducción es correcta.
Ya que anchuraCubeta= 1.0 / númCubetas, dividida entre anchuraCubeta es lo mismo que multiplicar por númCubetas. Si multiplicamos un número en la serie de 0.0 a 1.0 por númCubetas, obtenemos un número entre 0.0 y númCubetas. Si redondeamos ese número hasta el siguiente entero inferior, obtenemos exactamente lo que estábamos buscando, esto es, un índice de cubeta:
númCubetas = 8
cubetas = [0] * númCubetas
for i in lista:
índice = ent(i * númCubetas)
cubetas[índice] = cubetas[índice] + 1
Utilizábamos la función ent para convertir un número en coma flotante en un número entero.
¿Es posible producir un índice que esté fuera de la serie (que bien sea negativo o bien mayor que len(cubetas)-1) para este cálculo?
Una lista como cubetas que contiene recuentos del número de valores en cada serie se denomina histograma.
Como ejercicio escribe una función que se llame histograma que contenga una lista y un número de cubetas como parámetros, y que devuelva un histograma con dicho número de cubetas.Comentarios
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |