5. Structuri de date
********************

Capitolul de față se apleacă mai în detaliu asupra unor chestiuni cu
care v-ați întâlnit deja, iar acestora le adaugă, firește, și lucruri
noi.


5.1. Mai multe despre liste
===========================

Tipul de date listă dispune de mai multe metode. Iată toate aceste
metode ale obiectelor listă:

list.append(x)

   Adaugă un element la finalul listei. La fel ca "a[len(a):] = [x]".

list.extend(iterable)

   Extinde lista prin adăugarea tuturor elementelor unui (obiect)
   iterabil. La fel ca "a[len(a):] = iterabil".

list.insert(i, x)

   Inserează un element la poziția dată. Primul argument este indicele
   elementului înaintea căruia se va face inserția, de aceea
   "a.insert(0, x)" va realiza o inserție la începutul listei în timp
   ce "a.insert(len(a), x)" este echivalentă cu "a.append(x)".

list.remove(x)

   Elimină primul element din listă a cărui valoare este egală cu *x*.
   Va ridica o excepție "ValueError" dacă nu există niciun astfel de
   element.

list.pop([i])

   Elimină din listă elementul de la poziția dată și îl returnează.
   Dacă nu se specifică indicele elementului, atunci "a.pop()" va
   elimina și va returna ultimul element al listei. Ridică o excepție
   "IndexError" atunci când fie lista este goală fie când indicele se
   găsește în afara plajei de valori a indicilor elementelor listei.

list.clear()

   Elimină toate elementele din listă. La fel ca "del a[:]".

list.index(x[, start[, end]])

   Întoarce indicele, numerotat de la zero, al primului element al
   listei a cărui valoare este egală cu *x*. Ridică o excepție
   "ValueError" dacă nu există niciun asemenea element.

   Argumentele opționale *start* și *end* sunt interpretate la fel ca
   în cazul unei tranșări și sunt folosite la limitarea căutărilor
   într-o anumită sub-secvență a listei. Indicele returnat se
   calculează relativ la începutul întregii secvențe și nu începând de
   la argumentul *start*.

list.count(x)

   Returnează numărul de apariții ale lui *x* în listă.

list.sort(*, key=None, reverse=False)

   Sortează elementele listei păstrându-le în aceeași listă
   (argumentele pot fi folosite la construcția unei sortări
   personalizate, vezi "sorted()" pentru o justificare a lor).

list.reverse()

   Scrie în ordine inversă elementele listei păstrându-le în aceeași
   listă.

list.copy()

   Întoarce o copie superficială a listei. La fel ca "a[:]".

Un exemplu care întrebuințează majoritatea metodelor listei:

   >>> fructe = ['portocală', 'măr', 'pară', 'banană', 'kiwi', 'măr', 'banană']
   >>> fructe.count('măr')
   2
   >>> fructe.count('mandarină')
   0
   >>> fructe.index('banană')
   3
   >>> fructe.index('banană', 4)  # Găsește prima banană situată la dreapta poziției 4
   6
   >>> fructe.reverse()
   >>> fructe
   ['banană', 'măr', 'kiwi', 'banană', 'pară', 'măr', 'portocală']
   >>> fructe.append('struguri')
   >>> fructe
   ['banană', 'măr', 'kiwi', 'banană', 'pară', 'măr', 'portocală', 'struguri']
   >>> fructe.sort()
   >>> fructe
   ['banană', 'banană', 'kiwi', 'măr', 'măr', 'pară', 'portocală', 'struguri']
   >>> fructe.pop()
   'struguri'

Poate că ați remarcat deja că metode precum "insert", "remove" sau
"sort", adică metode care fac modificări în listă păstrând lista, nu
afișează nicio valoare returnată -- în fapt, ele returnează valoarea
prestabilită "None". [1]  Acesta este un principiu de design valabil
pentru toate structurile de date mutabile din Python.

