El Blog de Trespams

Blog personal sobre tecnologia, gestió de projectes i coses que se me passen pel cap

Ordre a la llista!

Una de les accions més repetitives que sovint feim quan passam cap a la capa de presentació és la d'ordenar els elements que volem que es presentin. Potser algú dirà que els elements ja poden venir ordenats de la consulta a la base de dades, però què passa si en lloc d'atacar a una base de dades directament obtenim el que s'ha de mostrar d'un servei web? o ja hem fet un tractament de les dades i ara les volem ordenar per un altra camp. Poder fer ordenacions de manera senzilla i ràpida ens soluciona molts maldecaps.

Anem a veure com Python ens permet fer ordenacions de llistes de pràcticament qualsevol cosa. Python fa servir el mètode sort per a ordenar una llista. L'exemple més senzill seria l'ordenació d'una llista d'enters

$ llista = [2,4,5,6,7,6,7]
$ llista.sort()
$ llista [2, 4, 5, 6, 6, 7, 7]

Sí, és tan senzill com pareix, i fins i tot podem fer

$ llista.reverse() 
$ llista [7, 7, 6, 6, 5, 4, 2]

Però clar, una llista a Python pot contenir qualsevol cosa, no sols sencers, anem a veure què passa si passam una llista de parells (tuples)

$ llista = [(1,5),(1,2),(2,2),(3,5),(4,8),(1,9)] 
$ llista.sort()
$ llista [(1, 2), (1, 5), (1, 9), (2, 2), (3, 5), (4, 8)]

Suposem però que jo el que vull és que s'ordeni pel segon element de la tupla, aquí si un prové d'altres llenguatges de programació ja se pot esperar tenir que escriure un bon munt de codi, però no en Python, l'ordenació es fa per una clau i podem definir quina és aquesta clau passant-li al mètode sort una funció construida de manera que prengui un sol element i ens retorni la clau a comparar.

Per fer el que volem farem servir el mòdul operator, i dins aquest la funció itemgetter, aquesta funció ens retorna una altra funció que aplicada damunt una llista o tupla ens donarà l'element especificat que haguem definit, així per exemple

$ segon=itemgetter(1) 
$ segon(llista) (2, 2)
$ llista [(1, 2), (2, 2), (1, 5), (3, 5), (4, 8), (1, 9)]
$ segon(llista) (2, 2)
$ segon((1,5)) 5

Farem servir aquesta funció per a obtenir la clau per la qual volem ordenar la nostra llista, així

$ llista.sort(key=segon) 
$ llista [(1, 2), (2, 2), (1, 5), (3, 5), (4, 8), (1, 9)]

Pensem en les implicacions que té això quan volem omplir un select d'html, encara que la llista ens hagi arribada ordenada per codi, podem fàcilment canviar l'ordenació al texte sols amb aquesta instrucció. Un altra paràmetre que ens serà de molta utilitat a l'hora de fer ordenacions és el cmp, aquest ens permet passar una funció que donats dos arguments haurà de retornar un nombre positiu per indicar que el primer és major que el segon, zero per indicar que els elements són igual o negatiu per indicar que el segon és major que el primer.

Per exemple, suposem que el que volem fer és ordenar la nostra llista segons el que sumen els seus components.

$ t=llista[:] 
#Farem primer una còpia de la llista original
$ def ordena(x,y):
p1 = x[0]+x[1] p2 = y[0]+y[1] return p1-p2
$ t.sort(cmp=ordena)
$ t [(1, 2), (2, 2), (1, 5), (3, 5), (1, 9), (4, 8)] $ llista [(1, 2), (2, 2), (1, 5), (3, 5), (4, 8), (1, 9)]

O també poden fer un codi més florit i escriure

$ t = llista[:]
$ t [(1, 2), (2, 2), (1, 5), (3, 5), (4, 8), (1, 9)]
$ t.sort(lambda x,y: x[0]+x[1]-y[0]-y[1]) $ t [(1, 2), (2, 2), (1, 5), (3, 5), (1, 9), (4, 8)]

Si algú ha tingut la paciència d'arribar fins aquí, un momentet, que ara ve el més interessant. Què passa quan en lloc de llistes de nombres tenim llistes d'objectes? Doncs res, podem fer servir el paràmetre cmp o el paràmetre key segons ens vagi millor per fer l'ordenació. Anem a veure tres maneres de fer el mateix. Primer definirem la nostra llista d'objectes

class Persona: 
def __init__(self, nom, edat):
self.nom = nom
self.edat = edat
def __cmp__(self, altri):
return cmp(self.edat,altri.edat)

Aquí el que he defint és una funció cmp dins la classe, que és la que faríem servir per defecte a l'hora d'ordenar una llista d'objectes d'aquest tipus, així:

$ agenda = [Persona('Benjamí', 31), Persona('Pau', 22), Persona('Juan',34), Persona('Ricardo',38), Persona('Guillem',28), Persona('Bernat',58)]
$ agenda.sort()
$ for amic in agenda:
$ print "%25s \t %i" % (amic.nom, amic.edat)
Pau 22 Guillem 28 Benjamí 31 Juan 34 Ricardo 38 Bernat 58

Ara suposem però que volem ordenar la llista per nom. Una opció seria refer el mètode cmp, però tenim altres opcions. La primera és encriure una nova funció de comparació:

$ def compara_nom(amic1, amic2): 
$ return cmp(amic1.nom,amic2.nom)
$ agenda.sort(compara_nom)
$ for amic in agenda:
$ print "%25s \t %i" % (amic.nom, amic.edat)
Benjamí 31 Bernat 58 Guillem 28 Juan 34 Pau 22 Ricardo 38

O bé, si ens agrada més l'opció lambda

$ agenda.sort(lambda amic1, amic2: cmp(amic1.nom,amic2.nom))

Però encara tenim una altra maner, deixant que Python faci la feina per nosaltres, hem d'indicar la clau d'ordenació i fer que les eines de comparació del llenguatge facin la seva via. El problema però està en com dir-li quina clau fer servir, això s'aconsegueix amb attrgetter de la llibreria operator.

$ agenda.sort(key=attrgetter('nom')) 
$ for amic in agenda: $ print "%25s \t %i" % (amic.nom, amic.edat)

Fixau-vos el senzill que seria poder fer una ordenació per qualsevol camp de la classe.

Referències:

blog comments powered by Disqus