Tög sem geyma önnur tög
Listar, túplar og tætitöflur, hvað er það?
Last updated
Listar, túplar og tætitöflur, hvað er það?
Last updated
Flest forritunarmál bjóða upp leið til að halda utan um marga hluti í eins konar geymi (e. cointainer). Þessi geymir er af ákveðnu tagi (e. type) sem gerir okkur kleift að nálagst fleiri hluti sem hann heldur utan um. Það gerir hann með skipulögðum hætti og við getum nálgast "innihaldið" með því að gefa upp ákveðið heiltölu gildi, nokkurs konar sætisnúmer. Til að átta sig örlítið betur á þessu má ímynda sér sjálfsala sem inniheldur til dæmis drykki eða nammi. Til að fá eitthvað úr sjálfsalanum sláum við inn töluna sem er fyrir framan þann hlut sem okkur langar í og sjálfsalinn skilar til okkar því sem við báðum um.
Í Python eru innbyggð tög sem hafa þessa eiginleika og í þessum hluta ætlum við að skoða lista (e. lists), túpla (e. tuples) og að lokum tætitöflur (e. hash tables). Við ætlum að reyna að svara eftirfarandi spurningum:
Til hvers eru þessi tög?
Hvað einkennir þau?
Hvernig eru þau notuð?
Byrjum á að skoða lista (e. list). Í stuttu máli má segja að listi sé breytanleg, röðuð runa af stökum. Hvað þýða þessi hugtök; breytanleg, röðuð og runa? Það að listi sé breytanlegur (e. mutable) þýðir að við getum breytt uppbyggingu (innihaldi) hans eftir að hann hefur verið búinn til. Við getum sem sagt bætt við, breytt og fjarlægt stök úr listanum. Þegar við segjum að listi sé röðuð runa mætti segja að það þýði að öll stök listans fá einskonar sætistölu og þessi röð helst þar til (og ef) við breytum henni sérstaklega. Einn af kostum runu-taga er að við getum ítrað (e. iterate) í gegnum þau til dæmis með for-lykkju. Listar og strengir eiga það sameiginlegt að vera hvort tveggja runutök en eru þó ansi ólík tög. Strengir eru óbreytanlegir og halda einungis utan um stafi og tákn á meðan listar eru breytanlegir og geta innihaldið allskonar mismunandi tög, þar á meðal fleiri lista sem geta einnig innihaldið lista og svo framvegis.
Við vitum nú þegar að megin hlutverk lista er að halda utan um fleiri en einn hlut og að gera það með skipulögðum hætti. Þó er vert að nefna að listi getur verið tómur og við ákveðnar aðstæður gætum við kosið að búa til tóman lista. Við getum búið til tóman lista á tvo vegu; annars vegar með tómum hornklofum ([]
) og hins vegar með fallinu list()
:
Stökin sem listinn heldur utan um geta verið breytur eða einstaka gildi. Við getum einnig tilgreint stök listans þegar hann er skilgreindur (búinn til):
Til að nálgast stak úr lista skeitum við vísi (e. index) viðkomandi staks aftan við breytunafn listans. Prófum að prenta stak númer 1 úr síðasta dæmi:
Eins og við sjáum, þá telja listar (alveg eins og strengir) frá núlli (fyrsti vísirinn er 0). Ef að breyta hefur heiltölugildi, þá getum við notað breytunafnið sem vísi:
Við getum notað vísi listans til að breyta ákveðnu staki. Það gerum við með setningu þar sem gildinu er veitt á breytunafn listans ásamt vísi innan hornklofa:
Við getum viljað vísa í mörg stök lista og notum til þess aðferð sem kallast sneiðing. Það gerum við, rétt eins og þegar við vísum í stakt stak, með því að tilgreina vísa innan hornklofa. Munurinn er sá að í stað þess að tilgreina einn vísi, þá tilgreinum við fyrsta vísinn sem við viljum fá ásamt þeim vísi sem við viljum fara að (næsta vísi á eftir þeim síðasta sem við viljum fá) og skiljum þá að með tvípunkti ([x:y]
) innan hornklofanna. Þetta kann að virðast ruglingslegt í fyrstu en verður ljóst með því að skoða nokkur dæmi (og enn ljósara með því að prófa á eigin spýtur!).
Í þeim tilfellum þar sem þau stök sem við viljum liggja á öðrum hvorum enda, annað hvort frá upphafi og að ákveðnu staki eða frá ákveðnu staki og til enda, getum við sleppt því að tilgreina annann vísinn og Python les þá tvípunktinn þannig að spanið ná frá eða að endanum, eftir aðstæðum. Skoðum dæmi til að átta okkur betur á þessu:
Annað sem vert er að nefna er vísirinn -1
en hann táknar ávallt síðasta stak lista. Þannig er hægt að telja aftur á bak því -2
er jafnframt næst-síðasta stakið og þannig koll af kolli.
Til að kanna hvort að listi innihaldi ákveðið stak getum við notað in
virkjann. Ef við skrifum setningu með in
virkjanum fáum við Boole-skilagildi, annað hvort True
eða False
. Þannig erum við í raun að spyrja hvort að þetta stak finnist í þessum lista og svarið verður annað hvort já eða nei.
Við getum skeytt saman tveimur eða fleirum listum með því að setja plúsmerki á milli þeirra:
Við getum einnig búið til lista sem endurtekin stök annars lista:
Við getum fjarlægt stök úr lista og höfum til þess nokkrar aðferðir. Við ætlum að skoða þrjár leiðir:
pop()
del
remove()
Í tilfellum þar sem við viljum nota vísi staks til að eyða því getum við við notað innbyggt fall fyrir lista sem heitir pop()
. Fallinu er skeytt aftan við breytunafn listans og tekur inntaksgildi, heiltölu, sem er vísir þess staks sem skal fjarlægt.
Takið eftir því að þegar við notum pop()
, þá prentast sjálfkrafa stakið sem við fjarlægðum.
Önnur aðferð til að fjarlægja stök með því nota vísa þeirra er að nota del
setningu.
Þegar við notum del
eru stökin ekki prentuð eins og með pop()
en það er vegna þess að del
getur fjarlægt mörg stök í einni setningu. Til að fjarlægja mörg stök í einu notum við sneiðingu. Skoðum nokkur dæmi:
Í tilfellum þar sem vísir þess staks sem við viljum fjarlægja er óþekktur getum við notað fallið remove()
. Þessu falli er skeytt aftan við breytunafn lista (listi.remove()
) en í stað þess að taka vísi staks sem inntaksgildi athugar fallið hvort listinn innihaldi stak sem er eins og inntaksgildið og fjarlægir það (ef það finnst).
Í þessum hluta höfum við kynnst ýmsum föllum í tengslum við ákveðnar aðgerðir. Hér á eftir komum við inn á nokkur föll til viðbótar, bæði föll sem eru sérstaklega fyrir lista en einnig föll sem notuð eru með fleiri gagnatögum en gagnlegt er að vita hvernig virka með listum.
Fallið append()
bætir staki aftast í lista.
Til að telja hversu oft eitthvað tiltekið stak kemur fyrir í lista getum við notað fallið count()
. Fallinu er skeytt aftan við lista og þegar kallað er á fallið þarf að tilgreina það stak sem skal talið (listi.count(<stak>)
). Strengurinn sem fallið tekur inn (leitarstrengurinn) getur verið staflesgildi að lengd eða lengri. Mikilvægt er að hafa í huga að fallið er hástafanæmt (e. case sensitive). Skilagildi fallsins er heiltala og ef ekkert tilvik finnst, þá verður skilagildið núll (0
).
Fallið insert()
færir stak inn í lista á ákveðinn vísi. Það þýðir að í stað þess að bæta stakinu aftast í listann (eins og append() gerir
) þá veljum við staðsetningu staksins innan listans.
Fallið tekur tvö inntaksgildi, annars vegar vísinn (sætistöluna) og hins vegar það gildi sem á að bæta inn í listann (listi.insert(<vísir>, <gildi>)
). Stök með vísi hærri en nýja stakið færast einu sæti aftar (vísir + 1). Engu staki er eytt úr listanum.
Fallið sort()
raðar stökum lista eftir stærð eða í stafrófsröð. Fallið getur ekki raðað innan lista sem innihalda bæði tölur og runur (t.d. strengi og heiltölur). Ef listinn inniheldur tölur, þá raðast þær eftir stærð og ef hann inniheldur strengi, þá raðast þeir í stafrófsröð.
Dæmi þar sem stökin eru heiltölur:
Dæmi þar sem stök eru strengir:
Fallið er hægt að nota án inntaksgilda og þá virkar það eins og því var lýst hér á undan. Fallið getur aftur á móti tekið inn eitt eða tvö inntaksgildi; key
og reverse
. Gildið reverse
er Boole-gildi og við getum skilgreint það sem True
og þá raðast stökin í öfugri röð. Gildið key
býður upp á að raða einnig eftir ákveðnum lykilþáttum. Í dæminu hér á eftir ætlum við að skilgreina leng (len
) sem lykilatriði og þá raðast strengir ekki lengur í stafrófsröð (sem er sjálfgefið) heldur eftir lengd.
Fallið reverse()
endurraðar stökum innan lista og speglar þá röð sem stökin voru í fyrir. Öfugt við sort()
, þá raðar reverse()
stökum ekki eftir neinni reglu eins og stærðarröð eða stafrófsröð, bara öfuga röð frá þeirri sem fyrir var.
Þessi föll eru ekki sérstaklega fyrir lista en geta engu síður reynst nytsamlega fyrir lista.
Fallið len()
skilar heiltölugildi sem segir til um fjölda staka innan viðkomandi lista.
Fallið max()
skilar hæsta tölugildi lista og er einungis fyrir tölur (heil- eða kommutölur).
Fallið min()
skilar lægsta tölugildi lista og er, rétt eins og max()
, einungis fyrir tölur (heil- eða kommutölur).
Túpull er runutag rétt eins og listi. Gildi sem geymd eru í túpli geta verið af hvaða tagi sem er og hafa sætistölu; vísi. Einhver kann að hugsa með sér að túpull sé þá bara alveg eins og listi? Túpull og listi eiga margt sameiginlegt en á þeim er þó einn mjög mikilvægur grundvallar munur; túpull er óbreytanlegur. Hvað þýðir það að túpull sé óbreytanlegur? Það þýðir að eftir að hann hefur verið skilgreindur, þá er ekki hægt að breyta honum. Þá er spurningin hvers vegna við myndum vilja nota túpul þegar við getum notað lista? Listar eru jú fjölhæfari. Meginástæðurnar eru tvær; annars vegar að túplar eru mun hraðari í meðhöndlun en listar og í tilfellum þar sem unnið er með stór gagnasöfn getur munað miklu á vinnslutíma. Hins vegar geta falist kostir í því að gögn geti ekki breyst og það hefur í för með sér ákveðið öryggi.
Hvað málskipan varðar þá eru túplar skilgreindir með svigum en ekki hornklofum eins og listar. Stök innan túpla eru aðskilin með kommum rétt eins og innan lista.
Við getum breytt lista í túpul með fallinu tuple()
og notum viðkomandi lista sem inntaksgildi.
Á sama máta getum við einnig breytt túplum í lista með list()
fallinu.
Með túplum getum notað flest föll sem eru fyrir lista svo lengi sem þau föll eru ekki til að breyta listanum. Við getum til dæmis notað föll eins og count()
til að telja stök innan túpulsins en við getum ekki notað fall eins og append()
sem bætir staki við lista. Ef við reynum það fáum við villuboð sem útskýra það fyrir okkur:
Tætitafla (e. hash table) er gagnatag sem byggir á pörun á milli lykils (e. key) og gildis (e. value). Í Python er tætitafla kölluð dictionary (ísl. orðabók eða uppflettirit) frekar en hash table þó svo að hugtökin beri sömu merkingu. Við munum hins vegar sjá að þetta gagnatag á ýmislegt sameiginlegt með orðabókum og því er kannski skiljanlegt að Python fólkið hafi frekar viljað nota það hugtak.
Tætitafla er breytanlegt og óraðað tag. Við segjum að hún sé breytanleg vegna þess að við getum breytt gildum hennar, bætt við og fjarlægt eftir að hún hefur verið skilgreind. Við segjum að tætitaflan sé óröðuð vegna þess að stök hennar hafa ekki vísi sem er heiltölugildi eins og til dæmis listar. Þess í stað má segja að að lyklar tætitöflunnar séu vísar en þeir, ásamt gildum þeirra, halda ekki endilega alltaf sömu röð. Hvert stak tætitöflunnar er par lykils og gildis.
Tætitöflur eru skilgreindar með slaufusvigum ({}
), lykill og gildi eru aðskilin með tvípunkti og pör með kommu.
Til að búa til tóma tætitöflu getum annaðhvort skilgreint breytu með tómum slaufusvigum eða með fallinu dict()
:
Eins og fram hefur komið, þá hafa stök tætitöflunnar engin föst sætisnúmer eins og stök lista. Þess í stað höfum við lykla og getum notað þá eins og vísa.
Lyklar tætitöflunnar geta verið af ýmsu tagi; heil- og kommutölur, strengir eða túplar. Gildi lyklanna geta hins vegar verið af hvaða tagi sem er til dæmis listar eða tætitöflur.
Skoðum dæmi þar sem við viljum prenta gildi lykilsins 'aldur'
úr tætitöflunni nemandi
hér á undan:
En hvað gerist ef við vísum á lykil sem ekki er í tætitöflunni?
Eins og við sjáum, þá fáum við villuboð ef við vísum á lykil sem ekki er til staðar. Stundum viljum við ekki að forrit stöðvist með villuboðum og þá þurfum við að gera ákveðnar ráðstafanir og við köllum það villumeðhöndlun (e. error management).
Skoðum næst aðra leið þar sem við notum fall fyrir tætitöflur sem heitir get()
. Þetta fall athugar hvort að sá lykill sem við erum að leita að sé í tætitöflunni. Sé viðkomandi lykill í tætitöflunni, þá skilar fallið gildi lykilsins en annars skilar það tómagildinu None
.
Einnig býður get()
upp á valkvæmt auka inntaksgildi sem er strengur. Þessi strengur verður að skilagildi fallsins í stað None
ef lykillinn finnst ekki.
Til að bæta nýju staki í tætitöflu skeytum við hornklofum aftan við breytunafn viðkomandi tætitöflu, innan hornklofanna setjum við lykilinn, eftir hægri hornklofann kemur jafnaðarmerki (=
) og að lokum gildið.
Skoðum dæmi þar sem við bætum lykli sem er strengurinn sími
í tætitöfluna nemandi
ásamt gildi sem er strengurinn 999-9999
:
Til að uppfæra gildi getum við notað sömu aðferð og við beittum í síðasta dæmi. Ímyndum okkur að nemandinn hafi fengið nýtt símanúmer og við viljum uppfæra það. Nýja símanúmerið er 999-9990
:
Til að athuga hvort að ákveðinn lykill finnist í tætitöflu getum við notað in
virkjann. Ef við skrifum setningu með in
virkjanum fáum við Boole-skilagildi, annað hvort True
eða False
.
Til að athuga hvort að tiltekið gildi sé að finna í tætitöflu getum við notað in
-virkjann alveg eins og ef við værum að gá að lykli. Í þessu tilfelli þurfum við þó að fara aðeins öðruvísi að heldur ef við værum að athuga með lykil. Skoðum dæmi þar sem við ætlum að kanna hvort að nafnið (eða strengurinn) Blær
komi fyrir. Prófum að beita sömu aðferð og ef um lykil væri að ræða:
Eins og við sjáum, þá fáum við False
jafnvel þó við vitum að gildið Blær
sé til staðar í tætiföflunni nemandi
. Ástæðan er sú að þegar við notum in
virkja með tætitöflu, þá á það alltaf við um lykla nema að við tökum annað sérstaklega fram. Það gerum við með því að nota fall fyrir tætitöflur sem heitir values()
. Ef við skeytum því aftan við breytunafn tætitöflunnar í in
-setningunni þá getum við leitað í gildum frekar en lyklum.
Til að eyða staki getum við notað del
-setningu. Segjum sem svo að við viljum ekki lengur halda utan um símanúmer nemenda og viljum því fjarlægja lykilinn sími
og gildið sem honum fylgir.
Einnig gætum við notað fallið pop()
til að fjarlægja stak úr tætiföflu. Þetta sama fall er einnig í boði fyrir lista og hefur sömu virkni fyrir tætitöflur; það tekur lykil sem inntaksgildi og það prentar síðan gildi þess staks sem við fjarlægjum.
Skoðum dæmi þar sem við endurtökum síðasta dæmi nema með pop()
-fallinu:
Fallið len()
skilar heiltölugildi sem segir til um fjölda staka innan viðkomandi tætitöflu þar sem hvert stak er par lykils og gildis (lykill : gildi
).
Við getum ítrað (e. iterate) í gegn um tætitöflu alveg eins og um lista væri að ræða og þá fer lykkjan á milli lykla tætitöflunnar.
Ef við viljum prenta gildi tætitöflunnar með for-lykkju, þá skulum við rifja upp hvernig við nálgumst gildi staks. Það gerum við með því að setja lykilinn innan hornklofa aftan við breytunafn tætitöflunnar <tætitafla>[<lykill>]
.
Ef við horfum á síðasta kóðadæmi þar sem við notuðum for-lykkjuna, þá sjáum við að breytan x
er lykill tætitöflunnar hverju sinni. Það þýðir að við getum sett innan hornklofa aftan við nemandi
:
Við gætum einnig viljað prenta saman lykil og gildi:
Önnur leið til að prenta lykil og gildi saman væri að nota items()
-fallið. Munurinn er sá að items()
skilar tveimur gildum og það þýðir að við getum notað tvær breytur í for-lykkjunni; eina fyrir lykilinn og eina fyrir gildið.