Cesar Systems

Herramientas Informaticas

9.3. Tuplas como valor de retorno

Las funciones pueden devolver tuplas como valor de retorno. Por ejemplo, podría escribir una función que intercambie dos parámetros:

   1: def intercambio(x, y):

   2:     return y, x

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

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

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

   2: x, y = y, x

Si llamamos a esta función así:

   1: 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 que 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 calculo nos de siempre el mismo resultado.

Para algunas aplicaciones, sin embargo, queremos que el computador sea impredecible. El ejemplo obvio son los juegos, pero hay mas.

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 modulo random contiene una función llamada random que devuelve un numero en coma flotante entre 0,0 y 1,0. Cada vez que usted llama a random obtiene el siguiente numero de una larga serie. Para ver un ejemplo, ejecute este bucle:

   1: import random

   2:  

   3: for i in range(10):

   4:     x = random.random()

   5:     print x

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

Como ejercicio, genere un numero aleatorio entre minimo y maximo.
Como ejercicio adicional, genere un numero 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 numero aleatorio. El valor de retorno es una referencia a la lista completa:

   1: def listaAleatorios(n):

   2: s = [0] * n

   3: for i in range(n):

   4: s[i] = random.random()

   5: 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.

   1: >>> listaAleatorios(8)

   2: 0.15156642489

   3: 0.498048560109

   4: 0.810894847068

   5: 0.360371157682

   6: 0.275119183077

   7: 0.328578797631

   8: 0.759199803101

   9: 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 numero de veces que un valor cae en cada balde, deberemos tener mas o menos el mismo numero en todos.

Podemos contrastar esta teor³a escribiendo un programa que divida el intervalo en baldes y contando el numero de valores en cada uno.

9.6. Conteo

Un buen enfoque sobre problemas como este 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 numero 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 numero
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:

   1: cuenta = 0

   2: for car in fruta:

   3:     if car == 'a':

   4:         cuenta = cuenta + 1

   5: print cuenta

El primer paso es sustituir fruta con lista y car con núm. Esto no cambia el programa, solo lo hace mas legible.

El segundo paso es cambiar la comprobación. No estamos interesados en encontrar letras. Queremos ver si num esta entre los valores de mínimo y máximo.

   1: cuenta = 0

   2: for num in lista

   3:     if minimo < num < maximo:

   4:         cuenta = cuenta + 1

   5: print cuenta

El ultimo paso es encapsular este código en una función llamada enElBalde.

Los parámetros son la lista y los valores minimo y maximo.

   1: def enElBalde(lista, minimo, maximo):

   2:     cuenta = 0

   3:     for num in lista:

   4:             if minimo < num < maximo:

   5:                 cuenta = cuenta + 1

   6:     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 soluciono, reutilice la solución.

9.7. Muchos baldes

Tal como aumenta el numero de baldes, enElBalde se hace un tanto difícil de manejar. Con dos baldes, no esta mal:

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

   2: alto = enElBalde(a, 0.5, 1)

Pero con cuatro baldes ya es aparatoso.

   1: balde1 = enElBalde(a, 0.0, 0.25)

   2: balde2 = enElBalde(a, 0.25, 0.5)

   3: balde3 = enElBalde(a, 0.5, 0.75)

   4: 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 numero 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:

   1: anchuraBalde = 1.0 / numBaldes

   2: for i in range(numBaldes):

   3: minimo = i * anchuraBalde

   4: maximo = minimo + anchuraBalde

   5: 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 esta a tan solo una anchuraBalde.

