Siguiente Arriba Anterior Guía básica para razonar como un informático Índice

Capítulo 10

Comentarios

Diccionarios

Los tipos compuestos que ya hemos aprendido, como cadenas, listas y tuplas, emplean números enteros como índices. Si intentamos usar otro tipo como índice, obtendremos un error.

Los diccionarios son parecidos a otros tipos compuestos, salvo en que pueden usar cualquier tipo inmutable como índice. Para que nos sirva de ejemplo, crearemos un diccionario para traducir palabras del español al inglés. Para este diccionario, los índices son cadenas.

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

>>> esp_a_ingl = {}
>>> esp_a_ingl['uno'] = 'one'
>>> esp_a_ingl['dos'] = 'two'

La primera asignación crea un diccionario llamado esp_a_ingl; las otras asignaciones añaden nuevos elementos al diccionario. Podemos presentar en pantalla el valor actual del diccionario de la siguiente manera:

>>> print esp_a_ingl
{'uno': 'one', 'dos': 'two'}

Los elementos de un diccionario aparecen en una lista separada por comas. Cada entrada contiene un índice y un valor separado por una columna. En un diccionario, los índices se llaman claves, por lo que los elementos se llaman parejas de claves y valores.

Otra manera de crear un diccionario es proveer de una lista de parejas de claves y valores, usando la misma sintaxis que la empleada en la salida anterior:

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

Si presentamos en pantalla nuevamente el valor esp_a_ingl, tendremos una sorpresa:

>>> print esp_a_ingl
{'uno': 'one', 'tres': 'three', 'dos': 'two'}

¡Las parejas de claves y valores clave no están ordenadas! Afortunadamente, no hay motivos para preocuparse por el orden, ya que los elementos de un diccionario nunca se clasifican con índices de números enteros. En lugar de eso, empleamos las claves para buscar los valores correspondientes:

>>> print esp_a_ingl['dos']
'two'

La clave 'dos' produce el valor 'two' incluso cuando aparece en la tercera pareja de claves y valores.

10.1 Operaciones de diccionario

La instrucción del elimina del diccionario una pareja de claves y valores. Por ejemplo, el siguiente diccionario contiene los nombres de diferentes frutas y el número de cada fruta en stock:

>>> inventario = {'manzanas': 430, 'plátanos': 312, 'naranjas': 525,
'peras': 217}
>>> print inventario
{'naranjas': 525, 'manzanas': 430, 'peras': 217, 'plátanos': 312}

Si alguien comprase todas las peras, podríamos quitar su entrada del diccionario:

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

O si esperamos que lleguen pronto más peras, simplemente podemos cambiar el valor asociado a las peras:

>>> inventario['peras'] = 0
>>> print inventario
{'naranjas': 525, 'manzanas': 430, 'peras': 0, 'plátanos': 312}

La función len también trabaja con diccionarios, ya que restituye el número de parejas de claves y valores:

>>> len(inventario)
4

Comentarios

10.2 Métodos de diccionario

Un método es parecido a una función, ya que toma un parámetro y restituye su valor, pero la sintaxis es diferente. Por ejemplo, el método keys (en español "claves") toma un diccionario y restituye una lista de valores que aparece, pero en lugar de la función de sintaxis claves(esp_a_ingl), usamos el método de sintaxis esp_a_ingl.keys().

>>> esp_a_ingl.keys()
['uno', 'tres', 'dos']

Esta manera de notación de punto especifica el nombre de la función, keys, y el nombre del objeto al que aplicar la función esp_a_ingl. Los paréntesis indican que este método no necesita parámetros.

Una llamada técnica se conoce con el nombre de invocación; en este caso, podríamos decir que estamos invocando claves con objeto esp_a_ingl.

El método values (en español "valores") es parecido, ya que restituye una lista de valores al diccionario:

>>> esp_a_ingl.values()
['one', 'three', 'two']

El método items restituye ambos, en forma de lista de tuplas, una por cada pareja de claves y valores.

