post

Python ro:Structuri de date

Contents

Introducere

Structurile de date sunt în esenţă exact asta – structuri care pot memora date grupate. Cu alte cuvinte, sunt folosite pentru a stoca colecţii de date înrudite.

Există patru structuri predefinite în Python – liste, tupluri, dicţionare şi seturi. Vom învăţa să le folosim pe fiecare şi cum ne pot uşura ele viaţa.

Liste

O listă (engl. list) este o structură de date care păstrează o colecţie ordonată de elemente deci se poate memora o secvenţă de elemente întro listă. Asta este uşor de imaginat dacă ne gândim la o listă de cumpărături, numai că pe listă fiecare item ocupă un rând separat, iar în Python punem virgule intre ele.

Elementele listei trebuie incluse în paranteze drepte astfel încât Python să înţeleagă că este o specificare de listă. Odată ce am creat lista, putem adăuga, şterge sau căuta elemente în ea. De aceea se poate spune că listele sunt muabile, acest tip de date poate fi modificat.

Introducere rapidă în obiecte şi clase

Deşi în general am amânat discutarea obiectelor şi claselor pâna acum, este necesară o scurtă introducere, pentru a se putea înţelege listele mai bine. Detaliile le vom afla în capitolul dedicat acestora.

O listă este un exemplu de utilizare a obiectelor şi claselor. Când folosim o variabilă i şi îi atribuim o valoare, să zicem întregul 5, putem gândi că am creat un obiect (de fapt instanţă) i din clasa (de fapt tipul) int. De fapt se poate citi help(int) pentru o înţelegere mai aprofundatată.

O clasă poate avea şi metode adică funcţii definite pentru a fi folosite exclusiv în raport cu acea clasă. Puteţi folosi aceste funcţionalităţi numai asupra unui obiect din acea clasă. De exemplu, Python oferă o metodă append pentru clasa list care ne permite să adăugăm un element la sfârşitul listei. Prin urmare lista_mea.append('un item') va adăuga acel şir la lista_mea. De reţinut folosirea notaţiei cu punct pentru accesarea metodelor obiectelor.

O clasă poate avea şi câmpuri (engl. fields) care nu sunt altceva decât variabile definite în raport exclusiv cu acea clasă. Se pot folosi acele variabile/nume numai în raport un obiect din acea clasă. Câmpurile sunt accesate tot prin notaţia cu punct, de exemplu lista.câmp.

Exemplu:

#!/usr/bin/python
# Fişier: using_list.py

# Lista mea de cumpărături
shoplist = ['mere', 'mango', 'morcovi', 'banane']

print('Am de cumpărat', len(shoplist), 'itemuri.')

print('Acestea sunt:', end=' ')
for item in shoplist:
    print(item, end=' ')

print('nTrebuie să cumpăr şi orez.')
shoplist.append('orez')
print('Lista mea de cumpărături este acum', shoplist)

print('Acum vom sorta lista')
shoplist.sort()
print('Lista sortată este', shoplist)

print('Primul lucru de cumpărat este', shoplist[0])
item_cumpărat = shoplist[0]
del shoplist[0]
print('Am cumpărat', item_cumpărat)
print('Lista mea este acum', shoplist)

Rezultat:

   $ python using_list.py
   Am de cumpărat 4 itemuri.
   Acestea sunt: mere mango morcovi banane
   Trebuie să cumpăr şi orez.
   Lista mea de cumpărături este acum
   ['mere', 'mango', 'morcovi', 'banane', 'orez']
   Acum vom sorta lista
   Lista sortată este
   ['banane', 'mango', 'mere', 'morcovi', 'orez']
   Primul lucru de cumpărat este banane
   Am cumpărat banane
   Lista mea este acum
   ['mango', 'mere', 'morcovi', 'orez']

Cum funcţionează:

Variabila shoplist este o listă de cumpărături pentru cineva care merge la piaţă. În shoplist, memorăm şiruri care reprezintă numele lucrurilor pe care le avem de cumpărat, dar putem adăuga orice fel de obiect inclusiv numere sau alte liste.