Con numBaldes = 8, la salida es:

   1: 0.0 hasta 0.125

   2: 0.125 hasta 0.25

   3: 0.25 hasta 0.375

   4: 0.375 hasta 0.5

   5: 0.5 hasta 0.625

   6: 0.625 hasta 0.75

   7: 0.75 hasta 0.875

   8: 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 solo queremos hacerlo una vez. Dentro del bucle, podemos llamar repetidamente a enElBalde y actualizar el i-esimo elemento de la lista:

   1: numBaldes = 8

   2: baldes = [0] * numBaldes

   3: anchuraBalde = 1.0 / numBaldes

   4: for i in range(numBaldes):

   5:     minimo = i * anchuraBalde

   6:     maximo = minimo + anchuraBalde

   7:     baldes[i] = enElBalde(lista, minimo, maximo)

   8: print baldes

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

   1: [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 mas largas, y vea
si el numero 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 numero 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 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 numero del intervalo que va de 0,0 a 1,0 por numBaldes, obtenemos un numero del intervalo entre 0,0 y numBaldes. Si redondeamos ese numero al entero inferior obtendremos exactamente lo que estamos buscando, un índice de balde:

   1: numBaldes = 8

   2: baldes = [0] * numBaldes

   3: for i in lista:

   4:     indice = int(i * numBaldes)

   5:     baldes[indice] = baldes[indice] + 1

Usamos la función int para convertir un numero en coma flotante en un entero.

¿Es posible que este calculo genere un índice que este fuera del intervalo (tanto negativo como mayor que len(baldes)-1)?
Una lista como baldes que contiene conteos del numero 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 numero de baldes y devuelva un histograma con el numero 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 mas 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 calculo determinista.

histograma: Una lista de enteros en la que cada elemento cuenta el numero 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.

Capítulo 10

Diccionarios

Los tipos compuestos que ha visto hasta ahora (cadenas, listas y tuplas) usan enteros como índices. Si intenta usar cualquier otro tipo como índice provocara un error.

Los diccionarios son similares a otros tipos compuestos excepto en que pueden usar como índice cualquier tipo inmutable. A modo de ejemplo, crearemos un diccionario que traduzca palabras inglesas al español. En este diccionario, los índices son strings (cadenas).

Una forma de crear un diccionario es empezar con el diccionario vacío y añadir elementos. El diccionario vacío se expresa como {}:

   1: >>> ing_a_esp = {}

   2: >>> ing_a_esp['one'] = 'uno'

   3: >>> ing_a_esp['two'] = 'dos'

La primera asignación crea un diccionario llamado ing a esp; las otras asignaciones añaden nuevos elementos al diccionario. Podemos presentar el valor actual del diccionario del modo habitual:

   1: >>> print ing_a_esp

   2: {'one': 'uno', 'two': 'dos'}

Los elementos de un diccionario aparecen en una lista separada por comas.

Cada entrada contiene un índice y un valor separado por dos puntos (:). En un diccionario, los índices se llaman claves, por eso los elementos se llaman pares clave-valor.

Otra forma de crear un diccionario es dando una lista de pares clave-valor con la misma sintaxis que la salida del ejemplo anterior:

   1: >>> ing_a_esp = {'one': 'uno', 'two': 'dos', 'three': 'tres'}

Si volvemos a imprimir el valor de ing a esp, nos llevamos una sorpresa:

   1: >>> print ing_a_esp

   2: {'one': 'uno', 'three': 'tres', 'two': 'dos'}

¡Los pares clave-valor no están en orden! Afortunadamente, no necesitamos preocuparnos por el orden, ya que los elementos de un diccionario nunca se indexan con índices enteros. En lugar de eso, usamos las claves para buscar los valores correspondientes:

   1: >>> print ing_a_esp['two']

   2: 'dos'

La clave ‘two’ nos da el valor ‘dos’ aunque aparezca en el tercer par clave-valor.

10.1. Operaciones sobre diccionarios

La sentencia del elimina un par clave-valor de un diccionario. Por ejemplo, el diccionario siguiente contiene los nombres de varias frutas y el numero de esas frutas en el almacén:

   1: >>> inventario = {'manzanas': 430, 'bananas': 312,

   2: ... 'naranjas': 525, 'peras': 217}

   3: >>> print inventario

   4: {'naranjas': 525, 'manzanas': 430, 'peras': 217, 'bananas': 312}

Si alguien compra todas las peras, podemos eliminar la entrada del diccionario:

   1: >>> del inventario['peras']

   2: >>> print inventario

   3: {'naranjas': 525, 'manzanas': 430, 'bananas': 312}

O si esperamos recibir mas peras pronto, podemos simplemente cambiar el inventario asociado con las peras:

   1: >>> inventario['peras'] = 0

   2: >>> print inventario

   3: {'naranajas': 525, 'manzanas': 430, 'peras': 0, 'bananas': 312}

La función len también funciona con diccionarios; devuelve el numero de pares clave-valor:

   1: >>> len(inventario)

   2: 4

10.2. Métodos del diccionario

Un metodo es similar a una función, acepta parámetros y devuelve un valor, pero la sintaxis es diferente. Por ejemplo, el método keys acepta un diccionario y devuelve una lista con las claves que aparecen, pero en lugar de la sintaxis de la función keys(ing a esp), usamos la sintaxis del metodo ing a esp.keys().

   1: >>> ing_a_esp.keys()

   2: ['one', 'three', 'two']

Esta forma de notación de punto especifica el nombre de la función, keys, y el nombre del objeto al que se va a aplicar la función, ing a esp. Los paréntesis indican que este metodo no admite parámetros.

La llamada a un metodo se denomina invocación; en este caso, diríamos que estamos invocando keys sobre el objeto ing a esp.

El método valúes es similar; devuelve una lista de los valores del diccionario:

   1: >>> ing_a_esp.values()

   2: ['uno', 'tres', 'dos']

El metodo ítems devuelve ambos, una lista de tuplas con los pares clave-valor del diccionario:

   1: >>> ing_a_esp.items()

   2: [('one','uno'), ('three', 'tres'), ('two', 'dos')]

La sintaxis nos proporciona información muy útil acerca del tipo de datos. Los corchetes indican que es una lista. Los paréntesis indican que los elementos de la lista son tuplas.

Si un método acepta un argumento, usa la misma sintaxis que una llamada a una función. Por ejemplo, el metodo has key acepta una clave y devuelve verdadero (1) si la clave aparece en el diccionario:

   1: >>> ing_a_esp.has_key('one')

   2: 1

   3: >>> ing_a_esp.has_key('deux')

   4: 0

Si usted invoca un metodo sin especificar un objeto, provoca un error. En este caso, el mensaje de error no es de mucha ayuda:

   1: >>> has_key('one')

   2: NameError: has_key

Página 116 de 143

Creado con WordPress & Tema de Anders Norén