>>> esp_a_ingl.items()
[('uno','one'), ('tres', 'three'), ('dos', 'two')]

La sintaxis da información muy útil. Los corchetes nos indican que esto es una lista, y los paréntesis indican que los elementos de la lista son tuplas.

Si un método requiere un parámetro, usará la misma sintaxis que una función de llamada. Por ejemplo, el método has_key utiliza una clave y es verdadero (1) si la clave aparece en el diccionario:

>>> esp_a_ingl.has_key('uno')
1
>>> esp_a_ingl.has_key('deux')
0

Si intentamos llamar a un método sin especificar un objeto, obtendremos un error. En este caso, el mensaje de error no ayuda mucho.

>>> has_key('uno')
NameError: has_key

Comentarios

10.3 Alias y copia

Como los diccionarios cambian, tenemos que estar atentos a los alias. Cuando dos variables aluden al mismo objeto, los cambios que se producen en uno afectan al otro.

Si queremos modificar un diccionario y guardar una copia del original, tenemos que usar el método copy (en español "copia"). Por ejemplo, oposición (en inglés "opposittes") es un diccionario que contiene pares de oposiciones:

>>> oposición = {'arriba': 'debajo', 'bien': 'mal', 'verdadero': 'falso'}
>>> alias = oposición
>>> copia = oposición.copy()

alias y oposición hacen referencia al mismo objeto; pero copia hace referencia a la nueva copia del mismo diccionario. Si modificamos alias, oposición también cambia:

>>> alias['bien'] = 'izquierda'
>>> oposición['bien']
'izquierda'

Pero si modificamos copia, oposición no cambia:

>>> copia['bien'] = 'privilegio'
>>> oposición['bien']
'izquierda'

Comentarios

10.4 Matrices dispersas

En la Sección 8.14, usamos un listado para representar una matriz. Esa es una buena opción cuando se trata de matrices que contienen principalmente valores distintos de cero, pero tengamos en cuenta una matriz dispersa como la siguiente:

Esta representación contiene muchos 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 alternativa es usar un diccionario. Para las claves, podemos usar tuplas que contengan los números de fila y columna. Aquí tenemos la representación del diccionario de la misma matriz:

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

Sólo necesitamos tres parejas de claves y valores, una para cada elemento de la matriz, que no sea cero. Cada clave es una tupla y cada valor es un número entero.

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

matriz[0,3]
1

Fíjate en que la sintaxis para la representación del diccionario no es la misma sintaxis que la empleada para la representación de la lista anidada. En lugar de dos índices de números enteros, usamos un índice que se considera una tupla de números enteros.

Pero hay un problema. Si especificamos un elemento que sea cero, obtenemos un error porque no hay ninguna entrada en el diccionario con esa clave:

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

El método get (en español, "obtener") soluciona este problema:

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

El primer parámetro es la clave; y el segundo, el valor get debería restituirse si la clave no aparece en el diccionario:

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

Definitivamente, get mejora la semántica de acceso a la matriz dispersa. Lo malo es la sintaxis. Comentarios

10.5 Pistas

Si has practicado con la función fibonacci de la Sección 5.7, te habrás percatado de que cuanto mayor sea el parámetro que introduzcas, la función tardará más en ejecutarse. Además, el tiempo de ejecución aumenta muy rápido. Una de nuestras máquinas, la fibonacci(20), termina al momento; la fibonacci(30) tarda un poco más; y la fibonacci(40) se eterniza.

Para entender porqué pasa esto, ten en cuenta este grafo de llamada para la fibonacci con n=4:

Una búsqueda de grafo muestra ámbitos de funciones establecidas, con líneas conectando cada ámbito a los ámbitos de las funciones a las que hace referencia. Al comienzo del gráfico, la fibonacci con n=4 hace referencia a la fibonacci con n=3 y n=2. Sucesivamente, la fibonacci con n=3 hace referencia a la fibonacci con n=2 y n=1. Y así el resto.