Am folosit bucla for..in pentru a itera prin itemurile listei. Până acum cred că aţi realizat ca o listă este şi secvenţă în acelaşi timp. Specificul secvenţelor va fi discutat întrun subcapitol următor.

Observaţi utilizarea argumentului cuvânt cheie end al funcţiei print pentru a-i transmite că vrem să încheiem linia cu un spaţiu (‘ ‘) în loc de încheierea uzuală.

În continuare adăugăm un item la listă folosind metoda append a obiectului listă, aşa cum am discutat anterior. Verificăm că adăugarea s-a realizat tipărind conţinutul listei prin transmiterea ei funcţiei print care o tipăreşte frumos pe ecran.

Mai departe sortăm lista folosind metoda sort a listei. Este important să întelegem că această metodă afecteaza însăşi lista şi nu întoarce o listă modificată – spre deosebire de comportamentul sirurilor. Asta vrem să zicem prin muabile pe când şirurile sunt imuabile.

După ce cumpărăm un item de la piaţă, vrem să-l eliminăm din listă. Pentru aceasta utilizăm declaraţia del. Trebuie menţionat aici itemul pe care vrem să-l eliminăm şi declaraţia del îl elimină pentru noi. Specificăm că vrem să eliminăm primul item, de aici declaraţia del shoplist[0] (amintiţi-vă că Python începe numărătoarea de la 0).

Dacă vreţi să ştiţi toate metodele definite de obiectul listă, citiţi help(list).

Tupluri

Tuplurile sunt folosite pentru păstra colecţii de obiecte. Sunt similare cu listele, dar fără funcţionalitatea extinsă pe care o dau clasele. O facilitate majoră a tuplurilor este că ele sunt imuabile ca şi şirurile adică nu pot fi modificate.

Tuplurile sunt definite prin specificarea itemurilor separate prin virgule întro pereche opţională de paranteze.

Tuplurile sunt folosite de obicei în cazurile în care o declaraţie sau o funcţie definită de utilizator poate presupune fără risc de greşeală că o colecţie de valori nu se va schimba..

Exemplu:

#!/usr/bin/python
# Fişier: using_tuple.py

zoo = ('piton', 'elefant', 'pinguin') # reţineţi că parantezele sunt opţionale
print('Numărul animalelor în zoo este', len(zoo))

zoo_nou = ('maimuţă', 'cămilă', zoo)
print('Numărul de cuşti în noul zoo este', len(zoo_nou))
print('Animalele din noul zoo sunt ', zoo_nou)
print('Animalele aduse din vechiul zoo sunt ', zoo_nou[2])
print('Ultimul animal adus din vechiul zoo este', zoo_nou[2][2])
print('Numărul de animale în noul zoo este', len(zoo_nou)-1+len(zoo_nou[2]))

Rezultat:

   $ python using_tuple.py
   Numărul animalelor în zoo este 3
   Numărul de cuşti în noul zoo este 3
   Animalele din noul zoo sunt ('maimuţă', 'cămilă', ('piton', 'elefant', 'pinguin'))
   Animalele aduse din vechiul zoo sunt ('piton', 'elefant', 'pinguin')
   Ultimul animal adus din vechiul zoo este pinguin
   Numărul de animale în noul zoo este 5

Cum funcţionează:

Variabila zoo este un tuplu de itemuri. Vedem că funcţia len lucrează şi pentru tupluri. De asemenea asta arată că tuplurile sunt şi secvenţe.

Acum mutăm aceste animale într-un nou zoo, deoarece vechiul zoo s-a închis, să zicem. Ca urmare tuplul zoo_nou conţine nişte animale care erau acolo împreună cu animalele aduse din vechiul zoo. În realitate, acum, reţineţi că un tuplu în interiorul altui tuplu nu îşi pierde identitatea.

Putem accesa itemurile din tuplu specificând poziţia întro pereche de paranteze pătrate, ca pentru liste. Acesta se numeşte operator de indexare. Accesăm al treilea item din zoo_nou specificând zoo_nou[2] şi accesăm al treilea item al tuplului zoo din tuplul zoo_nou specificând zoo_nou[2][2]. E destul de simplu după ce aţi înteles regula.

