5  Söfn: Eitt nafn en mörg gildi

5.1  Inngangur

Fyrir utan grunngagnatögin sem fjallað hefur verið um að framan er Python með ýmis innbyggð tög fyrir söfn (containers), en breytur af slíku tagi geta geymt mörg stök eða gildi. Reyndar hefur þegar verið talað svolítið um eitt slíkt tag, nefnilega strengi sem geta geymt marga stafi. Mörg safntögin í Python teljast runur (sequences), nánar tiltekið eru það strengir, listar (lists), samstæður (tuples) og ítrarar (iterators; t.d. útkoma úr range), en önnur teljast ekki runur, m.a. mengi (sets) og uppflettitöflur (dictionaries). Uppflettitöflurnar eru svolítið sér á parti og umfjöllun um þær kemur seinna, en í þessum kafla og þeim næsta verða hin safntögin á dagskrá. Eftirfarandi tafla sýnir hvernig hægt er að safna tölunum 1, 2, 3 og 4 í streng, lista, samstæðu, mengi og ítrara:

strengur

st = "1234"

listi

L = [1, 2, 3, 4]

samstæða

S = (1, 2, 3, 4)

mengi

M = {1, 2, 3, 4}

ítrari

Í = range(1,5)

Strengurinn er reyndar sér á parti því hann geymir ekki heiltölurnar 1–4 heldur tölustafina 1–4, sem eru geymdir öðruvísi í minni tölvunnar.

Það sem er sameiginlegt öllum runum er að hægt er að vísa í tiltekin stök eða gildi í rununni með hornklofum: ef R er runa og k er heiltala þá er

R[k]

stakið í sæti k í rununni, og fremsta stakið telst vera númer 0. Fyrir runur og mengi er hægt er að nota:

if g in R:... til að kanna hvort gildi g sé í R, og
for g in R:... til að láta g hlaupa í gegn um öll stökin í R

Svo má telja stökin með len(R) og ennfremur er hægt að nota samskeytingar- og fjölföldunarvirkjana (+ og *) á allar runur. Öll þessi atriði verða útlistuð nánar að neðan, m.a. í kafla 6.

5.2  Strengir

Strengjatagið heitir str og í strengjum má geyma runur af hvaða Unicode stöfum sem vera skal (Unicode er alþjóðlegur staðall til að skrá í tölvu bókstafi allra heimsins tungumála auk fjölmargra annarra rittákna: tölur, greinarmerki, stærðfræðitákn, broskallar, o.s.frv.). Strengir eru búnir til með því að setja texta innan einfaldra eða tvöfaldra gæsalappa:

s1 = "Ísland", s2 = 'Ísland', s3 = \(\textrm{"}\mathrm{\beta\in A \cap B}\textrm{"}\)