Cuenta cuántas veces nos referimos a la fibonacci(0) y fibonacci(1). Esta solución al problema es poco productiva, y se pone peor a medida que crece el parámetro.

Una buena solución sería mantener pistas de valores que ya hayan sido calculados, almacenándolas en un diccionario. Un valor previo calculado que haya sido almacenado para usarlo posteriormente se denomina sugerencia. Aquí tenemos una ejecución de fibonacci usando sugerencias:

anterior (en inglés, "previous") = {0:1, 1:1}

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

El diccionario denominado anterior ("previous") mantiene pistas de la secuencia de Fibonacci que ya conocemos. Empezamos solamente con dos pares: 0 representa 1; y 1 representa 1.

Siempre que se haga referencia a fibonacci, se revisa el diccionario para determinar si hay resultado. Si lo hay, la función puede restituirse inmediatamente sin hacer ninguna otra llamada recursiva. Si no lo hay, tiene que calcularse el nuevo valor. Antes de que la función se restituya, el nuevo valor se añade al diccionario.

Usando esta versión de fibonacci, nuestras máquinas podrían calcular la fibonacci(40) en un abrir y cerrar de ojos. Pero cuando intentamos calcular la fibonacci(50), tenemos otro tipo de problema:

>>> fibonacci(50)
OverflowError: integer addition

La solución, como comprobaremos en un minuto, es 20,365,011,074. El problema es que este número es demasiado grande para considerarlo un número entero de Python. Se desborda. Afortunadamente, tiene fácil solución. Comentarios

10.6 Números enteros grandes

Python provee un tipo de número entero denominado long int y que puede tomar la dimensión de cualquier número entero. Hay dos formas de crear un valor long int. Una es escribir un entero con una L mayúscula al final:

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

La otra forma es usar la función long para convertir un valor en un long int. El long puede aceptar 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 en long int, así que no tenemos que hacer mucho para adaptar el fibonacci:

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

Sólo cambiando el índice de anterior ("previous"), cambiamos el comportamiento de fibonacci. Los primeros dos números en la secuencia son long int, por lo que, entonces, todos los números siguientes también lo son.

Como ejercicio, trasformen factorial para que produzca como resultado un long int.
Comentarios

10.7 Recuento de letras

En el Capítulo 7, escribimos una función que contaba el número de ejemplares de una letra en una cadena. Una versión más general de este problema es crear un histograma de letras en la cadena, es decir, cuántas veces aparece cada letra.

Dicho histograma debe servir para la comprensión de un archivo de texto. Debido a que diferentes letras aparecen con diferentes frecuencias, podemos comprimir un archivo usando códigos más pequeños para las letras más comunes y otros más grandes para las letras que aparecen con menos frecuencia.

Los diccionarios dan un ejemplo muy bueno de cómo crear un histograma:

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

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

Debe haber más entradas para presentar el histograma por orden alfabético. Podemos hacer eso con los métodos items (en español "elementos") y sort (en inglés "ordenación"):

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

Ya hemos visto antes el método items, pero el método sort es el primero que nos hemos encontrado que es aplicable a listas. Hay otros muchos métodos de listas, incluyendo adosar ("append"), ampliar ("extend") e invertir ("reverse"). Para más información, consultaremos la documentación de Python. Comentarios

10.8 Glosario

diccionario
Colección de parejas de claves y valores que especifican los valores a partir de las claves. Las claves pueden ser de tipo inmutable; y los valores, de cualquier tipo.
clave
Valor que se usa para buscar una entrada en un diccionario.
pareja de claves y valores
Uno de los datos en un diccionario.
método
Tipo de función a la que se llama con una sintaxis diferente y se invoca "en" un objeto.
invocar
Llamar a un método.
pista
Almacenaje temporal de un valor precalculado para evitar el cálculo redundante.
desbordamiento
Resultado numérico demasiado grande para ser representado en un formato numérico.


Siguiente Arriba Anterior Guía básica para razonar como un informático Índice