Paranteze
Deşi parantezele sunt opţionale, eu prefer să le pun mereu, pentru a face evident că e vorba de un tuplu, în special pentru a evita ambiguitatea. De exemplu print(1,2,3) şi print( (1,2,3) ) înseamnă două lucruri foarte diferite – prima tipăreşte trei numere, iar a doua tipăreşte un tuplu (care conţine trei numere).
Tupluri cu 1 sau 0 elemente
Un tuplu vid este construit folosind o pereche de paranteze goale myempty = (). Totuşi, un tuplu cu un singur element nu e aşa de simplu. Trebuie să îl specificaţi folosind virgula după primul (şi ultimul) element, ca Python să poată diferenţia între tuplu şi un alt obiect cuprins în paranteze întro expresie deci va trebui să specificaţi singleton = (2 , ) dacă vreţi să se înţeleagă ‘tuplul care conţine doar elementul 2‘.
Notă pentru programatorii în Perl
O listă întro listă nu-şi pierde identitatea adică nu este asimilată ca în Perl. Asta se aplică şi pentru un tuplu întrun tuplu, o listă întrun tuplu, un tuplu întro listă etc. În ce priveşte limbajul Python, ele sunt nişte obiecte stocate în alte obiecte.

Dicţionare

Un dicţionar este ca o carte de adrese în care poţi găsi adresa sau datele de contact ale persoanei doar ştiindu-i numele, adică asociem chei (nume) cu valori (detalii). De observat că o cheie trebuie să fie unică, pentru a nu exista confuzii, exact ca atunci când nu poţi deosebi două persoane dacă au acelaşi nume.

Pe post de chei puteţi folosi numai obiecte imuabile (precum şirurile), dar pe post de valori putem folosi orice fel de valori. În esenţă înseamnă că ar trebui să folosim pe post de chei numai obiecte simple.

Perechile cheie – valoare sunt specificate întrun dicţionar folosind notaţia d = {cheie1: valoare1, cheie2: valoare2 }. Observaţi că perechile cheie – valoare sunt separate prin virgulă, iar cheia este separată de valoare prin semnul două puncte. Dicţionarul este delimitat de o pereche de acolade.

Reţineţi că întrun dicţionar perechile cheie – valoare nu sunt ordonate în nici un fel. Dacă vreţi o anumită ordine, va trebui să îl sortaţi singuri înainte de folosire.

Dicţionarele pe care le veţi folosi sunt instanţe/obiecte ale clasei dict.

Exemplu:

#!/usr/bin/python
# Fişier: using_dict.py

# 'ab' este o prescurtare de la 'a'ddress'b'ook

ab = {  'Swaroop'  : 'swaroop@swaroopch.com',
        'Larry'    : 'larry@wall.org',
        'Matsumoto': 'matz@ruby-lang.org',
        'Spammer'  : 'spammer@hotmail.com'
    }

print("Adresa lui Swaroop este", ab['Swaroop'])

# Ştergerea unei perechi cheie - valoare
del ab['Spammer']

print('nExistă {0} contacte în address-bookn'.format(len(ab)))

for nume, adresa in ab.items():
    print('Contactaţi pe {0} la adresa {1}'.format(nume, adresa))

# Adăugarea unei perechi cheie - valoare
ab['Guido'] = 'guido@python.org'

if 'Guido' in ab: # OR ab.has_key('Guido')
    print("nAdresa lui Guido este", ab['Guido'])

Rezultat:

   $ python using_dict.py
   Adresa lui Swaroop este swaroop@swaroopch.com

   Există 3 contacte în address-book

   Contactaţi pe Swaroop la adresa swaroop@swaroopch.com
   Contactaţi pe Matsumoto la adresa matz@ruby-lang.org
   Contactaţi pe Larry la adresa larry@wall.org

   Adresa lui Guido este guido@python.org

Cum funcţionează:

Creăm dicţionarul ab folosind notaţia deja discutată. Accesăm perechile cheie – valoare specificând cheia şi folosind operatorul de indexare, aşa cum am discutat la liste şi tupluri. Observaţi simplitatea sintaxei.