Ef s er strengur þá er hægt að ná í einstaka stafi með s[i] þar sem i er númer sætis (fremst er sæti 0), og svo er hægt að ná í hlutstrengi (slices) með s[i:j] (stök i,…, j-1), s[:j] (frá byrjun til j-1) og s[i:] (frá i og út í enda). Ef i er mínustala er talið aftanfrá. Ef s er "abcdef" og gefin er skipun print(a[0], a[1:3], a[-3:-1], a[-1] þá prentast út:

a bc de f

Í grein 4.3 var sýnt hvernig hægt er að skeyta saman og fjölfalda strengi, og ýmsar strengjaaðgerðir eru sýndar í kynningarvinnubókinni í æfingunni aftan við kafla 3.7.

Tafla 5.1: Nokkrar strengjaaðgerðir

s = s1 + s2

samskeyting strengja

s = s1*3

sama og s1 + s1 + s1

s[3]

stafur í sæti 3

s[2:5]

hlutstrengur í sætum 2,3,4

s.lower()

breyta í lágstafi

s.upper()

breyta í hástafi

s.capitalize()

breyta fremsta staf í hástaf

s.find(s1)

staðsetning s1 í s, -1 ef ekkert s1 finnst

len(s)

lengd strengsins

c in s

er stafurinn c í strengnum s?

s.isupper()

eru allir stafir í s hástafir?

Æfing: Strengjablús

Búið til streng með nafninu ykkar (t.d. s = "Kristján Jónasson"). Notið aðgerðir úr töflunnin að ofan til að:

  1. finna hvar fyrsta bilið er (á eftir fornafni/fyrsta nafni) með find,

  2. ná í fornafnið,

  3. finna lengd þess,

  4. athuga hvort það sé r í nafninu (hér ætti að nota lower ef nafnið skyldi byrja á R.

  5. prófið líka upper og capitalize

5.3  Listar

Listi (list) er grunnhugtak í tölvufræði. Listi geymir endanlegan fjölda staka í röð og hvert stak getur komið fyrir oftar en einu sinni. Python hefur safntag list sem útfærir lista. Það eru engin takmörk á því hvernig stök má geyma, þau mega sjálf vera söfn og þurfa ekki öll að vera af sama tagi. Tómur listi fæst með L = [] og almennan lista má búa til með því að skrifa:

L = [gildi, gildi...]

5.3.1  Listaaðgerðir

Aðgerðir fyrir tölvufræðilega lista eru m.a. að ná í fremsta stakið (haus, head), að ná í öll hin stökin (hali, tail) að bæta staki framan á eða aftan á lista, og að ná í stak í tilteknu sæti. Python listar bjóða upp á þessar aðgerðir og ýmsar fleiri (sjá æfinguna í kafla 5.3.7).

../_images/head-tail.png

Mynd 5.1: Haus og hali

5.3.2  Vísað í stök

Eins og fyrr segir má vísa í einstök stök í lista með L[i] þar sem i er heiltala með númeri staks, og hlutlistar (slices) fást eins og fyrir strengi, sbr. eftirfarandi dæmi:

L = [2, 'abc', 4.33, [1,2]]
print(L[1], L[2], L[-1])
print(L[1:3])
print(L[2:])

# Forritið að ofan prentar út:
abc 4.33 [1, 2]
['abc', 4.33]
[4.33, [1, 2]]

5.3.3  Samskeyting og margföldun

Aðgerðunum + og * sem við höfum séð að duga til að skeyta saman og fjölfalda strengi má líka beita á lista. Þannig gefur [2,3,5] + [7,11] listann [2,3,5,7,11] og [2,3,5]*2 er [2,3,5,2,3,5].

5.3.4  Gildisgjöf gefur tilvísun

Ef lista er gefið gildi með venjulegri gildisgjöf M = L þá verður ekki til nýr listi heldur bara ný tilvísun (reference) eða nýtt nafn á listann L. Til að afrita listann mætti rita N = L.copy(). Hér er dæmi:

Sýnidæmi: Afrit og tilvísun

L = [2, 3, 5, 7, 9]
M = L
N = L.copy()
L[4] = 11
print(M)   # prentar [2, 3, 5, 7, 11]
print(N)   # prentar [2, 3, 5, 7, 9]

Takið eftir að L og M verða tvö nöfn á sama listanum, en N er nýr listi sem geymdur er á öðrum stað í minni tölvunnar.

Þegar kallað er á fall með lista sem viðfang þá fær fallið tilvísun í listann. Það þýðir að fallið getur breytt listanum sem það hefur sem stika og við það breytist tilsvarandi viðfang þar sem kallið er. Þetta gerist ekki ef kallað er á föll með stök gildi sem viðfang. Hér er dæmi sem sýnir þetta:

Sýnidæmi: Listaviðfang og töluviðfang

def listafall(A): A[0] = 3
def tölufall(a):  a = 3

tala = 5
listi = [5,6]
tölufall(tala)
listafall(listi)
print(tala,listi)  # prentar 5 [3, 6]

5.3.5  Bætt við lista

Í dæmunum hér á undan sést hvernig hægt er að breyta einu staki í lista, en sú aðferð dugar ekki til að lengja listann: L[5] = 13 mundi gefa villu. Til að bæta 13 aftan á listann mætti nota aðra hvora af eftirfarandi skipunum

L.append(13)
L = L + [13]

5.3.6  Aðferðir

Köllin á copy og append eru óvenjuleg. Fall sem kallað er á svona, með breyta.fall(...), er kallað aðferð (method), en aðferðir eru eitt af grundvallarhugtökum í hlutbundinni forritun (object oriented programming). Breytan á undan punktinum er þá kölluð hlutur (object), og aðferðin virkar sem sé á hann. Fallið find í töflunni yfir strengjaðgerðir hér framar er annað dæmi um aðferð.

5.3.7  Fleiri listaföll

Til eru fjölmörg fleiri föll fyrir lista til viðbótar við copy og append. Mörg þessara falla duga líka á önnur safntög, sér í lagi samstæður og mengi, og í þessum fyrirlestrarnótum hafa töflur með mikilvægustu safnaföllum verið settar á einn stað, í 6. kafla. Þar á meðal eru ýmis fleiri dæmi um hlutbundnar aðferðir

Æfing: Listablús

  1. Skrifið föll haus(L) og hali(L) sem skila haus og hala lista L. Prófið.

  2. Flettið upp á insert-fallinu í grein 6.5 og notið það til að búa til fall setjafremst(L,g) sem bætir g við sem nýjum haus fremst í listann. Ath. að fallið á ekki að hafa neina return skipun, heldur notfærir það sér að listaviðföng eru tilvísanir. Prófið.

  3. Búið til fall oddalisti(n) sem skilar lista með n fyrstu oddatölunum. Byrjið með tóman lista, [], og notið svo for-lykkju sem hleypur í gegn um tölurnar \(1, 3,\ldots, 2n-1\) og bætir hverri þeirra aftan á listann. Prófið.

5.4  Samstæður

Annað safntag í Python sem líka útfærir tölvunarfræðilega lista er samstæða (tuple). Enska orðið tuple er fengið að láni úr stærðfræði, en stærðfræðilegt tuple (þýtt í stærðfræðiorðasafninu með n-und) er skilgreint sem „endanleg röðuð runa af stökum“, venjulega táknuð með því að telja stökin upp innan sviga t.d. \((2, 3, 4)\). N-undir eiga margt skylt með punktum í plani eða rúmi, enda rithátturinn sá sami. Hér er hugtakið þýtt með samstæða, en undirrituðum finnst n-und stirt og hálfljótt. Svigarithátturinn er einmitt notaður til að búa til samstæður í Python:

S = (gildi, gildi...).

Reyndar má sleppa svigunum: S = gildi, gildi... er jafngilt. Til að búa til tóma samstæðu má rita S = () og til að búa til samstæðu með einu staki þarf að enda á kommu, t.d. S = (4,). Samstæða tveggja staka nefnist par eða tvennd (pair, couple), og þriggja staka samstæða er þrennd (triple).

Hornklofa má áfram nota til að vísa í einstök stök og hlutsamstæður: print(S[0]) prentar fyrsta gildið í S og print(S[0:2]) prentar fyrstu tvö. Einn helsti munurinn á listum og samstæðum er sá að það er ekki hægt að breyta stökum gildum í samstæðum, stækka þær eða minnka eftir að þær hafa verið búnar til:

L = [1,2,3]  # þriggja staka listi
L[1] = 4     # þetta má
S = (1,2,3)  # þrennd
S[1] = 4     # þetta gefur villu

Sagt er að samstæður séu óbreytanlegar (immutable) en listar séu breytanlegir (mutable). Stundum er gott að geta treyst því að einstök gildi breytist ekki, og auk þess notar Python málið samstæður í ýmsum skipunum, t.d. þegar kallað er á föll sem skila fleiru en einu gildi, sbr. kafla 4.7, og til að búa til föll með breytilegum stikafjölda (sjá kafla 12.4 í Think Python kennslubók). Eitt í viðbót sem er frábrugðið: Hægt er að búa til mengi af samstæðum, og þær geta verið lyklar í uppflettitöflum, en hvorugt má með listum.

Flest föllin sem talin eru upp í 6. kafla duga á samstæður, nema föllin í grein 6.5. Eins og fyrr segir má líka nota samskeytingarvirkjana + og * á þær.

5.5  Mengi

Stærðfræðihugtakið mengi (set) er skilgreint sem „safn ólíkra staka“ og endanleg mengi má tákna með því að telja stökin upp innan slaufusviga: \(\{2, 3, 4\}\). Mengi eru óröðuð og þótt stök séu talin tvisvar breytir það ekki menginu. Þannig gildir:

\[\{4, 3, 2\} = \{2, 3, 4\} = \{2, 3, 3, 4\}\]

Mengi í Python eru táknuð með sama hætti:

M = {gildi, gildi, ...}

Tómamengið er búið til með tómt = set() (rithátturinn {} er frátekinn til að búa til tóma uppflettitöflu). Mengi eru óbreytileg eins og samstæður þannig að eftir að þau hafa verið búin til er ekki hægt að breyta þeim. Það er heldur ekki hægt að vísa í stök með hornklofum, en hinsvegar eru til Python-virkjar fyrir helstu mengjaaðgerðir, eins og sýnt er í töflunni hér að neðan, og auk þess er hægt að nota föllin í töflunum í greinum 6.2 og 6.3.

Af því mengin eru ekki röðuð er alls ekki tryggt print skipun prenti þau í röð. Ef við viljum prenta mengi í röð er hægt að breyta þeim í raðaðan lista með fallinu sorted og prenta hann svo, sem sé print(sorted(M)).

Tafla 5.2: Mengjavirkjar

Python-virki

stærðfræði-virki

aðgerð

in

\(\in\)

er stak í

not in

\(\notin\)

er ekki stak í

<=

\(\subseteq\)

er hlutmengi í

&

\(\cap\)

sniðmengi

|

\(\cup\)

sammengi

\(-\textrm{ eða }\smallsetminus\)

mengjamismunur

^

\(\Delta\)

samhverfur mismunur

../_images/mengjamunur.png

Mynd 5.2: Mengjamismunurinn AB

../_images/samhverfur-munur.png

Mynd 5.3: Samhverfi mismunurinn AB

Æfing: Prímtölur < 20

  1. Búið til mengi S með sléttum tölum 2–20 og M3 með tölunum 3, 6,…, 18 og M5 með 5, 10, 15, 20 (með því að nota set(range(...))). Prentið svo M6 = S \(\cap\) M3 (margfeldi af 6 sem eru < 20).

  2. Látið X vera mengi talnanna 2–20 og finnið P = mengi prímtalna < 20 sem (X – (S \(\cup\) M3 \(\cup\) M5)) \(\cup\) {2,3,5}

Æfing: Enska og saga

Ef E er mengi nemenda í Ensku og S er mengi nemenda í Sögu þá er E \(\cap\) S mengi þeirra sem eru í báðum fögum, E \(\cup\) S er mengi þeirra sem eru í einhverju fagi og E \(\Delta\) S eru þeir sem eru í nákvæmlega einu fagi. Látið E = {"Ari", "Ása", "Fía", "Jói"}, S = {"Fía", "Jói", "Nói"} og ákvarðið samsettu mengin þrjú með Python-mengjaaðgerðum. Teiknið gjarna mynd á blað.

5.6  Ítrarar

Um fallið range sem notað er í for-lykkjum var fjallað í greinum 2.4 og 4.10.2. Það skilar gildi af taginu ítrari (iterator). Í for-lykkjum má líka nota nota aðrar gerðir af runum en ítararnir hafa þann kost að spara minnispláss, því aðeins byrjunargildið, lokagildið og skrefið eru geymd. Hér eru nokkur dæmi:

for i in range(1000): ... # þ.e. fyrir i = 0,1,2...999
r = range(2,8)            # r geymir bara 2, 8 og skrefið 1
r1 = range(2,11,3)        # r1 geymir 2, 11 og skrefið 3
L1 = list(r1)             # gefur L1 = [2, 5, 8]
for i in r1: ...          # fyrir i = 2, 5, 8
L = list(range(1000))     # gefur L = [0,1,...,999]
for k in L: ...           # jafngilt fyrsta dæminu en þarf meira minni

Æfing:

Fallið sys.getsizeof(x) skilar fjölda bæta sem breytan x tekur í minni. Finnið út hve mikið minni range(1000) og list(range(1000)) taka (byrjið með import sys).

Annað þægilegt fall sem skilar ítrara er fallið enumerate, en enumerate(L) skilar ítrara sem rennir sér í gegn um öll stök í L og býr til pör (0, L[0]), (1, L[1]) o.s.frv. Hugsum okkur að við höfum lista af nöfnum og viljum skrifa þau út ásamt númerum þeirra í listanum. Eftirfarandi dæmi sýnir tvær jafngildar leiðir til þess.

Sýnidæmi: Nöfn og númer

Báðar lykkjurnar í eftirfarandi forriti skrifa út:

1 Ása
2 Bjarni
3 Dóra
4 Einar
nöfn = ["Ása", "Bjarni", "Dóra", "Einar"]
for (nr,nafn) in enumerate(nöfn):
    print(nr,nafn)

for nr in range(len(nöfn)): # Þessi lykkja er jafngild þeirri að ofan
    nafn = nöfn[nr]
    print(nr,nafn)

Við sjáum  forritið er aðeins einfaldara þegar enumerate er notað.

Það er líka hægt að byrja að telja í 1 með enumerate(L,1) og svo má líka setja pörin sem verða til inn í sérstaka breytu:

Sýnidæmi: Byrjað með númer 1

for p in enumerate([5,25,125], 1):  # veldi af 5
    print(p)                        # prentar (1, 5), (2, 25) og (3, 125)

Þetta jafngildir:

L = [5, 25, 125]
for i in range(len(L)):
    p = (i+1, L[i])
    print(p)

zip er annað fall sem smíðar pör; það tekur inn tvo (eða fleiri) jafnlanga lista og parar þá saman: zip([0,1,2], [5,6,7]) skilar pörunum (0,5), (1,6) og (2,7).

Æfing: enumerate og zip

  1. Smíðið pörin (0,2), (1,4), (2,6), (3,8), (4,10) með enumerate og prentið út (til dæmis dugar E = enumerate(...) og print(list(E))).

  2. Smíðið sömu pör með zip.

  3. Látið nöfn vera nafnalistann í sýnidæminu „Nöfn og númer“ og eink vera [8, 7, 10, 9] (einkunnir í landafræði). Skrifið forrit sem notar enumerate til að búa til töfluna:

    Nr  Nafn  Einkunn
    –––––––––––––––––
    1   Ása      8
    2   Bjarni   7
    3   Dóra    10
    4   Einar    9
    

    Leiðbeining: Hægt er að nota enumerate og zip saman svona:

    for nr,(s1,s2) in enumerate(zip(L1,L2),1)

    Þá hleypur nr í gegn um 1,2,3… og samtímis hlaupa s1 og s2 í gegn um stökin í L1 og L2.