Diccionaris

Aquesta lliçó introdueix una altre tipus de dades: els diccionaris. Podem pensar en els diccionaris com una generalització de les llistes, on es pot indexar per qualsevol valor de (quasi) qualsevol tipus, sense que els índexs hagin de ser enters consecutius.

Introducció

Un diccionari (o mapa) és un tipus abstracte de dades que permet emmagatzemar una col·lecció d’elements. Cada element té dues parts:

Les operacions estan guiades per les claus, les quals han de ser úniques a cada diccionari. Les operacions principals són els següents:

Hi ha altres operacions, com les que permeten recórrer tots els elements d’un diccionari o que permeten utilitzar un diccionari com si fos un vector generalitzat.

Aplicacions

Els diccionaris són un tipus de dades recurrent en moltes aplicacions.

Per exemple, en una aplicació de traducció de textos del català a l’anglès, en algun lloc caldrà emmagatzemar que la traducció de 'casa' és 'house', que la de 'gos' és 'dog', que la de 'gat' és 'cat' i així successivament. En aquest cas les paraules en català són les claus i les paraules en anglès són els seus valors associats. En aquesta aplicació serà capital que l’operació de consulta (donada una paraula en català, saber quina és la traducció anglesa) sigui eficient.

Un aplicació de contactes per a mòbils també seria un exemple de diccionari. En aquest cas, les claus serien noms de persones i els valors les seves dades com ara telèfons, adreces físiques i electròniques i data d’aniversari, potser aplegades en una estructura.

Literals

La manera més senzilla d’escriure diccionaris en Python és enumerant els seus elements entre claus i separant-los per comes. Cada element té dues parts: la clau i el valor, separats per dos punts. Aquí en teniu dos exemples:

>>> catala_angles = {'casa': 'house', 'gos': 'dog', 'gat': 'cat'}
catala_angles
{'casa': 'house', 'gos': 'dog', 'gat': 'cat'}
>>> nombres = {1: 'un', 2: 'dos', 3: 'tres', 4: 'quatre'}
>>> nombres
{1: 'un', 2: 'dos', 3: 'tres', 4: 'quatre'}

El diccionari buit, s’escriu {} o dict().

Funcions predefinides

Igual que per les llistes i els conjunts, Python ofereix algunes funcions predefinides sobre diccionaris. Per exemple, la funció len, aplicada a un conjunt, en retorna el seu nombre d’elements (és a dir, el nombre de parelles clau-valor):

>>> nombres = {1: 'un', 2: 'dos', 3: 'tres', 4: 'quatre'}
>>> len(nombres)
4
>>> len({})
0

Les funcions min, max i sum aplicades sobre un diccionari en retornen, respectivament, el mínim, màxim i suma de les seves claus. No es fan servir gaire.

Manipulació de diccionaris

Els operadors in i not in permeten saber si una clau és o no en un diccionari:

>>> nombres = {1: 'un', 2: 'dos', 3: 'tres', 4: 'quatre'}
>>> 3 in nombres
True
>>> 14 in nombres
False
>>> 'dos' in nombres
False

Els diccionaris es poden indexar amb [] per tal de consultar i modificar els valors associats a claus.

Una assignació d[k] = v permet associar el valor v a la clau k del diccionari d. Si k ja era en d, l’antic valor associat es perd i és substituït per v. Si k no era al diccionari, la clau k és inserida al diccionari, amb valor v:

>>> nombres
{1: 'un', 2: 'dos', 3: 'tres', 4: 'quatre'}
>>> nombres[2] = 'two'
>>> nombres
{1: 'un', 2: 'two', 3: 'tres', 4: 'quatre'}
>>> nombres[1000] = 'mil'
>>> nombres
{1: 'un', 2: 'two', 3: 'tres', 4: 'quatre', 1000: 'mil'}

Si una clau k existeix en un diccionari d, l’expressió d[k] en retorna el seu valor associat, però si la clau no hi és, provoca un error:

>>> print(nombres[3])
tres
>>> print(nombres[14])
KeyError: 14

En canvi, d.get(k, x) retorna d[k] si k és a d i x sinó. És útil per oferir valors per defecte o evitar errors:

>>> notes = {'do':'C', 're':'D', 'mi':'E', 'fa':'F', 'sol':'G', 'la':'A', 'si':'B'}
>>> print(notes.get('do', None))
C
>>> print(notes.get('ut', None))
None
>>> print(notes.get('ut', 'no hi és'))
'no hi és'

Es pot esborrar una clau k d’un diccionari d amb del d[k], a condició que k pertanyi al diccionari:

>>> nombres
{1: 'un', 2: 'dos', 3: 'tres', 4: 'quatre'}
>>> del nombres[2]
>>> nombres
{1: 'un', 3: 'tres', 4: 'quatre'}
>>> del nombres[9]
KeyError: 9
>>> nombres[2] = 'dos'

El mètode .keys() aplicat a un diccionari retorna totes les claus que conté. Igualment, el mètode .values() retorna totes les claus que conté. A més, el mètode .items() retorna totes les tuples de parells claus-valors:

>>> nombres.keys()
dict_keys([1, 2, 3, 4, 5])
>>> nombres.values()
dict_values(['un', 'two', 'tres', 'quatre', 'cinc'])
>>> nombres.items()
dict_items([(1, 'un'), (2, 'two'), (3, 'tres'), (4, 'quatre'), (5, 'cinc')])

Si es volen convertir aquests resultats en llistes, se’ls ha d’aplicar una conversió (però en general no cal):

