Llistes de llistes en Python
En aquesta lliçó es parlarà de les llistes de llistes o llistes multidimensionals. Es començarà amb un breu repàs de les característiques de les llistes i la seva aplicació a les llistes de llistes. Posteriorment, es mostrarà una de les principals utilitats de les llistes multidimensionals, que és la possibilitat d’ordenar segons diferents camps.
I per acabar veureu com passar funcions com a paràmetres de funcions, com es fa amb les key functions quan es passen com a paràmetre a les eines d’ordenació del sistema, el mètode sort() i la funció sorted().
Llistes multidimensionals i algorismes d'ordenació
En el món de la programació en Python, les llistes són una eina fonamental. Permeten emmagatzemar dades i gestionar-les de manera eficient. Una llista és com un contenidor que emmagatzema una successió de diversos elements. Quan aquests elements que pertanyen a una llista són al seu torn altres llistes, us trobeu davant d’una llista de llistes.
Les llistes de llistes estan molt relacionades amb l’ordre i els criteris d’ordenació, ja que és molt comú gestionar llistes de llistes i haver de fer llistats ordenats segons diferents criteris. En aquest apartat es començarà fent un repàs de què sabeu sobre les llistes, amb exemples i explicacions us acostumareu a l’ús d’estructures de dades consistents en llistes de llistes i per acabar s’aprofundirà en el tema dels criteris d’ordenació i com ordenar llistes de llistes segons criteris diferents.
Les llistes en Python
Una llista de Python és un tipus de dada que permet emmagatzemar col·leccions d’elements. Aquesta col·lecció pot contenir elements de diferents tipus de dades, com nombres, cadenes de text, booleans o fins i tot altres llistes, i l’ordre a què es fa referència és un ordre basat en la posició de l’element: hi ha l’element que ocupa la primera posició, el que ocupa la segona i així successivament.
Python té diversos tipus d’objectes incorporats que serveixen per a guardar col·leccions de dades: són els tipus list, tuple, dict i set. Tot i que tots serveixen per estructurar diversos elements, cadascun d’ells té les seves particularitats (per exemple, el que diferencia una llista d’una tupla és la mutabilitat).
Una llista de Python té les característiques següents:
- És ordenada: l’ordre és la posició que ocupen els elements. A més, es pot accedir a l’element a través de la posició, és a dir, podem accedir a la posició
ide lalistlista1 amb la instruccióllista1[i]. Això s’anomena indexació de les dades de tipuslist. - És mutable: es poden modificar els valors pertanyents a una llista, afegir-ne de nous i eliminar-ne.
- No implementa la unicitat: les llistes poden contenir elements duplicats.
- Són dinàmiques en temps d’execució: la longitud d’una llista pot determinar-se en el temps d’execució del programa, no ha d’estar determinada amb antelació.
- En Python, no és necessari que tots els elements d’una llista siguin del mateix tipus.
És un tipus de dada que ja coneixeu. Però abans d’entrar en el tema de les llistes de llistes i l’ordre segons diferents criteris, es farà un breu repàs del tipus de dada list i les seves propietats.
Crear llistes
La creació d’una llista es pot fer, entre altres, amb els mecanismes següents:
El nom tècnic en Python per determinar el que estem definint com a seqüència és tipus de dada iterable, en el sentit que es pot accedir als elements d’una col·lecció i es pot recórrer sencera. En realitat, un iterable en Python és una classe que implementa els dunders __it__ i __next__. Simplificant, es podria dir que una iterable és un objecte que es pot recórrer completament utilitzant l’estructura de control for. Les cadenes, llistes, tuples i diccionaris, entre altres tipus de dades, són iterables.
- Escrivint els elements de la llista directament entre claudàtors
- llista = [1, 2, 3]
- Utilitzant la funció list(). Aquesta funció és capaç de crear una llista si amb qualsevol argument del ‘tipus’ que hem anomenat seqüències.
- >>> list('hola')
- ['h', 'o', 'l', 'a']
- >>> list([1, 2, 3])
- [1, 2, 3]
- >>> list((1, 2, 3))
- [1, 2, 3]
- Afegir elements amb un bucle. Podeu crear un bucle i utilitzar-lo per afegir elements a la llista.
- Llistes per comprensió. Les list comprehensions són una eina de Python que permet la creació de manera molt ràpida i eficient de llistes d’elements. Aquesta tècnica té un defecte: el codi resultant és molt críptic i difícil de llegir. És per això que en aquest curs no se’n farà un ús extensiu. Fixeu-vos, però, en els resultats dels següents exemples, on es crea una llista de quadrats i una altra de nombres parells utilitzant la tècnica de les list comprehension:
- >>> [x**2 for x in range(10)]
- [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
- >>> [x for x in range(10) if x % 2 == 0]
- [0, 2, 4, 6, 8]
Indexació en les llistes
Les llistes estan indexades. Això significa que és possible accedir a un element d’una llista a través d’un índex. L’índex és la posició que ocupa l’element dins de la llista.
>>> llista = [2, 3, 4] >>> llista[1] 3
Operadors per treballar amb llistes
Aquí teniu un breu repàs dels operadors que la classe list pot utilitzar:
- Operador de concatenació
+: operador binari que concatena dues o més llistes.
- >>> [1, 2, 3] + ['a', 'b']
- [1, 2, 3, 'a', 'b']
- Operador de repetició
*: operador binari que opera amb una llista i un enter i repeteix la llista el nombre de vegades que indica l’enter.
- >>> 5 * '*'
- '*****'
- Operadors d’igualtat
==i!=: tots els objectes de Python es poden comparar utilitzant aquests operadors. - Operadors de comparació
>,<,>=,⇐: les llistes tenen una relació d’ordre definida que permet comparar-les, sempre que els seus elements siguin comparables un a un. L’ordre és l’ordre lexicogràfic entre els elements que ocupen la mateixa posició. Fixeu-vos en l’últim cas de l’exemple:
- >>> llista1 = [1, 'Maria', (1,2,3)]
- >>> llista2 = [1, 'Maria', (1,2)]
- >>> llista3 = [1, 2, 3]
- >>> llista4 = [2, 3, 4]
- >>> llista1 > llista2
- True
- >>> llista1 > llista3
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- TypeError: '>' not supported between instances of 'str' and 'int'
- >>> llista1 > llista4
- False
Iterar llistes
Les llistes són tipus de Python iterables. Això indica que es poden recórrer completament utilitzant un bucle for. Hi ha dues maneres de fer-ho:
- Per ordre natural: s’anomena així perquè és el mètode més senzill.
- llista = [12, 2, 21]
- for element in llista:
- print(element)
- Per posició: quan la posició és necessària en el cos del bucle, s’utilitza la indexació de llistes per a fer el recorregut.
- llista = [12, 2, 21]
- for i in range(len(llista)):
- print(llista[i])
Quan hi hagi la possibilitat d’interrompre la iteració abans d’arribar al final, s’ha d’utilitzar un buclewhileamb la condició composta. - Sortir d’una iteració amb el
while: algorísmicament, elforrecorre tota la llista. En aquest exemple, se sortirà si es troba el valor 21 (o si s’acaben els elements de la llista).
- llista = [12, 2, 21]
- i = 0
- while i < len(llista) and llista[i] != 21:
- print(llista[i])
- i += 1
Alguns dels mètodes de llistes
Els mètodes de llistes que més heu utilitzat són els següents. Per saber què fan, podeu consultar el docstring que proporciona l’ajuda.
Fixeu-vos que alguns d’aquests mètodes produeixen un error en determinades condicions. És important que cada vegada que en feu ús us assegureu que aquest error no es produirà.
- El mètode append():
- append(self, object, /)
- | Append object to the end of the list.
- El mètode index():
- index(self, value, start=0, stop=9223372036854775807, /)
- | Return first index of value.
- |
- | Raises ValueError if the value is not present.
- El mètode insert:
- | insert(self, index, object, /)
- | Insert object before index.
- El mètode pop():
- pop(self, index=-1, /)
- | Remove and return item at index (default last).
- |
- | Raises IndexError if list is empty or index is out of range.
- El mètode remove():
- remove(self, value, /)
- | Remove first occurrence of value.
- |
- | Raises ValueError if the value is not present.
Retallar llistes (//slicing//)
Les llistes són una seqüència, i igual com passa amb els str es poden formar subllistes retallant o utilitzant el slicing. Comproveu els exemples següents:
>>> llista = [12, 2, 21, 3, 5] >>> llista[2:4] [21, 3] >>> llista[2:] [21, 3, 5] >>> llista[:2] [12, 2] >>> llista[-1] 5
Ordenar llistes
Les llistes es poden ordenar sempre que hi hagi un ordre definit entre els seus elements. Les eines que ofereix el Python per ordenar les llistes són:
- El mètode sort(): ordena la llista in situ.
- >>> llista = [12, 2, 21, 3, 5]
- >>> llista.sort()
- >>> llista
- [2, 3, 5, 12, 21]
- La funció sorted(): crea una nova llista ordenada, mantenint la llista original.
- >>> llista = [12, 2, 21, 3, 5]
- >>> sorted(llista)
- [2, 3, 5, 12, 21]
- >>> llista
- [12, 2, 21, 3, 5]
També es poden utilitzar les eines del sistema per ordenar segons altres criteris, proporcionant al mètode sort() o a la funció sorted() una clau o transformació prèvia per aplicar als elements de la llista abans de ser ordenats. En aquest exemple s’ordena una llista de paraules aplicant el criteri de la longitud de la paraula:
>>> llista = ['un', 'dos', 'tres', 'quatre', 'cinc'] >>> llista.sort(key = len) >>> llista ['un', 'dos', 'tres', 'cinc', 'quatre']
Les llistes de llistes en Python
En Python, les llistes de llistes no són més que llistes, els elements de les quals són altres llistes.És una estructura que permet emmagatzemar dades de manera jerarquitzada, i permet organitzar i ordenar la mateixa informació considerant diferents aspectes.
En aquest apartat es parlarà de les llistes de llistes, com crear-les i com accedir als seus elements. També aprendreu a organitzar la informació, i a presentar-la en llistats ordenats segons diferents criteris, aprofundint en l’ordre personalitzat utilitzant el sort() i el sorted() i ordenant segons els diferents camps en una llista de llistes.
Crear llistes de llistes
- Creació manual. Per crear una llista de llistes, només cal crear una llista principal amb elements que, alhora, siguin altres llistes.
- >>> llista1 = [[1, 2, 3], ['u', 'dos'], '']
- Utilitzant l’operador repetició. Sembla que crear una llista de llistes mitjançant la repetició d’una llista inicial hauria de ser una manera senzilla d’obtenir una llista de llistes. Heu d’anar amb compte, però, amb el problema de les llistes i les seves referències, ja que en realitat aquest operador crea diverses referències a la mateixa llista, com es mostrarà més endavant.
- >>> llista2 = [list('...')] * 3
- >>> llista2
- [['.', '.', '.'], ['.', '.', '.'], ['.', '.', '.']]
En aquest exemple, el que es multiplica per tres és el que ens retornalist(), que és una referència a la llista. Per això, s’obtenen tres referències a la mateixa llista. - Utilitzant un
for. Amb l’ús d’un bucle us podeu assegurar que cadascuna de les llistes d’una llista de llistes és independent. Sembla que el resultat d’aquest fragment de codi és el mateix que l’anterior, però de seguida veureu com no és així.
- cadena = '...'
- llista_taulell = []
- for i in range(3):
- llista_taulell.append(list(cadena))
En molts llenguatges, les llistes de llistes on cada llista interna té la mateixa quantitat d’elements s’anomenen matrius. En Python es prefereix utilitzar el terme de llista de llistes per emfatitzar que s’han de tractar com a una llista.
Longitud de les llistes de llistes
La longitud d’una llista de llistes és la quantitat d’elements que té la llista principal. Com que una llista de llistes no és més que una llista, la longitud es pot calcular amb la funció len(), com la de qualsevol llista.
>>> llista1 = [[1, 2, 3], ['u', 'dos'], ''] >>> len(llista1) 3 >>> llista2 = [list('...')] * 3 >>> len(llista2) 3
Accés als elements
Per entendre com s’accedeix als elements d’una llista de llistes, és necessari no perdre de vista que s’ha de treballar amb la llista principal, i indexar l’element oportú. Com que aquest element serà una llista, també haureu d’indexar la llista interna fins a aconseguir l’element desitjat.
Accés a un element d'una llista de llistes
En el codi següent veureu com per accedir a l’element que ocupa la primera posició de la llistaB (el nombre 4), quan aquesta llista ocupa la segona posició d’una llista de llistes.
>>> llistaA = [1, 2, 3] >>> llistaB = [4, 5, 6] >>> llista = [llistaA, llistaB] >>> llista [[1, 2, 3], [4, 5, 6]] >>> llistaB[0] 4 >>> llista[1][0] 4
Jugada en el tres en ratlla mal feta
El jugador que té les fitxes rodones fa la primera jugada a les taules del tres en ratlla i vol posar la seva fitxa a la cantonada superior dreta. En el codi següent es mostra com es crea el taulell, el taulell abans de cap jugada i el taulell després de la primera jugada.
Com podeu observar, el resultat obtingut en el taulell no és l’esperat.
>>> taulell = [list('...')] * 3 >>> taulell [['.', '.', '.'], ['.', '.', '.'], ['.', '.', '.']] >>> taulell[1][2] = 'O' >>> taulell [['.', '.', 'O'], ['.', '.', 'O'], ['.', '.', 'O']]
El problema no recau en la modificació de l’element, sinó en el fet que la creació del taulell s’ha fet amb tres referències a la mateixa llista. Això ha comportat que si es modifica un element en una d’elles, com que en realitat totes apunten al mateix espai de memòria, es produeixi la modificació en totes les línies del taulell.
Jugada del tres en ratlla ben feta
El taulell s’ha de construir creant tres llistes independents i unint-les en una nova llista. Si executeu aquest codi comprovareu que en aquest cas es registra la jugada correctament.
I el resultat de l’execució és:
cadena = '...' taulell = [] for i in range(3): taulell.append(list(cadena)) print(taulell) taulell[0][2] = 'O' print(taulell)
$ python3 tres_en_ratlla.py [['.', '.', '.'], ['.', '.', '.'], ['.', '.', '.']] [['.', '.', 'O'], ['.', '.', '.'], ['.', '.', '.']]
Afegir elements a la llista principal
Amb el mètode append() podeu afegir elements a una la llista principal, de la mateixa manera que els afegiríeu a qualsevol llista. Heu de vigilar, però, ja que si teniu la intenció d’afegir una nova llista, cal que l’argument del mètode sigui una dada de tipus list.
Afegir una nova llista a una llista multidimensional
Fixeu-vos en el resultat de les instruccions següents:
i en el resultat d’aquestes altres instruccions:
>>> llista = [[1], [1, 2], [1, 2, 3]] >>> llista.append(4) >>> llista [[1], [1, 2], [1, 2, 3], 4]
En el primer cas s’ha afegit a la llista el nombre enter 4. En el segon cas s’hi ha afegit una llista d’enters que conté únicament el nombre 4.
>>> llista.append([4]) >>> llista [[1], [1, 2], [1, 2, 3], [4]]
Afegir elements a una llista interna
Si l’objectiu és afegir elements a una de les llistes internes, haureu d’utilitzar el mètode append() engegat per la llista interna concreta i no per la llista principal.
Afegir elements a una llista interna
En aquest exemple s’ha afegit el nombre 4 a l’últim element de la llista principal.
>>> llista = [[1], [1, 2], [1, 2, 3]] >>> llista[2].append(4) >>> llista [[1], [1, 2], [1, 2, 3, 4]]
Ordenar llistes de llistes
En l’analítica de dades moltes vegades es treballa amb conjunts de dades que tenen una estructura tabular, simular a un full de càlcul. Les llistes de llistes permeten representar aquesta estructura de manera natural, on cada filera de la taula és tractada com una subllista i cada columna es representa amb els elements que ocupen la mateixa posició de cadascuna d’aquestes subllistes. Es pot decidir ordenar la llista principal segons diferents criteris, regits pel significat de cada columna de dades.
Al llarg d’aquest apartat aprendreu a configurar una mateixa llista de llistes segons diversos criteris determinats per cada columna d’elements. Per ordenar llistes de llistes segons diferents camps, només cal definir funcions key que retornin el camp necessari abans d’efectuar la comparació en l’algorisme d’ordenació. D’aquesta manera, s’aconseguirà tenir la mateixa llista ordenada de diferents maneres per poder efectuar els llistats en l’ordre requerit.
Al llarg d’aquest apartat es treballarà amb una llista d’alumnes. Per cada alumne es manté una llista amb la següent informació: nom de l’alumne, nota mitjana i aula en la qual estudia.
llista = [['Pere', 7.2, 'Aula 1'], ['Maria', 9.3, 'Aula 2'], ['Mireia', 3.9, 'Aula 1'], ['Paula', 5.7, 'Aula 2']]
Ordenar la llista segons l'ordre natural
Es començarà ordenant la llista segons l’ordre natural. L’ordre natural és el que ve definit amb els operadors de comparació. S’està parlant d’una llista, de manera que l’ordre natural compara el primer element (segons l’ordre natural definit pel tipus de dades que ocupen la primera posició de les subllistes o llistes internes), i només en el cas en què siguin iguals es procedirà a comparar el següent.
En aquest exemple, l’ordre natural de les llistes implicarà l’ordre segons els noms dels alumnes: com que les dades que ocupen la primera posició de les subllistes són str, l’ordre de les llistes resultants serà el determinat per l’ordre de les cadenes de la primera posició.
>>> llista = [['Pere', 7.2, 'Aula 1'], ['Maria', 9.3, 'Aula 2'], ['Mireia', 3.9, 'Aula 1'], ['Paula', 5.7, 'Aula 2']] >>> llista.sort() >>> llista [['Maria', 9.3, 'Aula 2'], ['Mireia', 3.9, 'Aula 1'], ['Paula', 5.7, 'Aula 2'], ['Pere', 7.2, 'Aula 1']]
Ordenar la llista segons la nota
Si es vol ordenar segons la qualificació, caldrà definir una funció key que modifiqui l’ordre natural de les llistes. En aquest cas, la transformació que es vol fer abans d’efectuar la comparació en l’algorisme d’ordenació, serà que cada alumne estigui representat només per la nota que ha tret, és a dir, el valor de la posició 1 de la llista de cada alumne.
Això vol dir que en comptes de comparar la llista del Pere llista[0], amb la llista de la Maria llista[1], s’ha de fer una transformació per aconseguir que el Pere estigui representat pel nombre 7.2, i la Maria pel 9.3.
La key function l’únic que haurà de fer és retornar el segon camp de la llista de l’alumne. Fixeu-vos en el codi següent:
def key_segon_camp(llista): ''' key function que serveix per ordenar pel segon camp d'una llista de llistes Entrada: llista d'elements Sortida: element del tipus del camp escollit ''' return llista[1] alumnes = [['Pere', 7.2, 'Aula 1'], ['Maria', 9.3, 'Aula 2'], ['Mireia', 3.9, 'Aula 1'], ['Paula', 5.7, 'Aula 2']] alumnes.sort(key = key_segon_camp, reverse = True) print(alumnes)
Separar els alumnes per aules i ordenar per notes cadascuna d'elles
Per últim, se separaran els alumnes segons l’aula a la qual assisteixen i s’ordenaran els alumnes de cada aula segons la seva nota.
En aquest exemple suposarem que sabeu que hi ha 2 alumnes per aula. Els passos a seguir seran els següents:
- Ordenar segons el tercer camp.
- Crear dues subllistes, una per cada aula.
- Ordenar cadascuna de les llistes segons el segon camp.
El codi amb el programa serà:
def key_segon_camp(llista): ''' key function que serveix per ordenar pel segon camp d'una llista de llistes Entrada: llista d'elements Sortida: element del tipus del camp escollit ''' return llista[1] def key_tercer_camp(llista): ''' key function que serveix per ordenar pel segon camp d'una llista de llistes Entrada: llista d'elements Sortida: element del tipus del camp escollit ''' return llista[2] alumnes = [['Pere', 7.2, 'Aula 1'], ['Maria', 9.3, 'Aula 2'], ['Mireia', 3.9, 'Aula 1'], ['Paula', 5.7, 'Aula 2']] print('alumnes ordenats per aula:') alumnes.sort(key = key_tercer_camp) print(alumnes) print() print('alumnes Aula 1 ordenats per nota:') alumnes_aula1 = alumnes[:2] alumnes_aula1.sort(key = key_segon_camp, reverse = True) print(alumnes_aula1) print() print('alumnes Aula 2 ordenats per nota:') alumnes_aula2 = alumnes[2:] alumnes_aula2.sort(key = key_segon_camp, reverse = True) print(alumnes_aula2)
I el resultat de l’execució:
$ python3 ordenar.py alumnes ordenats per aula: [['Pere', 7.2, 'Aula 1'], ['Mireia', 3.9, 'Aula 1'], ['Maria', 9.3, 'Aula 2'], ['Paula', 5.7, 'Aula 2']] alumnes Aula 1 ordenats per nota: [['Pere', 7.2, 'Aula 1'], ['Mireia', 3.9, 'Aula 1']] alumnes Aula 2 ordenats per nota: [['Maria', 9.3, 'Aula 2'], ['Paula', 5.7, 'Aula 2']]
Les funcions key no poden tenir més d'un argument
Les dues funcions key definides en l’últim exemple (key_segon_camp() i key_tercer_camp()) sembla que són massa semblants per no intentar fer-ne una generalització. Es podria pensar a passar un argument que indiqués el nombre del camp pel qual es vol ordenar, generalitzar i no definir tantes funcions per als diferents camps.
Però això no és possible perquè la signatura de les funcions key obliga que l’únic paràmetre permès sigui un element del mateix tipus que es vol comparar.
Fixeu-vos en aquest programa, en el que es defineix una presumpta funció key generalitzada que retorna l’element de la posició de la llista que indica l’argument.
def key_per_camp(llista, posicio): ''' Funció que retorna l'element que ocupa la posició demanada Entrada: llista d'elements, int (>= 0) Sortida: element del tipus del camp escollit ''' return llista[posicio] alumnes = [['Pere', 7.2, 'Aula 1'], ['Maria', 9.3, 'Aula 2'], ['Mireia', 3.9, 'Aula 1'], ['Paula', 5.7, 'Aula 2']] print('alumnes ordenats per aula:') alumnes.sort(key = key_per_camp) print(alumnes) print()
I en el resultat de l’execució:
$ python3 key_dos_parametres.py alumnes ordenats per aula: Traceback (most recent call last): File "treball/key_dos_parametres.py", line 14, in <module> alumnes.sort(key = key_per_camp, reverse = True) TypeError: key_per_camp() missing 1 required positional argument: 'posicio'
Recordeu que la signatura de la funció mostra com està definida i quins paràmetres espera rebre.
Funcions lambda
Les funcions lambda permeten evitar la definició de funcions que són tan senzilles que es podria dir que comporta més feina definir-les que utilitzar-les.
Una funció lambda en Python és una manera concisa de definir funcions anònimes, és a dir, funcions sense nom que s’utilitzen per a operacions simples i de curt termini. Es defineixen utilitzant la paraula clau lambda, seguida dels arguments, dos punts (:), i l’expressió que retorna el resultat de la funció.
Com definir les funcions lambda
Fixeu-vos en la definició de les funcions suma i quadrat.
>>> suma = lambda x, y: x + y >>> suma(2, 3) 5 >>> quadrat = lambda x: x ** 2 >>> quadrat(4) 16
En aquesta ocasió se’ls ha assignat un nom per utilitzar-les, però no sempre és així.
Funcions lambda per ordenar llistes de llistes
En comptes de definir una key function tan senzilla que tan sols retorni una posició concreta d’una llista, sol ser més comú utilitzar funcions lambda per indicar en cada ocasió el camp pel qual es vol aplicar el criteri d’ordenació. El següent programa separa ordena els alumnes per aules, els separa i ordena per nota, de manera descendent, als alumnes de cadascuna de les aules.
alumnes = [['Pere', 7.2, 'Aula 1'], ['Maria', 9.3, 'Aula 2'], ['Mireia', 3.9, 'Aula 1'], ['Paula', 5.7, 'Aula 2']] print('alumnes ordenats per aula:') alumnes.sort(key = lambda x: x[2]) print(alumnes) print() print('alumnes Aula 1 ordenats per nota:') alumnes_aula1 = alumnes[:2] alumnes_aula1.sort(key = lambda x: x[1], reverse = True) print(alumnes_aula1) print() print('alumnes Aula 2 ordenats per nota:') alumnes_aula2 = alumnes[2:] alumnes_aula2.sort(key = lambda x: x[1], reverse = True) print(alumnes_aula2)
I podeu comprovar que el resultat és l’esperat:
$ python3 ordenar.py alumnes ordenats per aula: [['Pere', 7.2, 'Aula 1'], ['Mireia', 3.9, 'Aula 1'], ['Maria', 9.3, 'Aula 2'], ['Paula', 5.7, 'Aula 2']] alumnes Aula 1 ordenats per nota: [['Pere', 7.2, 'Aula 1'], ['Mireia', 3.9, 'Aula 1']] alumnes Aula 2 ordenats per nota: [['Maria', 9.3, 'Aula 2'], ['Paula', 5.7, 'Aula 2']]
Relacions que no són d'ordre estricte
Quan s’utilitza una funció lambda per ordenar una llista de llistes en Python segons un camp específic (per exemple, el tercer camp), s’està definint una manera de comparar els elements de la llista per aquest camp.
Aquesta comparació no és exactament una relació d’ordre en el sentit formal perquè poden haver-hi elements diferents que tinguin el mateix valor per aquest camp i això pot trencar algunes propietats d’una relació d’ordre estricta.
En l’últim exemple hi ha elements diferents que ocuparien la mateixa posició dins de la relació. La relació no està ben definida perquè no determina quin element va abans que l’altre. Per exemple, vegem el fragment de codi següent:
alumnes = [['Pere', 7.2, 'Aula 1'], ['Maria', 9.3, 'Aula 2'], ['Mireia', 3.9, 'Aula 1'], ['Paula', 5.7, 'Aula 2']] print('alumnes ordenats per aula:') alumnes.sort(key = lambda x: x[2])
El Pere i la Mireia estan empatats segons la relació d’ordre que s’ha definit; tots dos retornen en valor Aula 1 en la transformació de la funció lambda que s’ha definit com a key. L’algorisme d’ordenació posarà indistintament al Pere davant de la Maria o al revés. És per això que el que s’ha definit no és una relació d’ordre completa.
En moltes ocasions no necessitareu definir relacions d’ordre estrictes. En els casos en què sigui necessari, sempre podeu afinar la relació definint un criteri de desempat que asseguri que donats dos elements diferents, un sempre estarà per davant de l’altre.
En aquest exemple es podria definir una relació d’ordre (estrictament parlant) afegint que, en el cas que l’aula sigui igual, el criteri de desempat és el nom de l’alumne, sempre que no pugui passar que existeixin dos alumnes amb el mateix nom.
Funcions com a paràmetres de funcions
En Python, les funcions són ciutadanes de primera classe, el que significa que es poden passar com arguments a altres funcions, retornar-les com a resultat i assignar-les a variables. Aquesta característica és molt poderosa i permet un alt nivell d’abstracció i flexibilitat en el codi.
les funcions que passen com a paràmetre d'una funció
Una funció com a paràmetre d’una altra funció és una funció que és entra com a argument dins d’una altra funció, i se’n pot fer ús de la mateixa manera que es fa amb els diversos tipus de dades. En Python, les funcions es poden utilitzar igual que qualsevol altra dada.
Són funcions qualssevol, amb l’única característica que entren en una altra funció com a argument. Qualsevol funció, amb tota mena de paràmetres i de tipus de retorn és susceptible d’entrar en una altra funció mitjançant un paràmetre d’aquesta mateixa.
Aquest mecanisme permet que una funció pugui aplicar diferents comportaments o operacions sobre dades segons la funció passada com a argument.
Key function com a paràmetre de la funció sorted()
Ja us heu trobat amb exemples de funcions que entren com a paràmetre d’una altra funció i en modifiquen el comportament. Les key functions són un dels arguments (optatiu) de les eines d’ordenar del Python.
La funció que entra com a argument és l’encarregada de definir el criteri d’ordenació.
En aquest exemple, la funció sort() sense paràmetres ordena la llista segons l’ordre natural.
>>> llista = [['Maria', 9], ['Pere', 9.2], ['Paula', 8.5]] >>> llista.sort() >>> llista [['Maria', 9], ['Paula', 8.5], ['Pere', 9.2]] >>> llista.sort(key = lambda x: x[1]) >>> llista [['Paula', 8.5], ['Maria', 9], ['Pere', 9.2]]
Si a la funció sort() se li passa com a paràmetre la funció lambda definida en la mateixa crida, es modifica el seu comportament i ordena segons la funció rebuda. En aquest cas, utilitza el criteri del segon camp.
Per argument es pot passar qualsevol tipus de funció, no només funcions lambda.
Aplicar funcions diferents a una llista
En aquest exemple s’operarà sobre les dades d’una llista de manera diferent segons la funció rebuda com a paràmetre.
I el resultat de l’execució:
def doble(x): return x * 2 def triple(x): return x * 3 def quadrat(x): return x * x def aplica_a_llista(func, llista): ''' Funció que aplica la funció rebuda per argument a una llista. Entrada: funció, llista d'enters Sortida: Nova llista d'enters ''' resultat = [] for element in llista: resultat.append(func(element)) return resultat # Exemples d'ús numeros = [1, 2, 3, 4, 5] print(aplica_a_llista(doble, numeros)) print(aplica_a_llista(triple, numeros)) print(aplica_a_llista(quadrat, numeros))
$ python3 f_param.py [2, 4, 6, 8, 10] [3, 6, 9, 12, 15] [1, 4, 9, 16, 25]
Exemple d'ús de funcions com a paràmetre de funcions
Per acabar, es mostrarà un exemple utilitzant un mòdul de funcions propi. Es treballarà sobre l’última versió del mòdul de funcions de nombres_racionals.
Es definirà una funció calcular() que rebrà com a paràmetre el nom de la funció que es vol utilitzar en cada cas (suma, resta, multiplicació o divisió), a part dels dos operands. L’únic que cal que faci la nova funció és cridar a l’operació corresponent i retornar el resultat:
def calcular(rac1, operacio, rac2): ''' Funció que efectua l'operació demanada entre els dos racionals. ATENCIÓ: En el cas de la funció divisió, cal comprovar que el segon operand no val zero. Entrada: str (nombre racional), funció (suma, resta, multiplicació o divisió), str (nombre racional) Sortida: str (nombre racional) ''' return operacio(rac1, rac2)
Aquí teniu la nova versió del mòdul nombres_racionals, amb la funció calcular() i el seu test-driver() inclosos:
''' Mòdul de funcions per a operar amb nombres racionals. Una dada de tipus racional és un ''str'' amb el format "num/den" que acompleix les següents condicions: - "num" és un nombre enter - "den" és un nombre enter diferent de zero. - si "den" val 1, el ''str'' es pot escriure com "num" ''' # Funcions incorporades en aquest mòdul per comoditat. Haurien d'estar # en un mòdul matematiques. Per això es declaren com a privades. def _es_enter(s): ''' Funció que ens diu si una cadena és o no un enter Entrada: str Sortida: bool ''' if len(s) == 0: return False #traiem el signe, si cal if s[0] in '+-': s = s[1:] return s.isdigit() def _maxim_comu_divisor(a, b): '''Funció que calcula el màxim comú divisor de dos nombres Entrada: n, m enters positius Sortida: int''' # algorisme per a calcular el màxim comú divisor t = 0 while b != 0: t = b b = a % b a = t return a # Funcions bàsiques def es_racional_valid(s): ''' Funció per validar si una cadena és un racional vàlid. Accepta com a racionals 3/1 i també 3 Entrada: str Sortida: bool ''' #comprovem si es tracta d'un enter if _es_enter(s): return True #separem la cadena per / l = s.split('/') #si no té 2 camps, malament if len(l) != 2: return False #treiem el signe si hi és #si cada camp no és enter, malament if not (_es_enter(l[0]) and _es_enter(l[1])): return False if int(l[1]) == 0: return False return True def crea_racional(num, den): ''' Funció que construeix una dada de tipus nombre racional Atenció, s'ha de comprovar que el resultat sigui un racional vàlid Entrada: dos enters. El segon diferent de zero. Sortida: String en format de racional (n/d o n) ''' if den == 1: return str(num) #ATENCIÓ, ha de ser str!!! return '%d/%d' % (num, den) def get_numerador(racional): ''' Funció que retorna el numerador d'una cadena de tipus nombre racional. Entrada: str tipus racional Sortida: int ''' llista_r = racional.split('/') return int(llista_r[0]) def get_denominador(racional): ''' Funció que retorna el denominador d'una cadena de tipus nombre racional. Entrada: str tipus racional Sortida: Int (serà 1 en el cas que el racional sigui un enter ''' llista_r = racional.split('/') denominador = 1 #si es tracta d'un enter, no pot petar if len(llista_r) == 2: denominador = int(llista_r[1]) return denominador # Operacions amb racionals def suma(rac1, rac2): '''Funció que suma dos racionals. Atenció, també pot acceptar 'num' com a racional Entrada: 2 str en format racional Sortida: str en format racional correcte ''' #extraiem numeradors i denominadors num1 = get_numerador(rac1) num2 = get_numerador(rac2) den1 = get_denominador(rac1) den2 = get_denominador(rac2) #busquem el denominador final (la multipl dels dos denominadors) den = den1 * den2 num = num1 * den2 + num2 * den1 return fraccio_canonica(crea_racional(num, den)) def multiplicacio(rac1, rac2): ''' Funció que multiplica dos racionals. Atenció, també pot acceptar 'num' com a racional Entrada: 2 str en format racional Sortida: str en format racional ''' #extraiem numeradors i denominadors num1 = get_numerador(rac1) num2 = get_numerador(rac2) den1 = get_denominador(rac1) den2 = get_denominador(rac2) #busquem el denominador final (la multipl dels dos denominadors) num = num1 * num2 den = den1 * den2 return fraccio_canonica(crea_racional(num, den)) def resta(rac1, rac2): ''' Funció que resta el segon racional del primer. Entrada: str (nombre racional) sortida: str (nombre racional) ''' return suma(rac1, oposat(rac2)) def divisio(rac1, rac2): ''' Funció que resta el segon racional del primer. ATENCIÓ: Utilitza la funció invers, de manera que cal comprovar que el resultat sigui un racional vàlid. Entrada: str (nombre racional) sortida: str (nombre racional) ''' return multiplicacio(rac1, invers(rac2)) def oposat(racional): ''' Funció que retorna l'oposat de un racional. l'oposat de a/b és -a/b. Entrada: str (nombre racional) Sortida:str (nombre racional) ''' a = get_numerador(racional) b = get_denominador(racional) return crea_racional(-a, b) def invers(racional): ''' Funció que retorna l'invers de un racional. l'invers de a/b és b/a. ATENCIÓ: s'ha de comprovar que el numerador resultat sigui un racional vàlid Entrada: str (nombre racional) Sortida:str (nombre racional) ''' a = get_numerador(racional) b = get_denominador(racional) return crea_racional(b, a) def fraccio_canonica(racional): ''' Funció que simplifica, i retorna la fracció canònica d'un racional Entrada: str (nombre racional) Sortida: str (nombre racional) ''' a = get_numerador(racional) b = get_denominador(racional) # guardar el signe positiu = True if a * b < 0: positiu = False # càlcul del mcm a = abs(a) b = abs(b) mcd = _maxim_comu_divisor(a, b) # valors del numerador i divisor (han de ser enters) a = a // mcd b = b // mcd #correcció del signe if not positiu: a = -a return crea_racional(a, b) def representacio_decimal(racional): ''' Funció que proporciona la representació decimal d'un nombre racional. Entrada: str (nombre racional) Sortida: float ''' a = get_numerador(racional) b = get_denominador(racional) return a / b # Mètodes semblants als dunders def son_equivalents(rac1, rac2): ''' Funció que implementa l'equivalència entre dos nombres racionals Entrada: str (nombre racional) Sortida: bool ''' return (get_numerador(rac1) * get_denominador(rac2) == get_numerador(rac2) * get_denominador(rac1)) def es_major(rac1, rac2): ''' Funció que diu si el primer nombre és major que el segon Entrada: str (nombre racional) Sortida: bool ''' return representacio_decimal(rac1) > representacio_decimal(rac2) def compara_racionals(rac1, rac2): ''' Funció comparator de nombres racionals Entrada: 2 str (nombres racionals) Sortida: int 1 si rac1 > rac2 0 si rac1 == rac2 -1 si rac1 < rac2 ''' resultat = -1 if es_major(rac1, rac2): resultat = 1 elif son_equivalents(rac1, rac2): resultat = 0 return resultat def calcular(rac1, operacio, rac2): ''' Funció que efectua l'operació demanada entre els dos racionals. ATENCIÓ: En el cas de la funció divisió, cal comprovar que el segon operand no val zero. Entrada: str (nombre racional), funció (suma, resta, multiplicació o divisió), str (nombre racional) Sortida: str (nombre racional) ''' return operacio(rac1, rac2) #Test driver if __name__ == '__main__': if False: print(_es_enter('-22')) print(_es_enter('-22.2')) print(_es_enter('+2')) print(_es_enter('2')) print(_es_enter('')) print() if False: print(('es un racional vàlid?')) print('45/4', es_racional_valid('45/4')) print('2.3/7', es_racional_valid('2.3/7')) print('-45/9', es_racional_valid('-45/9')) print('234/0', es_racional_valid('234/0')) print('23', es_racional_valid('23')) print('3/-33', es_racional_valid('3/-33')) print('2/1/-2', es_racional_valid('2/1/-2')) print('34m/23', es_racional_valid('34/23m')) print('34m/23.', es_racional_valid('34m/23.')) print('/', es_racional_valid('/')) print('6/', es_racional_valid('6/')) print('0/2', es_racional_valid('0/2')) print('3/1', es_racional_valid('3/1')) print() if False: print('constructor de racionals') print(crea_racional(-4, -234)) print(crea_racional(-2, 3)) print(crea_racional(-23, 1)) print(crea_racional(23, -1)) print() if False: print('getters') print('23/-1') print('numerador: ', get_numerador('23/-1')) print('denominador: ', get_denominador('23/-1')) print('23') print('numerador: ', get_numerador('23')) print('denominador: ', get_denominador('23')) print() if False: print('suma') print('1/2 + 1/2 =', suma('1/2', '1/2')) print('2/4 + 1/6 =', suma('2/4', '1/6')) print('1/2 + 1/-2 =', suma('1/2', '1/-2')) print('3 + 5/2 = ', suma('3', '5/2')) print('3 + 5/1 = ', suma('3', '5/1')) print('3 + 5 = ', suma('3', '5')) print() if False: print('multiplicació') print('1/2 * 1/2 =', multiplicacio('1/2', '1/2')) print('-6/5 * 5/-2 =', multiplicacio('-6/5', '5/-2')) print('-6/-1 * 1/-6 =', multiplicacio('-6/-1', '1/-6')) print() if False: print('màxim comú divisor') print(_maxim_comu_divisor(4, 10)) print(_maxim_comu_divisor(7, 60)) print(_maxim_comu_divisor(7, 7)) print() if False: print('la fracció canònica') print('2/4',fraccio_canonica('2/4')) print('14/24', fraccio_canonica('14/24')) print('1/4', fraccio_canonica('1/4')) print('-30/-10', fraccio_canonica('-30/-10')) print('6/-6', fraccio_canonica('6/-6')) print() if False: print('representacio_decimal') print('1/2', representacio_decimal('1/2')) print('4/2', representacio_decimal('4/2')) print('-6/-3', representacio_decimal('-6/-3')) print() if False: print(f'oposat i invers') print(f"1/2 oposat: {oposat('1/2')} invers: {invers('1/2')}") print(f"0/2 oposat: {oposat('0/2')} invers: {invers('0/2')}") print('ATENCIÓ: després de invers cal utilitzar es_racional_valid') print(f"4/-10 oposat: {oposat('4/-10 ')} invers: {invers('4/-10 ')}") print() if False: print(f'resta i divisió') print(f"1/2, 2/3 resta: {resta('1/2', '2/3')} divisió: {divisio('1/2', '2/3')}") print(f"0/2 , -2/-3 resta: {resta('0/2', '-2/-3')} divisio: {divisio('0/2', '-2/-3')}") print(f"-2/-3, 0 resta: {resta('-2/-3', '0')} divisio: {divisio('-2/-3', '0')}") print('ATENCIÓ: després de invers cal utilitzar es_racional_valid') print() if False: print('equivalència') print(f"2 i 4/2, {son_equivalents('2', '4/2')}") print(f"4/2 i 6/3, {son_equivalents('4/2', '6/3')}") print(f"2 i 6/3, {son_equivalents('2', '6/3')}") print(f"1/1000 i 1/1001, {son_equivalents('1/1000', '1/1001')}") print() if False: print("Comparació amb l'operador > ") print(f"1 > 1/2 {'1' > '1/2'}") print(f"3/5 > 4/5 {'3/5' > '4/5'}") print('es_major()') print(f"1 > 1/2 {es_major('1', '1/2')}") print(f"3/5 > 4/5 {es_major('3/5', '4/5')}") print() if False: print('compara_racionals') print(f"1 i 1/2 {compara_racionals('1', '1/2')}") print(f"3/5 i 4/5 {compara_racionals('3/5', '4/5')}") print(f"1/2 i 1/2 {compara_racionals('1/2', '1/2')}") if True: print(calcular('1/2', suma, '1/3')) print(calcular('1/2', resta, '1/3')) print(calcular('1/2', multiplicacio, '1/3')) print(calcular('1/2', divisio, '1/3'))
El resultat de l’execució com a fitxer principal, amb el test-driver de la funció calcular(), és el següent:
$ python3 nombres_racionals_new.py 5/6 1/6 1/6 3/2