Putem şterge perechi cheie valoare folosind vechiul nostru prieten – declaraţia del. Pur şi simplu specificăm dicţionarul şi operatorul de indexare pentru cheia care trebuie ştearsă şi le dăm declaraţiei del. Nu este necesar să se cunoască şi valoarea asociata cheii pentru a realiza această operaţie.

În continuare accesăm fiecare pereche cheie – valoare din dicţionar folosind metoda items a dicţionarului care întoarce o listă de tupluri în care fiecare tuplu conţine o pereche de itemuri – cheia urmată de valoare. Extragem această pereche şi o atribuim variabilelor nume şi adresă corespunzător pentru fiecare pereche folosind o buclă for..in în care tipărim aceste informaţii.

Putem adăuga perechi noi cheie – valoare prin simpla utilizare a operatorului de indexare pentru a accesa cheia şi a atribui acea valoare, aşa cum am făcut pentru Guido în cazul de mai sus.

Putem testa dacă există în dicţionar o anumită pereche cheie – valoare folosind operatorul in sau chiar metoda has_key a clasei dict. Puteţi consulta documentaţia pentru o listă completă a metodelor clasei dict folosind comanda help(dict).

Argumente cuvânt cheie şi dicţionare
Într-o altă ordine de idei, dacă aţi folosit argumente cuvânt cheie în funcţii, înseamnă că aţi folosit deja dicţionare! Ia gândiţi-vă: perechea cheie – valoare este specificată în lista de parametri a definiţiei funcţiei şi când accesaţi variabilele, numele lor sunt chei de acces ale unui dicţionar numit tabel de simboluri în terminologia proiectării de compilatoare).

Secvenţe

Listele, tuplurile şi şirurile sunt exemple de secvenţe, dar ce sunt secvenţele şi ce le face atât de speciale?

Facilitatea cea mai importantă este că au teste de apartenenţă (adică expresiile in şi not in) şi operaţii de indexare. Operaţia de indexare ne permite să extragem direct un item din secvenţă.

Au fost menţionate trei tipuri de secvenţe – liste, tupluri şi şiruri şi operaţia de feliere, care ne permite să extragem o parte (engl. slice) din secvenţă.

Exemplu:

#!/usr/bin/python
# Fişier: seq.py

shoplist = ['mere', 'mango', 'morcovi', 'banane']
nume = 'swaroop'

# Operaţia de indexare sau 'subscriere'
print('Itemul 0 este', shoplist[0])
print('Itemul 1 este', shoplist[1])
print('Itemul 2 este', shoplist[2])
print('Itemul 3 este', shoplist[3])
print('Itemul -1 este', shoplist[-1])
print('Itemul -2 este', shoplist[-2])
print('Caracterul 0 este', nume[0])

# Felierea unei liste
print('Itemurile de la 1 la 3 sunt', shoplist[1:3])
print('Itemurile de la 2 la sfârsit sunt', shoplist[2:])
print('Itemurile de la 1 la -1 sunt', shoplist[1:-1])
print('Itemurile de la început la sfârşit sunt', shoplist[:])

# Felierea unui şir
print('Caracterele de la 1 la 3 sunt', nume[1:3])
print('Caracterele de la 2 la end sunt', nume[2:])
print('Caracterele de la 1 la -1 sunt', nume[1:-1])
print('Caracterele de la început la sfârşit sunt ', nume[:])

Rezultat:

   $ python seq.py
   Itemul 0 este mere
   Itemul 1 este mango
   Itemul 2 este morcovi
   Itemul 3 este banane
   Itemul -1 este banane
   Itemul -2 este morcovi
   Caracterul 0 este s
   Itemurile de la 1 la 3 sunt ['mango', 'morcovi']
   Itemurile de la 2 la sfârşit sunt ['morcovi', 'banane']
   Itemurile de la 1 la -1 sunt ['mango', 'morcovi']
   Itemurile de la început la sfârşit sunt ['mere', 'mango', 'morcovi', 'banane']
   Caracterele de la 1 la 3 sunt wa
   Caracterele de la 2 la sfârşit sunt aroop
   Caracterele de la 1 la -1 sunt waroo
   Caracterele de la început la sfârşit sunt swaroop

Cum funcţionează:

La început, vedem cum se folosesc indecşii pentru a obţine un element anume din secvenţă. Asta se mai numeşte operaţia de subscriere. De câte ori specificaţi un număr întro pereche de paranteze drepte alăturate unei secvenţe, Python vă va extrage itemul corespunzator poziţiei în secvenţă. Reţineţi că Python începe numărătoarea de la 0. Astfel shoplist[0] extrage primul item şi shoplist[3] îl extrage pe al patrulea din secvenţa shoplist.

Indexul poate fi şi un număr negativ, caz în care poziţia este calculată de la sfârşitul secvenţei. Din acest motiv shoplist[-1] indică ultimul item din secvenţă, iar shoplist[-2] penultimul item.

Operaţia de feliere este folosită specificând numele secvenţei urmat de o pereche opţională de numere separate prin semnul doua puncte (engl. colon) incluse în paranteze drepte. Observaţi că este foarte asemănător cu operaţia de indexare folosită până acum. De reţinut că numerele sunt opţionale, semnul două puncte NU este opţional.

Primul număr (înainte de două puncte) în operaţia de feliere indică punctul unde începe felia, iar al doilea număr indică unde se temină. Dacă nu este specificat primul număr, Python va începe cu începutul secvenţei. Dacă este omis al doilea număr, Python se va opri la sfârşitul secvenţei. Observaţi că felia întoarsă (uneori se spune ‘returnată’) începe cu poziţia dată de primul număr, dar se încheie imediat înainte de poziţia dată de al doilea număr adică începutul este inclus, sfârşitul nu este inclus în felie.

Astfel, shoplist[1:3] întoarce o felie din secvenţa care începe cu poziţia 1, include poziţia 2, dar nu include poziţia 3 deci este întoarsă o felie de două itemuri. Similar, shoplist[:] întoarce o copie a secvenţei.

Mai puteti face feliere şi cu indecşi negativi. Numerele negative sunt folosite pentru a indica poziţii de la sfârşitul secvenţei. De exemplu, shoplist[:-1] va întoarce o felie din secvenţă care exclude ultimul item din secvenţă, dar include tot restul secvenţei.

De asemenea, putem da al treilea argument pentru feliere, care devine pasul de feliere (implicit pasul este 1):

>>> shoplist = ['mere', 'mango', 'morcovi', 'banane']
>>> shoplist[::1]
['mere', 'mango', 'morcovi', 'banane']
>>> shoplist[::2]
['mere', 'morcovi']
>>> shoplist[::3]
['mere', 'banane']
>>> shoplist[::-1]
['banane', 'morcovi', 'mango', 'mere']

Iată ce se întâmplă când pasul este 2, obţinem itemurile cu poziţiile 0, 2, … Dacă pasul este 3, obtinem itemurile din poziţiile 0, 3, etc.

Încercaţi variate combinaţii de specificaţii de feliere în modul interactiv, pentru a vedea imediat rezultatele. Marele avantaj al secvenţelor este că puteţi accesa tuplurile, listele şi şirurile în acelaşi fel!

Seturi

Seturile sunt colecţii neordonate de obiecte simple. Acestea sunt folosite atunci cand existenţa unui obiect în colecţie este mai importantă decât poziţia lui sau numărul de apariţii.

Folosind seturi, puteţi testa apartenenţa, dacă un set este subset al altui set, puteţi afla intersecţia a două seturi şi aşa mai departe.

>>> bri = set(['brazilia', 'rusia', 'india'])
>>> 'india' in bri
True
>>> 'usa' in bri
False
>>> bric = bri.copy()
>>> bric.add('china')
>>> bric.issuperset(bri)
True
>>> bri.remove('rusia')
>>> bri & bric # OR bri.intersection(bric)
{'brazilia', 'india'}

Cum funcţionează:

Exemplul este autoexplicativ deoarece implică teoria de bază învăţată la şcoala despre mulţimi (seturi).

Referinţe

Când creaţi un obiect şi îi atribuiţi o valoare, variabila doar indică obiectul creat, nu reprezintă obiectul însuşi! Asta înseamnă că numele variabilei este un indicator către acea parte a memoriei calculatorului în care este stocat obiectul. Acest fapt se numeşte legare (engl. binding) a numelui la obiect.

