Next Up Previous Hi Index

Chapter 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 provocará 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 {}:

>>> ing\_a\_esp = {}
>>> ing\_a\_esp['one'] = 'uno'
>>> 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:

>>> print ing\_a\_esp
{'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:

>>> ing\_a\_esp = {'one': 'uno', 'two': 'dos', 'three': 'tres'}

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

>>> print ing\_a\_esp
{'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:

>>> print ing\_a\_esp['two']
'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 número de esas frutas en el almacén:

>>> inventario = {'manzanas': 430, 'bananas': 312,
...               'naranjas': 525, 'peras': 217}
>>> print inventario
{'naranjas': 525, 'manzanas': 430, 'peras': 217, 'bananas': 312}

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

>>> del inventario['peras']
>>> print inventario
{'naranjas': 525, 'manzanas': 430, 'bananas': 312}

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

>>> inventario['peras'] = 0
>>> print inventario
{'naranajas': 525, 'manzanas': 430, 'peras': 0, 'bananas': 312}

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

>>> len(inventario)
4

10.2 Métodos del diccionario

Un método 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 método ing_a_esp.keys().

>>> ing\_a\_esp.keys()
['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 método no admite parámetros.

La llamda a un método se denomina invocación; en este caso, diríamos que estamos invocando keys sobre el objeto ing_a_esp.

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

>>> ing\_a\_esp.values()
['uno', 'tres', 'dos']

El método items devuelve ambos, una lista de tuplas con los pares clave-valor del diccionario:

>>> ing\_a\_esp.items()
[('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 método has_key acepta una clave y devuelve verdadero (1) si la clave aparece en el diccionario:

>>> ing\_a\_esp.has_key('one')
1
>>> ing\_a\_esp.has_key('deux')
0

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

>>> has_key('one')
NameError: has_key

10.3 Asignación de alias y copiado

Debe usted estar atento a los alias a causa de la mutabilidad de los diccionarios. Si dos variables se refieren al mismo objeto los cambios en una afectan a la otra.

Si quiere modificar un diccionario y mantener una copia del original, use el método copy. Por ejemplo, opuestos es un diccionario que contiene pares de opuestos:

>>> opuestos = {'arriba': 'abajo', 'derecho': 'torcido',
...             'verdadero': 'falso'}
>>> alias = opuestos
>>> copia = opuestos.copy()

alias y opuestos se refieren al mismo objeto; copia hace referencia a una copia nueva del mismo diccionario. Si modificamos alias, opuestos también resulta cambiado:

>>> alias['derecho'] = 'sentado'
>>> opuestos['derecho']
'sentado'

Si modificamos copia, opuestos no varía:

>>> copia['derecho'] = 'privilegio'
>>> opuestos['derecho']
'sentado'

10.4 Matrices dispersas

En la Sección usamos una lista de listas para representar una matriz. Es una buena opción para una matriz en la que la mayoría de los valores es diferente de cero, pero piense en una matriz como ésta:

La representación de la lista contiene un montón de ceros:

matriz = [ [0,0,0,1,0],
           [0,0,0,0,0],
           [0,2,0,0,0],
           [0,0,0,0,0],
           [0,0,0,3,0] ]

Una posible alternativa es usar un diccionario. Como claves, podemos usar tuplas que contengan los números de fila y columna. Ésta es la representación de la misma matriz por medio de un diccionario:

matriz = {(0,3): 1, (2, 1): 2, (4, 3): 3}

Sólo hay tres pares clave-valor, una para cada elemento de la matriz diferente de cero. Cada clave es una tupla, y cada valor es un entero.

Para acceder a un elemento de la matriz, podemos usar el operador []:

matriz[0,3]
1

Fíjese en que la sintaxis para la representación por medio del diccionario no es la misma de la representación por medio de la lista anidada. En lugar de dos índices enteros, usamos un índice que es una tupla de enteros.

Hay un porblema. Si apuntamos a un elemento que es cero, se produce un error porque en el diccionario no hay una entrada con esa clave:

>>> matriz[1,3]
KeyError: (1, 3)

El método get soluciona este problema:

>>> matriz.get((0,3), 0)
1

El primer argumento es la clave; el segundo argumento es el valor que debe devolver get en caso de que la clave no esté en el diccionario:

>>> matriz.get((1,3), 0)
0

get mejora sensiblemente la semántica del acceso a una matriz dispersa. Lástima de sintaxis.

10.5 Pistas

Si estuvo jugando con la función fibonacci de la Sección 5.7, es posible que haya notado que cuanto más grande es el argumento que le da, más tiempo le cuesta ejecutarse. Más aún, el tiempo de ejecución aumenta muy rápidamente. En nuestra máquina, fibonacci(20) acaba instantáneamente, fibonacci(30) tarda más o menos un segundo, y fibonacci(40) tarda una eternidad.

Para entender por qué, observe este gráfico de llamadas de fibonacci con n=4:

Un gráfico de llamadas muestra un conjunto de cajas de función con líneas que conectan cada caja con las cajas de las funciones a las que llama. En lo alto del gráfico, fibonacci con n=4 llama a fibonacci con n=3 y n=2. A su vez, fibonacci con n=3 llama a fibonacci con n=2 y n=1. Y así sucesivamente.

Cuente cuántas veces se llama a fibonacci(0) y fibonacci(1). Es una solución ineficaz al problema, y empeora mucho tal como crece el argumento.

Una buena solución es llevar un registro de los valores que ya se han calculado almacenándolos en un diccionario. A un valor que ya ha sido calculado y almacenado para un uso posterior se le llama pista. Aquí hay una implementación de fibonacci con pistas:

anteriores = {0:1, 1:1}

def fibonacci(n):
  if anteriores.has_key(n):
    return anteriores[n]
  else:
    nuevoValor = fibonacci(n-1) + fibonacci(n-2)
    anteriores[n] = nuevoValor
    return nuevoValor

El diccionario llamado anteriores mantiene un registro de los valores de Fibonacci que ya conocemos. El programa comienza con sólo dos pares: 0 corresponde a 1 y 1 corresponde a 1.

Siempre que se llama a fibonacci comprueba si el diccionario contiene el resultado ya calculado. Si está ahí, la función puede devolver el valor inmediatamente sin hacer más llamadas recursivas. Si no, tiene que calcular el nuevo valor. El nuevo valor se añade al diccionario antes de que la función vuelva.

Con esta versión de fibonacci, nuestra máquina puede calcular fibonacci(40) en un abrir y cerrar de ojos. Pero cuando intentamos calcular fibonacci(50), nos encontramos con otro problema:

>>> fibonacci(50)
OverflowError: integer addition

La respuesta, como verá en un momento, es 20.365.011.074. El problema es que este número es demasiado grande para caber en un entero de Python. Se desborda. Afortunadamente, hay una solución fácil para este problema.

10.6 Enteros largos

Python proporciona un tipo llamado long int que puede manejar enteros de cualquier tamaño. Hay dos formas de crear un valor long int. Una es escribir un entero con una L mayúscula al final:

>>> type(1L)
<type 'long int'>

La otra es usar la función long para convertir un valor en long
int
. long acepta cualquier tipo numérico e incluso cadenas de dígitos:

>>> long(1)
1L
>>> long(3.9)
3L
>>> long('57')
57L

Todas las operaciones matemáticas funcionan sobre los long ints, así que no tenemos que hacer mucho para adaptar fibonacci:

>>> previous = {0:1L, 1:1L}
>>> fibonacci(50)
20365011074L

Simplemente cambiando el contenido inicial de anteriores cambiamos el comportamiento de fibonacci. Los primeros dos números de la secuencia son long ints, así que todos los números subsiguientes lo serán también.

Como ejercicio, modifique factorial de forma que produzca un long int como resultado.

10.7 Contar letras

En el capítulo 7 escribimos una función que contaba el número de apariciones de una letra en una cadena. Una versión más genérica de este problema es crear un histograma de las letras de la cadena, o sea, cuántas veces aparece cada letra.

Ese histograma podría ser útil para comprimir un archivo de texto. Como las diferentes letras aparecen con frecuencias distintas, podemos comprimir un archivo usando códigos cortos para las letras más habituales y códigos más largos para las que aparecen con menor frecuencia.

Los diccionarios facilitan una forma elegante de generar un histograma:

>>> cuentaLetras = {}
>>> for letra in "Mississippi":
...   cuentaLetras[letra] = cuentaLetras.get (letra, 0) + 1
...
>>> cuentaLetras
{'M': 1, 's': 4, 'p': 2, 'i': 4}
>>>

Inicialmente, tenemos un diccionario vacío. Para cada letra de la cadena, buscamos el recuento actual (posiblemente cero) y lo incrementamos. Al final, el diccionario contiene pares de letras y sus frecuencias.

Puede ser más atractivo mostrar el histograma en orden alfabético. Podemos hacerlo con los métodos items y sort:

>>> itemsLetras = cuentaLetras.items()
>>> itemsLetras.sort()
>>> print itemsLetras
[('M', 1), ('i', 4), ('p', 2), ('s', 4)]

Ya había visto usted el método items, pero sort es el primer método aplicable a listas que hemos visto. Hay varios más, como append, extend, y reverse. Consulte la documentación de Python para ver los detalles.

10.8 Glosario

diccionario
Una colección de pares clave-valor que establece una correspondencia entre claves y valores. Las claves pueden ser de cualquier tipo inmutable, los valores pueden ser de cualquier tipo.
clave
Un valor que se usa para buscar una entrada en un diccionario.
par clave-valor
Uno de los elementos de un diccionario, también llamado "asociación".
método
Un tipo de función al que se llama con una sintaxis diferente y al que se invoca "sobre" un objeto.
invocar
Llamar a un método.
pista
Almacenamiento temporal de un valor precalculado para evitar cálculos redundantes.
desbordamiento
Un resultado numérico que es demasiado grande para representarse en formato numérico.


Next Up Previous Hi Index