Altă chestiune de remarcat este aceea că nu toate datele pot fi
sortate și nici comparate. De exemplu, "[None, 'salut', 10]" nu se va
sorta deoarece numerele întregi nu sunt comparabile cu șirurile de
caractere iar "None" nu poate fi comparat cu niciun alt tip de date.
De asemeni, există tipuri de date fără o relație de ordine totală.
Astfel, "3+4j < 5+7j" nu este o comparație validă.


5.1.1. Utilizarea listelor ca stive
-----------------------------------

Metodele tipului listă ne permit să utilizăm listele cu ușurință pe
post de stive, adică de instanțe ale acelui tip de date la care
ultimul element adăugat va fi extras primul ("ultimul intrat, primul
ieșit"). Pentru a pune un element în vârful stivei, folosiți
"append()". Pentru a scoate un element din vârful stivei, folosiți
"pop()" fără să precizați niciun index. De exemplu:

   >>> stiva = [3, 4, 5]
   >>> stiva.append(6)
   >>> stiva.append(7)
   >>> stiva
   [3, 4, 5, 6, 7]
   >>> stiva.pop()
   7
   >>> stiva
   [3, 4, 5, 6]
   >>> stiva.pop()
   6
   >>> stiva.pop()
   5
   >>> stiva
   [3, 4]


5.1.2. Făcând cozi din liste
----------------------------

Se poate să întrebuințăm o listă aidoma unei cozi, adică a unei
instanțe a acelui tip de date la care primul element adăugat va fi
extras primul ("primul intrat, primul ieșit"); însă listele nu au fost
proiectate ca să fie eficiente în acest scop. În timp ce adăugările la
final și extragerile de la finalul unei liste sunt rapide, realizarea
de inserări la început și de eliminări de la începutul unei liste sunt
operații lente (aceasta deoarece toate celelalte elemente trebuie
deplasate în lateral cu câte o poziție).

Pentru a implementa o coadă, folosiți "collections.deque" care a fost
proiectată special ca să realizeze adăugiri și eliminări rapide la
ambele capete. De exemplu:

   >>> from collections import deque
   >>> coadă = deque(["Eric", "John", "Michael"])
   >>> coadă.append("Terry")      # Terry intră în scenă
   >>> coadă.append("Graham")     # Graham intră în scenă
   >>> coadă.popleft()            # Primul sosit iese din scenă
   'Eric'
   >>> coadă.popleft()            # Al doilea sosit iese din scenă
   'John'
   >>> coadă                      # Coada rămasă, în ordinea intrărilor în scenă

   deque(['Michael', 'Terry', 'Graham'])


5.1.3. Comprehensiunea listelor
-------------------------------

Comprehensiunea listelor (*înțelegerea* dar și *explicarea* lor)
reprezintă o metodă succintă de a crea liste. Aplicațiile esențiale
ale acesteia sunt construcțiile de liste noi ale căror elemente să fie
rezultatul realizării anumitor operații cu fiecare din membrii unei
secvențe ori ai unui iterabil, respectiv crearea unei sub-secvențe din
acele elemente ale unei secvențe date care îndeplinesc o anumită
condiție.

De exemplu, să presupunem că dorim să creăm o listă de pătrate
perfecte, precum urmează:

   >>> pătrate_perfecte = []
   >>> for x in range(10):
   ...     pătrate_perfecte.append(x**2)
   ...
   >>> pătrate_perfecte
   [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Să remarcăm că procedeul anterior creează (ori suprascrie) o variabilă
numită "x" care va exista și după încheierea ciclului "for". Putem
calcula lista pătratelor perfecte și fără asemenea efecte secundare
dacă utilizăm fie expresia:

   pătrate_perfecte = list(map(lambda x: x**2, range(10)))

fie pe cea echivalentă acesteia:

   pătrate_perfecte = [x**2 for x in range(10)]

care este mai concisă și mai ușor de citit.

Comprehensiunea unei liste constă din paranteze drepte care încadrează
o expresie urmată de o clauză "for", apoi de zero sau mai multe clauze
"for" ori clauze "if". Rezultatul comprehensiunii va fi o listă nouă,
produsă ca urmare a evaluării expresiei în contextul clauzelor "for"
și "if" care îi urmează în cod. De exemplu, următoarea comprehensiune
de liste (în englezește, ca jargon, *listcomp*) combină elementele a
două liste atunci când aceste elemente nu sunt egale:

   >>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
   [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

și este echivalentă cu:

   >>> combinații = []
   >>> for x in [1,2,3]:
   ...     for y in [3,1,4]:
   ...         if x != y:
   ...             combinații.append((x, y))
   ...
   >>> combinații
   [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

Remarcați că ordinea instrucțiunilor "for" și "if" se păstrează în
ambele fragmente de cod.

Dacă expresia este un tuplu (precum "(x, y)"-ul din exemplul
precedent), atunci ea trebuie încadrată de paranteze.

   >>> vectorul = [-4, -2, 0, 2, 4]
   >>> # crearea unei liste noi cu valoarea elementelor dublată
   >>> [x*2 for x in vectorul]
   [-8, -4, 0, 4, 8]
   >>> # filtrarea listei pentru a exclude numerele negative
   >>> [x for x in vectorul if x >= 0]
   [0, 2, 4]
   >>> # aplicarea unei funcții fiecărui element din listă
   >>> [abs(x) for x in vectorul]
   [4, 2, 0, 2, 4]
   >>> # apelul unei metode a fiecărui element din listă
   >>> fructe_proaspete = ['  banană', '  hibrid de mure și zmeură ', 'fructul pasiunii  ']
   >>> [armă.strip() for armă in fructe_proaspete]
   ['banană', 'hibrid de mure și zmeură', 'fructul pasiunii']
   >>> # crearea unei liste de 2-tupluri de felul (numărul, pătratul său)
   >>> [(x, x**2) for x in range(6)]
   [(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
   >>> # tuplul trebuie încadrat de paranteze, în caz contrar producându-se o eroare
   >>> [x, x**2 for x in range(6)]
     File "<stdin>", line 1
       [x, x**2 for x in range(6)]
        ^^^^^^^
   SyntaxError: did you forget parentheses around the comprehension target?
   >>> # serializarea unei liste folosind o listcomp cu două 'for'-uri
   >>> vectorul = [[1,2,3], [4,5,6], [7,8,9]]
   >>> [numărul for elementul in vectorul for numărul in elementul]
   [1, 2, 3, 4, 5, 6, 7, 8, 9]

Comprehensiunile de liste pot conține atât expresii complexe cât și
funcții imbricate:

   >>> from math import pi
   >>> [str(round(pi, i)) for i in range(1, 6)]
   ['3.1', '3.14', '3.142', '3.1416', '3.14159']


5.1.4. Comprehensiuni imbricate de liste
----------------------------------------

Expresia inițială dintr-o comprehensiune de liste poate fi orice fel
de expresie, deci poate include și o (altă) comprehensiune de liste.

Să luăm în considerare următorul exemplu de matrice 3x4 implementată
ca listă formată din 3 liste de lungime 4:

   >>> matrice = [
   ...     [1, 2, 3, 4],
   ...     [5, 6, 7, 8],
   ...     [9, 10, 11, 12],
   ... ]

Comprehensiunea de liste care urmează va transpune liniile și
coloanele matricei:

   >>> [[linia[i] for linia in matrice] for i in range(4)]
   [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

Așa cum am văzut în secțiunea anterioară, comprehensiunea listei
interioare este evaluată în contextul "for"-ul care îi urmează listei,
așa că exemplul de cod este echivalent cu:

   >>> transpusa = []
   >>> for i in range(4):
   ...     transpusa.append([linia[i] for linia in matrice])
   ...
   >>> transpusa
   [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

cod care, la rândul său, este identic cu:

   >>> transpusa = []
   >>> for i in range(4):
   ...     # următoarele 3 linii de cod implementează listcomp-a imbricată
   ...     linia_transpusei = []
   ...     for linia in matrice:
   ...         linia_transpusei.append(linia[i])
   ...     transpusa.append(linia_transpusei)
   ...
   >>> transpusa
   [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

În lumea reală, preferați funcțiile predefinite oricăror instrucțiuni
iterative complexe. Funcția "zip()" s-ar descurca de minune în cazul
de întrebuințare pe care îl studiem:

   >>> list(zip(*matrice))
   [(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

A se vedea Despachetarea listelor de argumente pentru detalii despre
asteriscul de pe această linie de cod.


5.2. Instrucțiunea "del"
========================

Există o modalitate de a elimina un element dintr-o listă atunci când
îi știm indicele (poziția) dar nu și valoarea: instrucțiunea "del".
Aceasta diferă de metoda "pop()" care returnează valoarea elementului
eliminat. Instrucțiunea "del" poate fi utilizată și la eliminarea de
tranșe din listă ori la golirea întregii liste (ceea ce am făcut
anterior atribuindu-i tranșei o listă goală). De exemplu:

   >>> a = [-1, 1, 66.25, 333, 333, 1234.5]
   >>> del a[0]
   >>> a
   [1, 66.25, 333, 333, 1234.5]
   >>> del a[2:4]
   >>> a
   [1, 66.25, 1234.5]
   >>> del a[:]
   >>> a
   []

"del" poate fi folosită și la ștergerea completă a unei variabile:

   >>> del a

Referențierea numelui "a" de acum înainte este eronată (cel puțin până
când acestui nume îi vom atribui o valoare). Îi vom găsi ulterior și
alte întrebuințări lui "del".


5.3. Tupluri și secvențe
========================

Am văzut că șirurile de caractere și listele au multe proprietăți în
comun, cum ar fi operațiile de indexare și de tranșare. Tipurile
acestor obiecte sunt exemple de tipuri de date *secvență* (vezi Tipuri
secvență -- list, tuple, range). Deoarece Python-ul este un limbaj
aflat în plină evoluție, se poate ca și alte tipuri secvență de date
să îi fie adăugate. Mai există un tip secvență de date standard:
*tuplul*.

Un tuplu constă dintr-un număr de valori separate prin virgulă, cum ar
fi:

   >>> t = 12345, 54321, 'salut!'
   >>> t[0]
   12345
   >>> t
   (12345, 54321, 'salut!')
   >>> # Tuplurile pot fi imbricate:
   >>> u = t, (1, 2, 3, 4, 5)
   >>> u
   ((12345, 54321, 'salut!'), (1, 2, 3, 4, 5))
   >>> # Tuplurile sunt imutabile:
   >>> t[0] = 88888
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: 'tuple' object does not support item assignment
   >>> # însă pot conține obiecte mutabile:
   >>> v = ([1, 2, 3], [3, 2, 1])
   >>> v
   ([1, 2, 3], [3, 2, 1])

După cum puteți observa, atunci când sunt afișate, tuplurile apar
întotdeauna încadrate de paranteze, drept pentru care tuplurile
imbricate vor putea fi interpretate corect; pe de altă parte, ele pot
fi introduse cu sau fără paranteze, chiar dacă adesea parantezele sunt
necesare (atunci când tuplul face parte dintr-o expresie mai
cuprinzătoare). Deși nu îi putem atribui valori niciunui element
individual al unui tuplu, putem crea tupluri care conțin obiecte
mutabile, precum listele.

Chiar dacă tuplurile par să semene cu listele, ele se folosesc adesea
în situații diferite și pentru scopuri diferite de cele ale utilizării
listelor. Tuplurile sunt *imutabile* și conțin, de obicei, o secvență
eterogenă de elemente care pot fi accesate fie via despachetare (după
cum vom vedea ulterior în secțiunea de față) fie prin indexare (ba
chiar și prin intermediul atributelor în cazul "namedtuples"). În
schimb, listele sunt *mutabile* iar elementele unei liste sunt
omogene, de obicei, și se accesează prin iterări de-a lungul listei.

O problemă aparte o constituie construcția de tupluri cu 0 sau 1
elemente: sintaxa impune câteva adaosuri ciudate pentru a le acomoda.
Astfel, tuplurile goale se construiesc cu o pereche goală de paranteze
rotunde; un tuplu cu un singur element este construit dintr-o valoare
urmată de o virgulă (nu este suficient să încadrăm valoarea între
paranteze). Neplăcut, dar eficient. Ca, de exemplu:

   >>> gol = ()
   >>> singular = 'salut',    # <-- remarcați virgula de final
   >>> len(gol)
   0
   >>> len(singular)
   1
   >>> singular
   ('salut',)

Instrucțiunea "t = 12345, 54321, 'salut!'" este un exemplu de
*împachetare în tuplu*: valorile "12345", "54321" și "'salut!'" sunt
puse laolaltă într-un tuplu. Este posibilă și operația inversă:

   >>> x, y, z = t

Aceasta se numește, destul de potrivit, *despachetarea secvenței* și
funcționează pentru orice secvență admisă ca valoare de pe partea
dreaptă (ori r-valoare). Despachetarea secvențelor cere să fie tot
atâtea variabile pe partea stângă a semnului egal câte elemente are
secvența. Să observăm că atribuirea multiplă este, în realitate, doar
o combinație de împachetări de tupluri și de despachetări de secvențe.


5.4. Seturi
===========

Python-ul posedă și un tip de date pentru tratamentul *mulțimilor* (al
*seturilor*). Un set este o colecție neordonată de elemente, fără
duplicate. Întrebuințările de bază ale acestui tip de date includ
verificarea apartenenței și eliminarea duplicatelor de elemente.
Obiectele mulțime suportă, de asemeni, și operații matematice precum
reuniunea, intersecția, diferența ori diferența simetrică.

Atât acoladele cât și funcția "set()" pot fi folosite la crearea de
mulțimi. Remarcă: pentru a crea o mulțime vidă trebuie să utilizați
"set()", nu "{}"; cea din urmă formulă va construi un dicționar de
date gol, iar despre o asemenea structură de date vom discuta în
secțiunea următoare.

Iată o scurtă demonstrație:

   >>> coș = {'măr', 'portocală', 'măr', 'pară', 'portocală', 'banană'}
   >>> print(coș)               # arată că duplicatele au fost eliminate
   {'portocală', 'banană', 'pară', 'măr'}
   >>> 'portocală' in coș       # testarea rapidă a apartenenței
   True
   >>> 'pălămidă' in coș
   False

   >>> # Demonstrarea operațiilor cu mulțimi într-o problemă
   >>> # privind literele unicat din două cuvinte
   >>>
   >>> a = set('abracadabra')
   >>> b = set('alacazam')
   >>> a                        # litere unicat în a
   {'a', 'r', 'b', 'c', 'd'}
   >>> a - b                    # literele care sunt în a dar nu sunt în b
   {'r', 'd', 'b'}
   >>> a | b                    # literele care sunt sau în a sau în b sau în amândouă
   {'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
   >>> a & b                    # literele care sunt și în a și în b
   {'a', 'c'}
   >>> a ^ b                    # literele care sunt sau în a sau în b dar nu în amândouă
   {'r', 'd', 'b', 'm', 'z', 'l'}

Asemănător comprehensiunii listelor, sunt permise și comprehensiunile
de mulțimi:

   >>> a = {x for x in 'abracadabra' if x not in 'abc'}
   >>> a
   {'r', 'd'}


5.5. Dicționare de date
=======================

Alt tip de date util, predefinit în Python, este *dicționarul* (de
date, vezi Tipuri de asociere -- dict). Dicționare de date se găsesc
și în alte limbaje de programare, numite uneori "memorii asociative"
ori "tablouri asociative" (de la englezescul *array*). Spre deosebire
de secvențe, ale căror elemente sunt indexate cu indici dintr-un
domeniu de valori numerice, dicționarele de date au elementele
indexate cu *chei*, acestea putând fi obiecte ale oricărui tip de date
imutabile; șirurile de caractere și numerele pot fi oricând chei.
Tuplurile sunt întrebuințabile drept chei cu condiția să nu conțină
decât șiruri de caractere, numere, ori tupluri; dacă un tuplu conține
vreun obiect mutabil, fie în mod direct ori indirect, atunci el nu va
putea fi folosit pe post de cheie. Nu puteți utiliza listele drept
chei, deoarece listele pot fi modificate pe loc cu ajutorul
atriburilor la (un) index (dat), a atribuirilor la (o) tranșă (dată),
respectiv a unor metode precum "append()" și "extend()".

Cel mai bine este să ne gândim la dicționarul de date ca la o mulțime
de perechi *cheie: valoare* pentru care se cere să avem chei unice
(într-un singur dicționar). O pereche de acolade creează un dicționar
gol: "{}". Plasând o listă de perechi cheie:valoare cu elementele
separate prin virgule între aceste acolade, vom adăuga perechile
cheie:valoare din listă la dicționarul de date; acesta este, de fapt,
modul în care dicționarele sunt afișate.

The main operations on a dictionary are storing a value with some key
and extracting the value given the key.  It is also possible to delete
a key:value pair with "del". If you store using a key that is already
in use, the old value associated with that key is forgotten.

Extracting a value for a non-existent key by subscripting ("d[key]")
raises a "KeyError". To avoid getting this error when trying to access
a possibly non-existent key, use the "get()" method instead, which
returns "None" (or a specified default value) if the key is not in the
dictionary.

Executând instrucțiunea "list(d)" asupra unui dicționar de date "d",
se va returna lista tuturor cheilor utilizate de dicționar, în ordinea
în care ele au fost folosite la inserarea de valori (dacă le doriți
sortate, atunci folosiți "sorted(d)" în locul instrucțiunii
anterioare). Pentru a determina dacă o anumită cheie a fost deja
inclusă în dicționar, întrebuințați cuvântul-cheie "in".

Iată un exemplu succint de utilizare a dicționarului:

   >>> tel = {'jack': 4098, 'sape': 4139}
   >>> tel['guido'] = 4127
   >>> tel
   {'jack': 4098, 'sape': 4139, 'guido': 4127}
   >>> tel['jack']
   4098
   >>> tel['irv']
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   KeyError: 'irv'
   >>> print(tel.get('irv'))
   None
   >>> del tel['sape']
   >>> tel['irv'] = 4127
   >>> tel
   {'jack': 4098, 'guido': 4127, 'irv': 4127}
   >>> list(tel)
   ['jack', 'guido', 'irv']
   >>> sorted(tel)
   ['guido', 'irv', 'jack']
   >>> 'guido' in tel
   True
   >>> 'jack' not in tel
   False

Constructorul "dict()" produce dicționare de date direct din secvențe
de perechi cheie-valoare:

   >>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
   {'sape': 4139, 'guido': 4127, 'jack': 4098}

În plus, comprehensiunile de dicționare pot fi întrebuințate la a crea
dicționare de date din expresii arbitrare folosite de post de chei și
de valori:

   >>> {x: x**2 for x in (2, 4, 6)}
   {2: 4, 4: 16, 6: 36}

Atunci când cheile sunt simple șiruri de caractere este mai
convenabil, uneori, să specificăm perechile dicționarului folosind
argumente cuvânt-cheie:

   >>> dict(sape=4139, guido=4127, jack=4098)
   {'sape': 4139, 'guido': 4127, 'jack': 4098}


5.6. Tehnici de iterare
=======================

Atunci când iterăm de-a lungul unui dicționar, cheia și valoarea
corespunzătoare ei pot fi capturate simultan cu ajutorul metodei
"items()".

   >>> cavaleri = {'Gallahad': 'cel fără de prihană', 'Robin': 'cel viteaz'}
   >>> for k, v in cavaleri.items():
   ...     print(k, v)
   ...
   Gallahad cel fără de prihană
   Robin cel viteaz

Atunci când iterăm de-a lungul unei secvențe, indicele de poziție și
valoarea corespunzătoare lui pot fi capturate simultan cu ajutorul
funcției "enumerate()".

   >>> for i, v in enumerate(['x', 'și', 'zero']):
   ...     print(i, v)
   ...
   0 x
   1 și
   2 zero

Pentru a itera de-a lungul a două sau mai multe secvențe în același
timp, elementele acestora pot fi împerecheate cu ajutorul funcției
"zip()".

   >>> întrebări = ['numele', 'țelul', 'stindardul tău']
   >>> răspunsuri = ['Lancelot', 'Sfântul Potir', 'cel albastru']
   >>> for q, a in zip(întrebări, răspunsuri):
   ...     print('Care este {0} tău?  Este {1}.'.format(q, a))
   ...
   Care este numele tău?  Este Lancelot.
   Care este țelul tău?  Este Sfântul Potir.
   Care este stindardul tău?  Este cel albastru.

Pentru a itera de-a lungul unei secvențe în sens invers, mai întâi
precizați secvența în ordinea ei obișnuită după care apelați funcția
"reversed()".

   >>> for i in reversed(range(1, 10, 2)):
   ...     print(i)
   ...
   9
   7
   5
   3
   1

Ca să iterați de-a lungul unei secvențe în ordinea sortării ei,
utilizați funcția "sorted()" care va returna o listă nouă, sortată,
lăsând nemodificată lista inițială.

   >>> coș = ['măr', 'portocală', 'măr', 'pară', 'portocală', 'banană']
   >>> for i in sorted(coș):
   ...     print(i)
   ...
   banană
   măr
   măr
   pară
   portocală
   portocală

Aplicând "set()" asupra unei secvențe îi eliminăm duplicatele.
Întrebuințarea lui "sorted()" în combinație cu "set()" asupra unei
secvențe constituie o modalitate idiomatică de a itera de-a lungul
elementelor unicat ale secvenței în ordinea sortării acesteia.

   >>> coș = ['măr', 'portocală', 'măr', 'pară', 'portocală', 'banană']
   >>> for f in sorted(set(coș)):
   ...     print(f)
   ...
   banană
   măr
   pară
   portocală

Este tentant, uneori, să modificăm o listă în timp ce iterăm de-a
lungul ei; cu toate acestea, este adesea mai simplu și mai sigur să
creăm o listă nouă în loc să o modificăm pe cea inițială.

   >>> import math
   >>> date_brute = [56.2, float('NaN'), 51.7, 55.3, 52.5, float('NaN'), 47.8]
   >>> date_filtrate = []
   >>> for valoarea in date_brute:
   ...     if not math.isnan(valoarea):
   ...         date_filtrate.append(valoarea)
   ...
   >>> date_filtrate
   [56.2, 51.7, 55.3, 52.5, 47.8]


5.7. Mai multe despre expresiile de control
===========================================

*Condițiile* de îndeplinit (sau *expresiile condiționale* ori
*expresiile de control*) conținute în instrucțiunile "while" și "if"
pot folosi orice fel de operatori nu doar pe cei de comparație
(relaționali).

Operatorii de comparație "in" și "not in" realizează teste de
apartenență care determină dacă o valoare se află (sau nu se află)
într-un container. Operatorii "is" și "is not" compară două obiecte
pentru a stabili dacă ele sunt, cu adevărat, același obiect. Toți
operatorii de comparație au aceeași prioritate, aceasta fiind mai mică
decât cea a indiferent cărui operator numeric.

Operațiile de comparație pot fi înlănțuite. De exemplu, "a < b == c"
va testa dacă "a" este (strict) mai mic decât "b" și dacă "b" este
egal cu "c".

Expresiile condiționale pot fi combinate folosind operatorii booleeni
"and" și "or", iar rezultatul unei comparații (ori valoarea de adevăr
a oricărei alte expresii booleene) poate fi negat cu "not". Acești
operatori booleeni au priorități mai mici decât cele ale operatorilor
de comparație; dintre ei, "not" are prioritatea cea mai mare iar "or"
pe cea mai mică, astfel că expresia "A and not B or C" este
echivalentă cu "(A and (not B)) or C". Ca de obicei, parantezele
rotunde pot fi întrebuințate la construcția compoziției dorite a
oricărei expresii.

Operatorii booleeni "and" și "or" fac parte din așa-numiții *operatori
de scurtcircuit*: argumentele (operanzii) lor se evaluează de la
stânga către dreapta iar evaluarea se oprește de îndată ce rezultatul
este determinat. De exemplu, dacă expresiile "A" și "C" sunt adevărate
iar "B" este falsă, atunci evaluarea expresiei "A and B and C" nu
necesită evaluarea lui "C". Dacă este folosită ca valoare generală și
nu ca dată booleană (valoare de adevăr), valoarea returnată de un
operator de scurtcircuit este ultimul argument evaluat.

Este posibil să atribuim rezultatul unei operații de comparare ori pe
cel al evaluării altei expresii booleene unei variabile. De exemplu,

   >>> șirul1, șirul2, șirul3 = '', 'Trondheim', 'Hammer Dance'
   >>> nenul = șirul1 or șirul2 or șirul3
   >>> nenul
   'Trondheim'

Atenție la faptul că în Python, spre deosebire de C, atribuirile din
interiorul expresiilor trebuie realizate folosind în mod explicit
operatorul morsă ":=". Această restricție ne permite să evităm un tip
de dificultăți întâlnite în mod obișnuit la programarea în C: situația
în care tastăm "=" într-o expresie în care intenționam să introducem
"==".


5.8. Comparând secvențe și alte tipuri de obiecte
=================================================

Obiectele secvență se compară, de obicei, cu alte obiecte de același
tip secvență. Operația de comparare folosește ordinea *lexicografică*:
mai întâi sunt comparate primul element al unuia din cele două obiecte
cu primul element al celuilalt obiect, iar dacă acestea diferă unul de
celălalt, atunci diferența dintre ele determină rezultatul
comparației; în schimb, dacă cele două elemente sunt egale, atunci se
va compara cel de-al doilea element al primului obiect cu cel de-al
doilea element al celuilalt obiect și așa mai departe, până când vom
ajunge la finalul  (măcar) unuia dintre obiecte. Dacă se întâmplă ca
elementele care sunt comparate să fie ele însele obiecte ale aceluiași
tip secvență, atunci comparația lexicografică va fi continuată în mod
recursiv. Dacă toate elementele celor două obiecte secvență sunt egale
două câte două, atunci obiectele secvență vor fi considerate egale.
Dacă unul dintre obiecte este, de la început, o sub-secvență a
celuilalt obiect, atunci obiectul mai scurt este cel mai mic (dintre
cele două). Ordinea lexicografică a șirurilor de caractere
întrebuințează numerele de punct de cod Unicode pentru ordonarea
caracterelor individuale. Iată câteva exemple de comparații între
secvențe de același tip:

   (1, 2, 3)              < (1, 2, 4)
   [1, 2, 3]              < [1, 2, 4]
   'ABC' < 'C' < 'Pascal' < 'Python'
   (1, 2, 3, 4)           < (1, 2, 4)
   (1, 2)                 < (1, 2, -1)
   (1, 2, 3)             == (1.0, 2.0, 3.0)
   (1, 2, ('aa', 'ab'))   < (1, 2, ('abc', 'a'), 4)

Remarcați că este permisă operația de comparare a obiectelor de tipuri
diferite folosind operatorii "<" sau ">" cu condiția ca obiectele
corespunzătoare să posede metode de comparație potrivite. De exemplu,
tipurile numerice diferite se compară folosind valoarea numerică a
obiectelor lor, astfel că 0 este egal cu 0.0 șamd. În caz contrar, în
loc să furnizeze o ordonare arbitrară, interpretorul va ridica o
excepție "TypeError".

-[ Note de subsol ]-

[1] În alte limbaje de programare, este posibil să se returneze
    obiectul mutat, ceea ce va permite înlănțuirea metodelor, cum ar
    fi "d->insert("a")->remove("b")->sort();".
