# 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();`.