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)

   Add an item to the end of the list.  Equivalent to "a[len(a):] =
   [x]".

list.extend(iterable)

   Extend the list by appending all the items from the iterable.
   Equivalent to "a[len(a):] = iterable".

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()

   Remove all items from the list.  Equivalent to "del a[:]".

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

   Return zero-based index in the list of the first item whose value
   is equal to *x*. Raises a "ValueError" if there is no such item.

   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()

   Return a shallow copy of the list.  Equivalent to "a[:]".

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

   >>> fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']
   >>> fruits.count('apple')
   2
   >>> fruits.count('tangerine')
   0
   >>> fruits.index('banana')
   3
   >>> fruits.index('banana', 4)  # Find next banana starting at position 4
   6
   >>> fruits.reverse()
   >>> fruits
   ['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']
   >>> fruits.append('grape')
   >>> fruits
   ['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']
   >>> fruits.sort()
   >>> fruits
   ['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']
   >>> fruits.pop()
   'pear'

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.

Another thing you might notice is that not all data can be sorted or
compared.  For instance, "[None, 'hello', 10]" doesn't sort because
integers can't be compared to strings and *None* can't be compared to
other types.  Also, there are some types that don't have a defined
ordering relation.  For example, "3+4j < 5+7j" isn't a valid
comparison.


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:

   >>> stack = [3, 4, 5]
   >>> stack.append(6)
   >>> stack.append(7)
   >>> stack
   [3, 4, 5, 6, 7]
   >>> stack.pop()
   7
   >>> stack
   [3, 4, 5, 6]
   >>> stack.pop()
   6
   >>> stack.pop()
   5
   >>> stack
   [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
   >>> queue = deque(["Eric", "John", "Michael"])
   >>> queue.append("Terry")           # Terry arrives
   >>> queue.append("Graham")          # Graham arrives
   >>> queue.popleft()                 # The first to arrive now leaves
   'Eric'
   >>> queue.popleft()                 # The second to arrive now leaves
   'John'
   >>> queue                           # Remaining queue in order of arrival
   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ă:

   >>> squares = []
   >>> for x in range(10):
   ...     squares.append(x**2)
   ...
   >>> squares
   [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:

   squares = list(map(lambda x: x**2, range(10)))

fie pe cea echivalentă acesteia:

   squares = [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:

   >>> combs = []
   >>> for x in [1,2,3]:
   ...     for y in [3,1,4]:
   ...         if x != y:
   ...             combs.append((x, y))
   ...
   >>> combs
   [(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.

   >>> vec = [-4, -2, 0, 2, 4]
   >>> # create a new list with the values doubled
   >>> [x*2 for x in vec]
   [-8, -4, 0, 4, 8]
   >>> # filter the list to exclude negative numbers
   >>> [x for x in vec if x >= 0]
   [0, 2, 4]
   >>> # apply a function to all the elements
   >>> [abs(x) for x in vec]
   [4, 2, 0, 2, 4]
   >>> # call a method on each element
   >>> freshfruit = ['  banana', '  loganberry ', 'passion fruit  ']
   >>> [weapon.strip() for weapon in freshfruit]
   ['banana', 'loganberry', 'passion fruit']
   >>> # create a list of 2-tuples like (number, square)
   >>> [(x, x**2) for x in range(6)]
   [(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
   >>> # the tuple must be parenthesized, otherwise an error is raised
   >>> [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?
   >>> # flatten a list using a listcomp with two 'for'
   >>> vec = [[1,2,3], [4,5,6], [7,8,9]]
   >>> [num for elem in vec for num in elem]
   [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:

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

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

   >>> [[row[i] for row in matrix] 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:

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

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

   >>> transposed = []
   >>> for i in range(4):
   ...     # the following 3 lines implement the nested listcomp
   ...     transposed_row = []
   ...     for row in matrix:
   ...         transposed_row.append(row[i])
   ...     transposed.append(transposed_row)
   ...
   >>> transposed
   [[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(*matrix))
   [(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, 'hello!'
   >>> t[0]
   12345
   >>> t
   (12345, 54321, 'hello!')
   >>> # Tuples may be nested:
   ... u = t, (1, 2, 3, 4, 5)
   >>> u
   ((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
   >>> # Tuples are immutable:
   ... t[0] = 88888
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: 'tuple' object does not support item assignment
   >>> # but they can contain mutable objects:
   ... 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:

   >>> empty = ()
   >>> singleton = 'hello',    # <-- note trailing comma
   >>> len(empty)
   0
   >>> len(singleton)
   1
   >>> singleton
   ('hello',)

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:

   >>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
   >>> print(basket)                      # show that duplicates have been removed
   {'orange', 'banana', 'pear', 'apple'}
   >>> 'orange' in basket                 # fast membership testing
   True
   >>> 'crabgrass' in basket
   False

   >>> # Demonstrate set operations on unique letters from two words
   ...
   >>> a = set('abracadabra')
   >>> b = set('alacazam')
   >>> a                                  # unique letters in a
   {'a', 'r', 'b', 'c', 'd'}
   >>> a - b                              # letters in a but not in b
   {'r', 'd', 'b'}
   >>> a | b                              # letters in a or b or both
   {'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
   >>> a & b                              # letters in both a and b
   {'a', 'c'}
   >>> a ^ b                              # letters in a or b but not both
   {'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.

Operațiile principale pe care le realizăm asupra unui dicționar sunt
inserarea unei valori cu ajutorul unei anumite chei și extragerea unei
valori căreia îi știm cheia. Este, de asemeni, posibil să elimnăm o
pereche cheie:valoare cu ajutorul lui "del". Dacă inserăm o valoare
folosind o cheie care a fost deja asociată unei anumite valori, atunci
vechea valoare se va pierde. Încercarea de a extrage o valoare
folosind o cheie inexistentă va produce o eroare.

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
   >>> 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()".

   >>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
   >>> for k, v in knights.items():
   ...     print(k, v)
   ...
   gallahad the pure
   robin the brave

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(['tic', 'tac', 'toe']):
   ...     print(i, v)
   ...
   0 tic
   1 tac
   2 toe

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()".

   >>> questions = ['name', 'quest', 'favorite color']
   >>> answers = ['lancelot', 'the holy grail', 'blue']
   >>> for q, a in zip(questions, answers):
   ...     print('What is your {0}?  It is {1}.'.format(q, a))
   ...
   What is your name?  It is lancelot.
   What is your quest?  It is the holy grail.
   What is your favorite color?  It is blue.

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ă.

   >>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
   >>> for i in sorted(basket):
   ...     print(i)
   ...
   apple
   apple
   banana
   orange
   orange
   pear

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.

   >>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
   >>> for f in sorted(set(basket)):
   ...     print(f)
   ...
   apple
   banana
   orange
   pear

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
   >>> raw_data = [56.2, float('NaN'), 51.7, 55.3, 52.5, float('NaN'), 47.8]
   >>> filtered_data = []
   >>> for value in raw_data:
   ...     if not math.isnan(value):
   ...         filtered_data.append(value)
   ...
   >>> filtered_data
   [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,

   >>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
   >>> non_null = string1 or string2 or string3
   >>> non_null
   '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();".
