Col·leccions i iteradors
Introducció
- Una col·lecció és un conjunt d'objectes que es poden recórrer. No especifiquem la manera en com està organitzada l'estructura de dades (pot ser una llista, vector, hash table, arbre, etc). És, per tant, una generalització del concepte d'estructura de dades.
- Un iterador defineix una interfície amb uns mètodes que permeten accedir als elements d'una col·lecció. Els típics mètodes son:
- Primer()
- Ultim()
- Seguent()
- Anterior()
- ...
Alguns enllaços de referència:
- Wikipedia "Iterator"
- Iteradors a python.org
- Com crear una classe iterable
- Itertools de Python: a fons amb els iteradors (no ho cobrirem en aquest curs).
- Yield expressions: per un ús més avançat (no el cobrirem ara) permet crear iteradors d'objectes més còmodament.
En el cas de Python per tal de crear una classe iterable cal implementar els mètodes:
- __iter__() : retorna un element iterable
- next() : l'element iterable implementa aquest mètode per passar al següent element.
Per exemple, una llista implementa iteradors. Si examinem els mètodes implementats a la llista:
>>> dir(list) ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Iteradors implícits
Qualsevol bucle tipus "foreach" (p.ex. en PHP) utilitza el patró iterador. En Python ho solem fer al recórrer llistes:
for element in llista:
print element
"element" cada cop serà un objecte de la llista i en cada iteració avançarem fins el següent element. Encara que no ens adonem, estem utilitzant un objecte iterador de la llista que cada volta avança cap a next().
Iteradors explícits
Una altra manera de recórrer els elements és amb un iterador explícit. Ho podem veure en el següent exemple:
>>> l = [3, 6, 3, 8, 3, 321] >>> it = l.__iter__() >>> it <listiterator object at 0x8bbb88c> >>> it.next() 3 >>> it.next() 6 >>> it.next() 3 >>> it.next() 8 >>> it.next() 3 >>> it.next() 321 >>> it.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
I si ho volem utilitzar en un while:
l = [3, 6, 3, 8, 3, 321]
it = l.__iter__()
elem = it.next()
while elem:
print elem
elem = it.next()
A l'executar-ho obtindrem:
3 6 3 8 3 321 Traceback (most recent call last): File "<stdin>", line 3, in <module> StopIteration
Veiem que al final del bucle tenim un problema ja que el darrer element provoca un error (no té cap next()!). Per solventar-ho podem utilitzar un try-catch de l'excepció:
l = [3, 6, 3, 8, 3, 321]
it = l.__iter__()
try:
elem = it.next()
while elem:
print elem,
elem = it.next()
except StopIteration:
print "final"
Amb aquest codi, no ens donarà l'error, sinó: (oju, també hem afegit una "," després del print perquè no faci salt de línia):
3 6 3 8 3 321 final