În general, nu trebuie să vă îngrijoraţi de asta, dar există un efect subtil datorat referinţei de care trebuie să fiţi conştienţi:

Exemplu:

#!/usr/bin/python
# Fişier: reference.py

print('Atribuire simplă')
lista_iniţială = ['mere', 'mango', 'morcovi', 'banane']
lista_mea = lista_iniţială # lista_mea este doar un alt nume al aceluiaşi obiect!

del lista_iniţială[0] # Am cumpărat primul item, deci să-l ştergem din listă

print('lista initială este', lista_iniţială)
print('lista mea este', lista_mea)
# Observaţi că ambele liste apar fără 'mere', confirmând
# astfel că ele indică acelaşi obiect

print('Copiere făcând o felie intergală')
lista_mea = lista_iniţială[:] # Face o copie prin feliere integrală
del lista_mea[0] # eliminăm primul item

print('lista_iniţială este', lista_iniţială)
print('lista_mea este', lista_mea)
# Observaţi că acum cele două liste sunt diferite?

Rezultat:

   $ python reference.py
   Atribuire simplă
   lista_iniţială este ['mango', 'morcovi', 'banane']
   lista_mea este ['mango', 'morcovi', 'banane']
   Copiere făcând o felie intergală
   lista_iniţială este ['mango', 'morcovi', 'banane']
   lista_mea este ['morcovi', 'banane']

Cum funcţionează:

Partea principală a explicaţiei se regăseşte în comentarii.

Reţineţi că dacă vreţi să faceţi o copie a unei liste sau a unor obiecte complexe (nu simple obiecte, cum ar fi întregii), atunci trebuie să folosiţi felierea. Dacă folosiţi atribuirea obţineţi încă un nume pentru acelaşi obiect şi asta poate aduce probleme dacă nu sunteţi atenţi.

Notă pentru programatorii în Perl
Reţineţi că o declaraţie de atribuire pentru liste nu creeaza o copie. Va trebui să folosiţi felierea pentru a face o copie a secvenţei.

Alte detalii despre şiruri

Am discutat deja în detaliu despre şiruri. Ce ar mai putea fi de ştiut? Ei bine, ştiaţi că şirurile sunt obiecte şi au metode care fac orice de la verificarea unor părţi ale şirului până la eliminarea spaţiilor?

Sirurile pe care le folosiţi în programe sunt obiecte din clasa str. Câteva metode utile sunt arătate în exemplul următor. Pentru o listă completă a metodelor, citiţi help(str).

Exemplu:

#!/usr/bin/python
# Fişier: str_methods.py

nume = 'Swaroop' # Acesta este obiectul şir

if name.startswith('Swa'):
    print('Da, şirul începe cu "Swa"')

if 'a' in nume:
    print('Da, şirul conţine subşirul "a"')

if name.find('war') != -1:
    print('Da, şirul conţine subşirul "war"')

delimitator = '_*_'
Lista_mea= ['Brazilia', 'Rusia', 'India', 'China']
print(delimitator.join(lista_mea))

Rezultat:

   $ python str_methods.py
   Da, şirul începe cu "Swa"
   Da, şirul conţine subşirul "a"
   Da, şirul conţine subşirul "war"
   Brazilia_*_Rusia_*_India_*_China

Cum funcţionează:

Aici vedem o mulţime de metode în acţiune. Metoda startswith este folosită pentru a testa dacă şirul începe sau nu cu un şir dat. Operatorul in se foloseşte pentru a verifica includerea unui şir în şirul dat.

Metoda find este folosită pentru a găsi poziţia unui şir dat în şirul iniţial, care întoarce -1 dacă nu a găsit nimic. Clasa str are şi o metodă simpatică join pentru a alătura itemurile dintro secvenţă, cu un şir pe post de delimitator între itemuri şi intoarce un şir lung generat din toate acestea.

Rezumat

Am explorat diversele structuri de date predefinite în Python în detaliu. Aceste structuri de date vor deveni esenţiale pentru scrierea de programe de dimensiuni rezonabile.

Acum că avem o mulţime de elemente Python asimilate, vom vedea cum se proiectează şi se scriu programe Python în lumea de zi cu zi.


Advertisements