Diccionaris
Els diccionaris són una de les estructures de dades més importants i flexibles en Python. A diferència d’altres tipus com les llistes o tuples, que emmagatzemen elements de forma ordenada en funció de la seva posició, els diccionaris permeten emmagatzemar parelles clau.
El tipus de dada diccionari de Python
Com en els diccionaris tradicionals (un llibre que contenia un llistat de paraules ordenades alfabèticament i la seva explicació), el tipus de dada diccionari emmagatzema parelles de clau (la paraula que busquem) i valor (l’explicació).
Això vol dir que cada element té una clau única associada a un valor, cosa que permet un accés ràpid i directe a les dades mitjançant les seves claus.
Aquests elements clau són molt útils quan voleu organitzar informació que no es pot indexar simplement per un nombre, com les dades d’un usuari (nom, edat, adreça), inventaris de productes, o qualsevol situació en què necessiteu associar una descripció a un valor.
Conceptes i ús del tipus de dada diccionari
Un diccionari en Python és un tipus de dada que emmagatzema informació en forma de parelles clau.
Cada clau és única i està associada a un valor, que pot ser de qualsevol mena de dada, inclòs nombres, cadenes de text, llistes o fins i tot altres diccionaris.
Utilitzeu l’intèrpret interactiu i teclegeu l’exemple següent:
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1}
Acabeu de crear un diccionari.
Característiques principals dels diccionaris
Els diccionaris emmagatzemen dades en forma de parelles clau. Les claus s’utilitzen per accedir als valors corresponents.
Això proporciona un accés ràpid i directe a la informació. I per això l’eficiència en l’accés de les dades d’un diccionari és molt superior a una llista. El següent codi us mostra com accedir a un valor, fent servir com a element d’indexació, la clau:
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} >>> alumne['nom'] 'Pere'
Els diccionaris són estructures de dades mutables, és a dir, podeu afegir, modificar o eliminar parelles clau després de la seva creació. El següent codi us mostra com modificar un valor, accedint a la posició del valor i assignant-li un de nou:
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} >>> alumne['notes'] = [7.2, 3, 10] >>> alumne {'nom': 'Pere', 'notes': [7.2, 3, 10], 'aula': 1}
I, per afegir un element (clau-valor) a un diccionari, haureu d’assignar al diccionari una nova parella de clau-valor:
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} >>> alumne['cognom'] = 'Cases' >>> alumne {'nom': 'Pere', 'notes': [7.2, 3], 'aula': 1, 'cognom': 'Cases'}
Per eliminar un element, feu servir del:
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} >>> del alumne['aula'] >>> alumne {'nom': 'Pere', 'notes': [7.2, 3]}
Si s’intenta accedir a un valor d’una clau inexistent, es produeix un error. El concepte és semblant a l’error que es produeix si s’intenta accedir a una posició d’una llista major que la seva longitud.
El següent codi us mostra l’error que es produeix quan la clau no existeix:
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} >>> alumne['nmo'] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'nmo'
Les claus d’un diccionari han de ser úniques i immutables, és a dir, no poden repetir-se. Si s’intenta assignar un nou valor a una clau existent, el valor anterior se sobreescriurà (com heu comprovat en l’exemple de modificació d’un valor).
A més, les claus han de ser d’un tipus de dada immutable, com ara nombres, cadenes de text o tuples. No es poden utilitzar llistes o altres diccionaris com a claus a causa de la seva mutabilitat.
El següent codi us mostra l’error que es produeix si la clau és un tipus de dada mutable:
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} >>> alumne[['mitjana']] = 5.1 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list'
Si intenteu afegir una parella clau-valor, en la qual la clau és la llista [‘mitjana’], el processador es queixa perquè la clau no pot ser un tipus de dada mutable. I les llistes ho són.
Els valors associats a les claus poden ser de qualsevol tipus de dada (nombres, cadenes, llistes, diccionaris, etc.), i poden ser mutables. Això vol dir que podeu modificar els valors sense preocupar-vos de la mutabilitat o la immutabilitat del tipus de dades. Un exemple d’això és quan s’afegeix una parella clau on el valor és un diccionari.
En el següent codi, s’afegeix a l’alumne la clau assignatures. El valor d’aquesta clau és un diccionari que relaciona el nom de la matèria en el que està matriculat amb el seu codi.
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} >>> alumne['assignatures'] = {'matemàtiques':'MA1', 'català':'CA3'} >>> alumne {'nom': 'Pere', 'notes': [7.2, 3], 'aula': 1, 'assignatures': {'matemàtiques': 'MA1', 'català': 'CA3'}}
A partir de la versió Python 3.7, els diccionaris mantenen l’ordre d’inserció. Això vol dir que les parelles clau es guarden en l’ordre en què s’afegeixen al diccionari. El diccionari es desa mantenint l’ordre en el qual ha estat creat. Si afegiu un nou element, aquest sempre ocuparà l’última posició, tal com heu pogut comprovar en l’exemple anterior.
Abans de Python 3.7, els diccionaris no mantenien cap ordre. Les dades es podien retornar en un ordre qualsevol, independent de com es van inserir.
Els diccionaris són molt eficients a l’hora d’accedir als valors a través de les claus, gràcies a la seva implementació basada en taules hash. Això fa que l’accés, la modificació i l’eliminació de parelles clau siguin molt ràpids.
Taules hash
Una taula hash és una estructura de dades que emmagatzema informació en forma de parells clau-valor. Opera fent servir una funció anomenada “hash”, que converteix la clau en un índex dins la taula. D’aquesta manera, pot trobar ràpidament un valor a partir d’una clau. Les taules hash s’utilitzen en molts llenguatges de programació per buscar, afegir o eliminar dades de manera eficient.
L’ús dels diccionaris aporta llegibilitat en treballar amb estructures de dades. La possibilitat d’utilitzar una clau descriptiva millora la comprensió del codi respecte a l’ús de la posició en les llistes.
En emmagatzemar les dades en un diccionari, l’accés a través de la clau permet identificar el camp que s’utilitza, com podeu comprovar en l’exemple següent.
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1, 'cognom': 'Cases'} >>> f"{alumne['nom']} {alumne['cognom']}" 'Pere Cases'
Si la informació s’hagués desat en una llista, s’hauria d’haver utilitzat la posició per accedir a la dada, dificultant la llegibilitat del codi i, en aquest cas entendre que s’està creant una cadena amb el nom i el cognom de l’alumne.
>>> alumne = ['Pere', [7.2, 3], 1, 'Cases'] >>> f"{alumne[0]} {alumne[3]}" 'Pere Cases'
Per obtenir la longitud d’un diccionari, es fa servir la funció len(), que retorna el nombre total de parelles clau en un diccionari.
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} >>> len(alumne) 3
Creació de diccionaris
Els diccionaris són una estructura de dades molt flexible i es poden crear de diverses maneres sense necessitat d’utilitzar mètodes específics.
Creació de diccionaris buits
Podeu començar creant un diccionari buit i, posteriorment, afegir-hi elements:
>>> dic = {}
El concepte de diccionari buit és equiparable amb el de la llista buida: són estructures preparades per a emmagatzemar dades quan convingui.
Creació de diccionaris amb parelles clau
Un diccionari també es pot crear directament especificant les parelles clau entre claudàtors {}. En aquest cas, cada clau es relaciona amb un valor. És la manera que heu utilitzat per a crear diccionaris fins ara.
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1}
Creació de diccionaris amb la funció dict()
La funció integrada dict() és una altra manera comuna de crear diccionaris. Accepta diferents formats d’entrada per generar diccionaris, i és anàloga a les funcions int(), str() o list() que ja coneixeu. Són funcions que intenten fer una transformació al tipus de dada indicat a partir dels arguments introduïts, sempre que sigui possible, és a dir, sempre que els arguments coincideixin amb el docstring de la funció.
A continuació veureu alguns exemples de com es poden crear diccionaris amb la funció dict().
La funció dict() i el mètode copy() només produeixen còpies superficials. Això significa que es fa una nova còpia de totes les dades que són immutables, però si hi ha valors que són mutables, l’únic que es crea és una nova referència a la dada.
Diccionari creat amb parelles de tuples o llistes
Podeu passar una llista de tuples (o llistes) amb parelles clau-valor com a argument de la funció dict().
>>> llista_alumne = [('nom', 'Pere'), ('notes', [7.2, 3]), ('aula', 1)] >>> dict(llista_alumne) {'nom': 'Pere', 'notes': [7.2, 3], 'aula': 1}
Diccionari creat amb arguments de paraula clau
També podeu crear un diccionari passant els arguments com a paraules clau dins de la funció dict(). Els noms dels arguments es convertiran en claus:
>>> alumne = dict(nom="Pere", notes=[7.2, 3], aula=1) >>> alumne {'nom': 'Pere', 'notes': [7.2, 3], 'aula': 1}
En aquest cas, la clau serà el nom de l’argument (nom, notes, aula) i el valor serà el valor que s’assigna a l’argument.
Còpia superficial d’un diccionari
També podeu crear un nou diccionari copiant-ne un de ja existent, utilitzant la mateixa funció dict():
>>> alumne_original = {'nom': 'Pere', 'notes': [7.2, 3], 'aula': 1} >>> alumne_nou = dict(alumne_original) >>> alumne_nou['aula'] = 33 >>> alumne_original {'nom': 'Pere', 'notes': [7.2, 3], 'aula': 1} >>> alumne_nou {'nom': 'Pere', 'notes': [7.2, 3], 'aula': 33}
Els dos diccionaris són iguals, però presenten un problema: els valors que són un tipus de dada mutable (com la llista de ‘notes’) només se n’ha copiat la referència. Això vol dir que la llista de notes de l‘alumne i l‘alumne_nou són la mateixa.
Mireu què passa si afegiu les línies següents al codi anterior:
>>> alumne['notes'].append(10) >>> alumne {'nom': 'Pere', 'notes': [7.2, 3, 10], 'aula': 1} >>> alumne_nou {'nom': 'Pere', 'notes': [7.2, 3, 10], 'aula': 33}
La còpia d’un diccionari amb la funció dict() produeix només el que se’n diu una còpia superficial, i s’ha d’anar molt amb compte. Quan estudieu els mètodes de diccionaris s’explicarà com solucionar aquest problema.
Utilitzant la comprensió de diccionaris
Python també permet crear diccionaris utilitzant una estructura anomenada comprensió de diccionaris, similar a les list comprehensions. Aquesta tècnica permet crear diccionaris de manera eficient, en una sola línia de codi.
En aquest codi es crea un diccionari on les claus són els elements de la llista i els valors són els quadrats d’aquests elements.
>>> llista = [1, 2, 3, 4] >>> dicc = {x: x**2 for x in llista} >>> dicc {1: 1, 2: 4, 3: 9, 4: 16}
Mètodes de diccionaris
Ara que ja heu vist com crear i manipular diccionaris de manera bàsica, és hora d’aprendre els mètodes més comuns que podeu utilitzar per treballar amb ells de manera més eficient. Els mètodes dels diccionaris us permetran obtenir informació, modificar-los i fer operacions amb les seves dades.
Obtenir el valor d'una clau: ''get()''
El mètode get() permet accedir al valor associat a una clau sense que es produeixi un error si la clau no existeix en el diccionari.
La seva sintaxi és la següent:
diccionari.get(clau, valor_per_defecte)
El següent codi mostra l’ús del mètode get().
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} >>> alumne.get('nom') 'Pere' >>> alumne.get('nmo') >>> alumne.get('matèria', False) False
El mètode get() evita l’error que es produeix quan s’intenta accedir a una clau inexistent en un diccionari. A més a més de la clau, admet un segon argument opcional. Si no es proporciona el segon element, get() retorna None en comptes de llençar un error (en l’exemple, quan busqueu el valor de nmo).
Si es proporciona un segon argument al mètode i el diccionari no conté la clau que es busca retorna aquest segon argument. És una manera útil per comprovar si les consultes han estat efectives.
Obtenir les claus d'un diccionari: ''keys()''
El mètode keys() retorna un objecte especial que conté totes les claus del diccionari. Aquest objecte, tot i que no és una llista pròpiament, es pot utilitzar per recórrer tot el diccionari. Si es vol convertir aquest resultat (els dels mètodes següents) en una llista, se’ls ha d’aplicar una conversió, però en general no cal:
La seva sintaxi és la següent:
diccionari.keys()
Aquest mètode és útil per si voleu iterar per les claus d’un diccionari. A continuació teniu un exemple:
>>> alumne.keys() dict_keys(['nom', 'notes', 'aula']) >>> list(alumne.keys()) ['nom', 'notes', 'aula']
Obtenir els valors del diccionari: ''values()''
Aquest mètode permet obtenir fàcilment tots els valors sense haver d’accedir-los individualment. També retorna un objecte que es pot convertir en una llista.
La seva sintaxi és la següent:
diccionari.values()
El següent codi obté tot el contingut del diccionari alumne i el mostra:
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} >>> alumne.values() dict_values(['Pere', [7.2, 3], 1]) >>> list(alumne.values()) ['Pere', [7.2, 3], 1]
Obtenir parelles clau: ''items()''
El mètode items() retorna un objecte que conté totes les parelles clau del diccionari com a tuples.
La seva sintaxi és la següent:
diccionari.items()
Això és especialment útil si voleu treballar amb les claus i els valors junts en bucles o altres operacions.
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} >>> alumne.items() dict_items([('nom', 'Pere'), ('notes', [7.2, 3]), ('aula', 1)]) >>> list(alumne.items()) [('nom', 'Pere'), ('notes', [7.2, 3]), ('aula', 1)]
Eliminar i retornar el valor d'una clau: ''pop()''
El mètode pop() elimina una clau del diccionari i retorna el valor associat. Si la clau no existeix, podeu passar un valor per defecte per evitar errors.
La seva sintaxi és la següent:
diccionari.pop(clau, valor_per_defecte)
Aquest mètode és útil quan necessiteu accedir i eliminar un element alhora:
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} >>> alumne.pop('aula') 1 >>> alumne {'nom': 'Pere', 'notes': [7.2, 3]} >>> alumne.pop('aula') Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'aula' >>> alumne.pop('aula', False) False
Fixeu-vos que el segon argument, quan es fa la crida alumne.pop(‘aula’, False), el False, permet evitar l’error en cas que no existeixi la clau, i retornar el valor especificat.
Actualitzar o afegir parelles clau: ''update()''
El mètode update() permet actualitzar múltiples valors alhora o afegir noves parelles clau.
La seva sintaxi és la següent:
diccionari.update(altre_diccionari)
El següent codi afegeix al diccionari alumne, el contingut del diccionari actualitzacions:
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} >>> actualitzacions = {'cognom': 'Cases', 'edat': 33} >>> alumne.update(actualitzacions) >>> alumne {'nom': 'Pere', 'notes': [7.2, 3], 'aula': 1, 'cognom': 'Cases', 'edat': 33}
Buida el diccionari: ''clear()''
El mètode clear() elimina tots els elements del diccionari, deixant-lo buit.
La seva sintaxi és la següent:
diccionari.clear()
A l’exemple següent s’elimina tot el contingut del diccionari alumne:
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} >>> alumne.clear() >>> alumne {}
Crear una còpia superficial del diccionari: ''copy()''
El mètode copy() retorna una còpia superficial del diccionari. Això vol dir que es fa una còpia del diccionari, però no dels objectes mutables que pot contenir (llistes, diccionaris). Més ben dit, es copia només la referència, però l’objecte és el mateix.
És el mateix tipus de còpia que s’obté quan s’utilitza la funció dict() amb un altre diccionari com a argument.
La seva sintaxi és la següent:
diccionari.copy()
El següent codi crea una còpia superficial del diccionari alumne:
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} >>> nou_alumne = alumne.copy() >>> nou_alumne {'nom': 'Pere', 'notes': [7.2, 3], 'aula': 1}
En aquest cas:
- La nova instància
alumne_copiaté una còpia de les clausnom,notesiaula. - El valor de la clau “nom” és una cadena immutable (“Pere”), així que es copia la referència, però això no és un problema perquè les cadenes no es poden modificar.
- El valor de la clau “aula” és un enter immutable, així que es copia correctament.
- El valor de la clau “notes” és una llista mutable, i aquí és on entra en joc la còpia superficial. Tant el diccionari original com la còpia comparteixen la mateixa llista.
Fixeu-vos què passa amb el codi següent:
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} >>> nou_alumne = alumne.copy() >>> nou_alumne['notes'].append(10) >>> nou_alumne {'nom': 'Pere', 'notes': [7.2, 3, 10], 'aula': 1} >>> alumne {'nom': 'Pere', 'notes': [7.2, 3, 10], 'aula': 1}
Com evitar el problema de les còpies superficials Tot i que en Python existeixen llibreries per a tot, la manera més senzilla per arreglar el problema existent amb les còpies superficials és localitzar-lo (és a dir, tenir clar quins són els valors amb tipus de dada mutables) i crear-ne una nova instància de la dada manualment.
En l’exemple anterior, només cal accedir al valor notes i crear una llista nova, que serà independent de l’original. Fixeu-vos en el fragment de codi següent:
>>> alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} >>> nou_alumne = alumne.copy() >>> nou_alumne['notes']=[] >>> alumne {'nom': 'Pere', 'notes': [7.2, 3], 'aula': 1} >>> nou_alumne {'nom': 'Pere', 'notes': [], 'aula': 1}
Amb aquest procés s’ha assignat a la clau notes una llista nova. En canvi, el mètode copy() només proporcionava una nova referència a la mateixa llista.
Iterar un diccionari en Python
Després d’aprendre a crear diccionaris i treballar amb els seus mètodes, un altre pas essencial és aprendre com iterar per sobre d’un diccionari. Iterar significa recórrer les seves claus, valors o parelles clau per poder processar o modificar la informació que conté.
Hi ha diverses maneres per accedir a la totalitat dels elements del diccionari. Tingueu en compte que en els següents exemples ja no s’utilitzarà l’intèrpret interactiu, sinó que el codi s’ha de passar a l’intèrpret amb un fitxer de Python.
Iterar per les claus
La manera més senzilla de recórrer un diccionari és iterar per les seves claus. Per això també s’anomena iteració natural. Quan utilitzeu un bucle for directament sobre el diccionari, per defecte, el bucle recorre les claus. Si voleu accedir al valor corresponent, només cal que utilitzeu la clau.
Al codi següent recorre tot el diccionari fent servir el for per iterar per les seves claus:
alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} for key in alumne: print(f'{key}')
I el resultat de l’execució és:
nom notes aula
Iterar per les parelles clau amb ''items()''
Si voleu iterar directament per sobre de les parelles clau, el mètode items() és la millor opció. Això us permet treballar amb ambdues a la vegada sense haver d’accedir-hi manualment.
Aquest mètode és més eficient i elegant que accedir al valor mitjançant la clau dins del bucle. A continuació teniu un exemple:
alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} for key, valor in alumne.items(): print(f'{key} {valor}')
I el resultat de l’execució és:
nom Pere notes [7.2, 3] aula 1
Iterar per sobre els valors amb ''values()''
Si només us interessa treballar amb els valors del diccionari, podeu utilitzar el mètode values() per iterar-hi directament.
Aquest exemple ho fa:
alumne = {'nom':'Pere', 'notes':[7.2, 3], 'aula':1} for valor in alumne.values(): print(f'{valor}')
I el resultat de l’execució és:
Pere [7.2, 3] 1
Exemple de la incorporació de diccionaris en un mòdul de funcions
L’objectiu d’aquest exemple és utilitzar diccionaris en Python per convertir una data del format dd/mm/aaaa (per exemple, 17/01/2030) a una cadena més llegible per als humans, com “disset de gener del 2030”. S’ha d’incorporar aquesta nova utilitat al mòdul calendari, per oferir l’opció de mostrar la data en aquest format.
S’aplicaran els principis de la programació estructurada i de la responsabilitat única per a afegir el nou codi al mòdul.
Primer pas: escriure el docstring i el test-driver de la funció
El codi que voleu escriure és molt clar que ha de ser una funció, donat que no hi ha cap dubte en els arguments que ha de rebre (en aquest cas es tracta d’un únic argument, que és la data 17/01/2030) i el resultat que ha de retornar la funció, que serà la cadena disset de gener del 2030.
def data_calendari_a_data_descriptiva(data): ''' Funció que transforma una data del tipus calendari a una cadena de text del tipus "disset de gener del 2030". Entrada: data de calendari Sortida: str ''' # test-driver per passar de data numérica a descriptiva if True: print(data_calendari_a_data_descriptiva('17/01/2030')) print(data_calendari_a_data_descriptiva('01/10/2030'))
Segon pas: refinar la funció
Els passos que cal seguir són:
- Aconseguir el dia, mes i any de la data
- Buscar la cadena (valor) associada al número (clau) del dia.
- Buscar la cadena (valor) associada al número (clau) del mes.
- Crear la cadena que cal retornar
Per aconseguir els passos 2 i 3, necessitareu definir dos diccionaris, un que relacioni el valor numèric dels nombres naturals (des de l’1 al 31), i un segon que relacioni els valors numèrics dels mesos amb el seu nom.
Caldrà que definiu dos diccionaris diferents com a constants, que poden estar dins de la mateixa funció.
Tercer pas: codificar la funció
Per a fer el codi cal que repasseu cadascuna de les obligacions de la funció (SRP) i determineu si s’han de subdividir en una nova funció, si cal cridar a una funció existent o es poden codificar directament dins la nova funció.
En aquest exemple, i per cadascuna de les línies del refinament:
- Existeix la funció
get_data()que fa específicament aquesta feina - Es pot codificar directament, ja que es tracta de donar el valor corresponent a una clau d’un diccionari.
- Es pot codificar directament, ja que es tracta de donar el valor corresponent a una clau d’un diccionari.
- Es pot codificar directament utilitzant les
f’strings
D’aquesta manera, caldrà definir dos diccionaris diferents: un que relacioni el valor numèric dels nombres naturals (des de l’1 al 31), i un segon que relacioni els valors numèrics dels mesos amb el seu nom.
El codi de la nova funció serà el següent:
def data_calendari_a_data_descriptiva(data): ''' Funció que transforma una data del tipus calendari a una cadena de text del tipus "disset de gener del 2030". Entrada: data de calendari Sortida: str ''' DIES_INT_A_STR = dies = { 1: "u", 2: "dos", 3: "tres", 4: "quatre", 5: "cinc", 6: "sis", 7: "set", 8: "vuit", 9: "nou", 10: "deu", 11: "onze", 12: "dotze", 13: "tretze", 14: "catorze", 15: "quinze", 16: "setze", 17: "disset", 18: "divuit", 19: "dinou", 20: "vint", 21: "vint-i-u", 22: "vint-i-dos", 23: "vint-i-tres", 24: "vint-i-quatre", 25: "vint-i-cinc", 26: "vint-i-sis", 27: "vint-i-set", 28: "vint-i-vuit", 29: "vint-i-nou", 30: "trenta", 31: "trenta-u"} MESOS_INT_A_STR = {1: "gener", 2: "febrer", 3: "març", 4: "abril", 5: "maig", 6: "juny", 7: "juliol", 8: "agost", 9: "setembre", 10: "octubre", 11: "novembre", 12: "desembre"} (d, m, a) = get_data(data) return f'{DIES_INT_A_STR[d]} de {MESOS_INT_A_STR[m]} de {a}'
Fixeu-vos que el resultat de l’execució del nou test-driver és:
disset de gener de 2030 u de octubre de 2030
És raonable que us pregunteu què es podia fer perquè el programa no faci faltes d’ortografia com en la segona entrada? La resposta seria crear el diccionari dels mesos amb les entrades {…,9: “de setembre”, 10: “d’octubre”, …} i ser coherent en el moment de construir la f’string.
Aquí hi ha el codi complet del mòdul (amb aquesta funció incorporada) perquè l’executeu i proveu:
import datetime ''' Mòdul per a treballar amb dates de calendari. Una data de calendari és una cadena del tipus dd/mm/aaaa On dd, mm i aaaa son enters que corresponen a un dia, mes i any d'una data real. Només es tindran en compte anys majors o iguals a zero. ''' def es_data_valida(cad): ''' Funció que determina si una data és vàlida (dd/mm/aaaa) Entrada: cadena qualsevol Output: Boolean ''' # 1. Comprovem que el format és correcte if not _es_data_format_correcte(cad): return False # calculem dia, mes i any (d, m, a) = get_data(cad) # 2. Comprovem que els enters de la data son correctes. return _es_data_real(d, m, a) def get_data(data): '''Funció que donada una data en format correcte retorna el dia, mes i any Entrada: str (Data en format correcte) Sortida: tupla d’enters (dia, mes, any) ''' valors = data.split('/') return (int(valors[0]), int(valors[1]), int(valors[2])) def _es_data_format_correcte(cadena): ''' Funció que determina si una cadena qualsevol té format dd/mm/aaaa, amb dd, mm i aaaa nombres enters Entrada: Cadena Sortida: Boolean ''' # controlar longitud if len(cadena) != 10: return False # que tingui 2 / camps = cadena.split('/') if len(camps) != 3: return False #cada camp amb la longitud que toca if not (len(camps[0]) == 2 and len(camps[1]) == 2 and len(camps[2]) == 4): return False #que tots siguin enters positius return camps[0].isdigit() and camps[1].isdigit() and camps[2].isdigit() def es_any_traspas(a): ''' Funció que diu si un any és o no de traspàs Entrada: enter positiu (any) Sortida: boolean ''' return (a % 400 == 0) or ( a % 4 == 0 and a % 100 != 0) def dies_mes(*args): ''' Funció sobrecarregada que mostra els dies que té un mes. Entrada: dos enters (corresponents a dia i any correctes) ó un str (data correcta) Sortida: enter ''' DIES_MES = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] if len(args) == 1: (d, m, a) = get_data(args[0]) else: m = args[0] a = args[1] dies = DIES_MES[m] # si l'any és de traspàs el febrer té 29 dies if m == 2 and es_any_traspas(a): dies = 29 return dies def _es_data_real(dia, mes, a): ''' Funció que determina di tres enters corresponen a un dia que existeix. Considerem que els anys vàlids només son els positius. Entrada: int, int, int Sortida: bool ''' # comprovem que l'any és correcte if a < 0: return False # comprovem que el mes és correcte if not (mes >= 1 and mes <= 12): return False # comprovem que el dia és correcte return dia >= 1 and dia <= dies_mes(mes, a) def dia_seguent(data): ''' Funció que rep una data i retorna el dia seguent Entrada: str (Data correcta) Sortida: str (data correcta) ''' (d, m, a) = get_data(data) if d == dies_mes(m, a): d = 1 m = m + 1 if m == 13: m = 1 a = a + 1 else: d = d + 1 return crea_data(d, m, a) def crea_data(d, m, a): ''' Funció que crea una data en format vàlid Entrada: 3 int Sortida: str (data en format correcte) ''' return f'{d:02}/{m:02}/{a:04}' def _dia_setmana(data): ''' Funció que ens dona el número del dia de la setmana en que cau una data vàlida Entrada: Data vàlida Sortida: int (0 diumenge, 1 dilluns, 2 dimarts, etc) ''' dia, mes, year = get_data(data) a = (14 - mes) // 12 b = year - a c = mes - 2 + a * 12 d = b // 4 e = b // 100 f = b // 400 g = 31 * c // 12 h = dia + b + d -e + f + g i = h % 7 return i def _nom_dia_setmana(num): ''' Funció que ens dona el nom d'un dia de la setmana. 0 dilluns, 1 dimarts etc... Entrada enter ente el 0 i el 6 (inclosos) Sortida: string ''' DIA_SETMANA = [ 'diumenge', 'dilluns', 'dimarts', 'dimecres', 'dijous', 'divendres', 'dissabte'] return DIA_SETMANA[num] def dia_anterior(data): ''' Funció que rep una data i retorna el dia anterior No funciona per la data 31/12/0001 perquè en retorna una incorrecta). Entrada: str (Data correcta) Sortida: str (data correcta) ''' (d, m, a) = get_data(data) d = d - 1 if d == 0: m = m - 1 if m == 0: m = 12 a = a - 1 d = dies_mes(m, a) return crea_data(d, m, a) def ultim_dia_mes(data): ''' Funció que retorna l'últim dia del mes de la data Entrada: str (data correcta) Sortida: str (data correcta) ''' (d, m, a) = get_data(data) d = dies_mes(data) return crea_data(d, m, a) def avui(): ''' Funció que obté la data del sistema Entrada: res (sense arguments) Sortida: data vàlida ''' avui = datetime.datetime.now() return crea_data(avui.day, avui.month, avui.year) def afegir_any(data): ''' Funció que afegeix un any a una data concreta. Entrada: data de calendari Sortida: data de calendari ''' (d, m, a) = get_data(data) if d == 29 and m == 2: d = 1 m = 3 return crea_data(d, m, a + 1) def minorar_any(data): ''' Funció que resta un any a una data concreta. Entrada: data de calendari Sortida: data de calendari ''' (d, m, a) = get_data(data) return crea_data(d, m, a - 1) def afegir_mes(data): ''' Funció que afegeix un any a una data concreta. Entrada: data de calendari Sortida: data de calendari ''' (d, m, a) = get_data(data) m = m + 1 # si és el desembre if m == 13: m = 1 a = a + 1 # si el mes era més curt if d > dies_mes(m , a): d = dies_mes(m , a) return crea_data(d, m, a) # Funcions anàlogues als dunder def to_str(data): ''' Funció que presenta un objecte data en format llegible Entrada: str (data correcta) Sortida: str ''' return f'{data}, {_nom_dia_setmana(_dia_setmana(data))}' def es_major(data1, data2): ''' Funció que diu si la primera data és major que la segona Entrada: str (nombre racional) Sortida: bool ''' (d1, m1, a1) = get_data(data1) (d2, m2, a2) = get_data(data2) primer = (a1, m1, d1) segon = (a2, m2, d2) return primer > segon def compara_dates(data1, data2): ''' Funció comparator de dates de calendari Entrada: 2 str (dates correctes) Sortida: int 1 si data1 és cronològicament posterior a data2 0 si data1 i data2 són la mateixa -1 si data1 és cronològicament anterior a data2 ''' resultat = -1 if es_major(data1, data2): resultat = 1 elif data1 == data2: # l'equivalència val la de la classe base resultat = 0 return resultat def key_dates(data): ''' Funció key perr implementar l'ordre cronològic a les dates de calendari Entrada: data de calendari Sortida: tupla ''' (d, m, a) = get_data(data) return (a, m, d) def data_calendari_a_data_descriptiva(data): ''' Funció que transforma una data del tipus calendari a una cadena de text del tipus "disset de gener del 2030". Entrada: data de calendari Sortida: str ''' DIES_INT_A_STR = dies = { 1: "u", 2: "dos", 3: "tres", 4: "quatre", 5: "cinc", 6: "sis", 7: "set", 8: "vuit", 9: "nou", 10: "deu", 11: "onze", 12: "dotze", 13: "tretze", 14: "catorze", 15: "quinze", 16: "setze", 17: "disset", 18: "divuit", 19: "dinou", 20: "vint", 21: "vint-i-u", 22: "vint-i-dos", 23: "vint-i-tres", 24: "vint-i-quatre", 25: "vint-i-cinc", 26: "vint-i-sis", 27: "vint-i-set", 28: "vint-i-vuit", 29: "vint-i-nou", 30: "trenta", 31: "trenta-u" } MESOS_INT_A_STR = { 1: "gener", 2: "febrer", 3: "març", 4: "abril", 5: "maig", 6: "juny", 7: "juliol", 8: "agost", 9: "setembre", 10: "octubre", 11: "novembre", 12: "desembre" } (d, m, a) = get_data(data) return f'{DIES_INT_A_STR[d]} de {MESOS_INT_A_STR[m]} de {a}' if __name__ == "__main__": # Si el mòdul s'executa com a programa principal # TEST-Driver es_data_valida if False: # 1. Segons si el format és correcte print(es_data_valida('14/03/20')) print(es_data_valida('14/3/2021')) print(es_data_valida('14/03/2021')) print(es_data_valida('4/03/2021')) print(es_data_valida('14-03-2021')) print(es_data_valida('14-03-02021')) print(es_data_valida('1.4/03/2021')) print(es_data_valida('data valida')) print(es_data_valida('ab/12/abcd')) print(es_data_valida('12/12/-212')) print() # 2. Segons si la data és correcta # Si es té en compte que el mes sigui correcte print(es_data_valida('14/13/2020')) print(es_data_valida('14/00/2020')) print(es_data_valida('14/03/-002')) print() # Si es té en compte que l’any sigui correcte print(es_data_valida('14/03/0000')) print(es_data_valida('')) print() # Si es té en compte l’any de traspàs print(es_data_valida('29/02/2020')) print(es_data_valida('29/02/2021')) print(es_data_valida('29/02/2000')) print(es_data_valida('29/02/2100')) print() # Si es té en compte que el dia sigui correcte print(es_data_valida('31/01/2021')) print(es_data_valida('31/04/2021')) print(es_data_valida('32/07/2021')) print(es_data_valida('31/05/2021')) print(es_data_valida('00/03/2021')) print(es_data_valida('31/12/2021')) print() # Test driver get_data if False: print(get_data('19/03/2021')) print() # Test_driver _es_data_format_correcte if False: for cadena in ['14/03/20', '14/3/2021', '14/03/2021', '4/03/2021', '14-03-2021', '14-03-02021', '1.4/03/2021', 'data valida', 'ab/12/abcd', '12/12/-212'] : print(_es_data_format_correcte(cadena)) print() # Test driver es_any_traspàs if False: print(es_any_traspas(2000)) print(es_any_traspas(2100)) print(es_any_traspas(2020)) print(es_any_traspas(2021)) print() # Test driver dies_mes if False: print(dies_mes(1, 2021)) print(dies_mes(2, 2021)) print(dies_mes(2, 2020)) print(dies_mes(4, 2021)) print() # Test driver _es_data_real if False: # 2. Segons si la data és correcta # Si es té en compte que el mes sigui correcte print(_es_data_real(14, 13, 2020)) print(_es_data_real(14, 0, 2020)) print(_es_data_real(14, 3, -123)) print() # Si es té en compte que l’any sigui correcte print(_es_data_real(14, 3, 0)) print(_es_data_real(14, 3, -10)) print() # Si es té en compte l’any de traspàs print(_es_data_real(29,2,2020)) print(_es_data_real(29,2,2021)) print(_es_data_real(29,2,2100)) print(_es_data_real(29,2,2000)) print() # Si es té en compte que el dia sigui correcte print(_es_data_real(31,1,2021)) print(_es_data_real(31,4,2021)) print(_es_data_real(32,7,2012)) print(_es_data_real(31,5,2021)) print(_es_data_real(0,3,2021)) print(_es_data_real(31,12,2021)) print() # test-driver dia_seguent if False: print(dia_seguent('31/12/2000')) print(dia_seguent('31/03/2000')) print(dia_seguent('28/02/2000')) print() # test_driver crea data if False: print(crea_data(3, 5, 2000)) print(crea_data(33, 5, 2000)) # test_driver dia_setmana if False: print(_dia_setmana('26/03/2021')) print(_dia_setmana('18/04/2005')) print() # test_driver _nom_dia_setmana if False: print(_nom_dia_setmana(0)) print(_nom_dia_setmana(3)) print(_nom_dia_setmana(6)) print() # test-driver dia_anterior if False: print(dia_anterior('01/03/2000')) print(dia_anterior('31/03/2030')) print(dia_anterior('01/01/2030')) print() # test-driver de la sobrecàrrega de dies_mes() if False: print(dies_mes(1, 2021)) print(dies_mes(2, 2021)) print(dies_mes('31/03/2030')) print(dies_mes('01/02/2030')) print() # test-driver ultim_dia_mes if False: print('ultim_dia_mes()') print(ultim_dia_mes('01/02/2000')) print(ultim_dia_mes('01/02/2030')) print() # test-driver avui if False: print('avui') print(avui()) print() # test-drivers nous pels dunders: if False: print('to_str()') print(to_str('01/03/2000')) print(to_str('31/03/2030')) print() if False: print('es_major()') print(es_major('01/02/2030', '02/02/2000')) print(es_major('01/02/2030', '01/02/2030')) print(es_major('01/02/2030', '02/02/2030')) print() if False: print('compara_dates()') print(compara_dates('01/02/2030', '02/02/2000')) print(compara_dates('01/02/2030', '01/02/2030')) print(compara_dates('01/02/2030', '02/02/2030')) if False: print(afegir_any('01/02/2030')) print(afegir_any('29/02/2000')) print(afegir_mes('31/12/2030')) print(afegir_mes('31/01/2030')) print(afegir_mes('31/03/2030')) if False: print(minorar_any('01/02/2030')) print(minorar_any('31/12/2030')) print(minorar_any('31/01/2030')) print() if False: print('funció key_dates') print(key_dates('29/02/2004')) print(key_dates('02/01/1990')) print() # test-drive per passar de data numérica a descriptiva if True: print(data_calendari_a_data_descriptiva('17/01/2030')) print(data_calendari_a_data_descriptiva('01/10/2030'))