>>> list(nombres.keys())
[1, 2, 3, 4, 5]

La implementació interna de Python està dissenyada perquè totes aquestes operacions siguin molt eficients.

Recórrer tots els elements d’un diccionari

Sovint, es vol recórrer tots els elements d’un diccionari, realitzant alguna tasca amb cadascun d’aquests elements. La manera més habitual de fer-ho és amb un bucle for i els mètodes keys, values o items. Per exemple:

>>> nombres = {1: 'un', 2: 'dos', 3: 'tres', 4: 'quatre'}
>>> for k in nombres.keys(): print(k)
1
2
3
4
>>> for v in nombres.values(): print(v)
un
dos
tres
quatre
>>> for k, v in nombres.items(): print(k, v)
1 un
2 dos
3 tres

Fixeu-vos com es desempaqueten les tuples en dues variables en el cas de items.

L’ordre en què es recorren els elements és l’ordre en el qual s’han inserit els elements (a partir de Python 3.6). Això es deu a la tècnica que utilitza Python internament per emmagatzemar els conjunts eficientment. Per fer programes portables, jo no comptaria massa en aquesta característica.

Modificar un diccionari mentre s’està iterant sobre ell sol ser una mala idea. No ho feu.

El tipus diccionari

En Python, els diccionaris són de tipus dict, ho podem comprovar així:

>>> nombres = {1: 'un', 2: 'dos', 3: 'tres', 4: 'quatre'}
>>> type(nombres)
<class 'dict'>

Per tal de comptar amb la seguretat que aporta la comprovació de tipus, d’ara en endavant suposarem que totes les claus d’un diccionari han de ser del mateix tipus i que tots els valors d’un diccionari han de ser del mateix tipus (possiblement diferent del tipus de les claus): es diu que aquests diccionaris són estructures de dades homogènies. Això no és cap imposició de Python, però és un bon costum per a novells.

En el sistema de tipus de Python, dict[K, V] descriu un nou tipus que és un diccionari on les claus són de tipus K i els valors de tipus V. Per exemple, dict[int, str] és el tipus d’un diccionari d’enters a textos, dict[str, str] és un diccionari de textos a textos, i dict[str, set[int]] és un diccionari de textos a conjunts d’enters.

En la majoria d’ocasions, no cal anotar els diccionaris amb el seu tipus, perquè el sistema ja ho determina sol a través dels seus valors. Només en el cas de crear diccionaris buits cal indicar el tipus dels elements de les llistes perquè, evidentment, el sistema no ho pot saber:

d1: dict[int, int] = {1:1, 2:4}       # no cal anotar el tipus: es dedueix automàticament
d2: dict[int, int] = {}               # cal anotar el tipus que tindrà el diccionari buit
                                      # perquè no es pot pas deduir

Un altre lloc on sempre cal anotar el tipus dels diccionaris és quan es defineixen paràmetres:

def pacient_amb_febra_mes_alta(dict[str, float]) -> str: 
    ...

Diccionaris per comprensió

Els diccionaris també es poden escriure per comprensió de manera semblant als conjunts per comprensió. Aquest cop, però, cal donar els elements separant la clau del valor amb dos punts:

>>> {n : n * n for n in range(10) if n % 2 == 0}
{0: 0, 2: 4, 4: 16, 6: 36, 8: 64}
>>> nombres = {1: 'un', 2: 'dos', 3: 'tres', 4: 'quatre'}
>>> {k : v.upper() for k, v in nombres.items()}
{1: 'UN', 2: 'DOS', 3: 'TRES', 4: 'QUATRE'}

Els diccionaris són objectes

Com les llistes i els conjunts, els diccionaris també són objectes i, per tant, es manipulen a través de referències. Aquest codi ho demostra.

>>> d1 = {1:1, 2:2}
>>> d1
{1: 1, 2: 2}
>>> d2 = d1
>>> d1[3] = 3
>>> d1
{1: 1, 2: 2, 3: 3}
>>> d2
{1: 1, 2: 2, 3: 3}

Es poden copiar diccionaris fàcilment amb el mètode copy:

>>> d1 = {1:1, 2:2}
>>> d2 = d1.copy()
>>> d1[3] = 3
>>> d1
{1: 1, 2: 2, 3: 3}
>>> d2
{1: 1, 2: 2}

Però atenció, si les claus són objectes, el diccionaris també en guarden una referència:

>>> llista = [1, 2, 3]
>>> dic = {'info': llista}
>>> dic['info']
[1, 2, 3]
>>> llista.append(9)
>>> dic['info']
[1, 2, 3, 9]

Si això us fa dubtar, vegeu-ho amb Python Tutor.

Resum de les operacions bàsiques

operació significat
{} crea un diccionari buit.
{k1:v2, k2:v2, ...} crea un diccionari amb elements k1:v1, k2:v2, …
len(d) retorna el nombre de claus del diccionari d.
d[k] = v assigna el valor v a la clau k al diccionari d.
d[k] consulta el valor de la clau k del diccionari d (s’enfada si no hi és).
d.get(k, x) retorna d[k] si k és a d i x sinó.
del d[k] esborra la clau k i el seu valor del diccionari d (no s’enfada si no hi és).
k in d o k not in d diu si k és o no una clau de d.
d.keys() retorna totes les claus de d.
d.values() retorna tots els valors de d.
d.items() retorna tots els parells claus-valors de d.




Lliçons.jutge.org
Jordi Petit
Universitat Politècnica de Catalunya, 2022

Prohibit copiar. Tots els drets reservats.
No copy allowed. All rights reserved.