3. Structuren in de taal In dit hoofdstuk behandelen we de belangrijkst econtrolestructuren die in de algoritmiek gebruikt worden. Dit zijn o.a. de opeenvolging, selectie en lussen (herhaling). Vóór we deze controle-structuren behandelen, vertellen we iets over variabelen. Ingewikkelder vormen van variabelen komen later in het hoofdstuk ter sprake in de vorm van de gegevenstructuren vector en matrix. Alle behandelde structuren zijn te gebruiken in de pseudotaal (zie ook het overzicht van de pseudotaal in hoofdstuk 11 van deze syllabus). Zoals we in hoofdstuk 2 hebben uitgelegd moeten we, om algoritmen in de pseudotaal te kunnen beschrijven, afspraken maken over syntax en semantiek van die taal. De eerste stap die we zetten in die richting heeft betrekking op zg controle structuren. De eenvoudigste structuur in een taal is de opeenvolging. Al iets ingewikkelder worden de herhalingen en de uitzonderingen (selecties). Structuur in een algoritme is erg belangrijk, niet alleen om de computer ons te laten begrijpen, maar ook om zelf inzicht te krijgen in wat er eigenlijk gebeurt. Ook als je andere mensen je algoritme wilt geven, is een goede structuur noodzakelijk. Hierdoor begrijpen de mensen eerder wat je bedoelt en zullen ze meer vertrouwen in de resultaten van je algoritme hebben. 3.6 Gegevensstructuren: Scalair, Vector en Matrix In deze paragrafen leggen we de begrippen scalair, vector en matrix uit, en waar je ze in algoritmen voor kunt gebruiken. Verder behandelen we hoe je met behulp van indices gegevens uit een vector of een matrix haalt of er weer in stopt. Eerder in dit hoofdstuk hebben we kennisgemaakt met controle-structuren (Als Dan, Wanneer, Voor Tot, Herhaal Totdat, Zolang Doe). Deze worden in een programma gebruikt om de reeks van bewerkingen op de gegevens (input) te kunnen sturen (voorwaardelijk uitvoeren, of herhalen). Een basale eigenschap van gegevens waar we al mee hebben kennisgemaakt is het type. Pseudotaal kent twee gegevens
pagina 3.6-2 typen: het type teken (bijv. A ), ook wel aangeduid als het type woord (bijv. Hallo ), en het type getal (bijv. 7; 4.14; 3E7). Daarnaast kunnen de gegevens ook een structuur hebben. We kennen drie verschillende structuren: scalair, vector, en matrix Gegevens-structuren hebben twee eigenschappen waarmee we ze van elkaar kunnen onderscheiden: dimensie en lengte. 3.6.1 Scalair De eerste meest eenvoudige structuur is het scalair. Een enkelvoudig teken, A, of getal, 143, heeft een scalaire structuur. De dimensie van een scalair is gelijk aan nul. De lengte van een scalair is leeg! We kunnen gebruik maken van de modules DIMENSIE en LENGTE (Hoofdstuk 11) om van een variabele te kunnen kontroleren welke structuur hij heeft. Bijvoorbeeld:? PootLengte WORDT 12 LENGTE PootLengte (We zien niets op het scherm!!! De lengte van een scalair is immers leeg). 1 EMPTY IDENTIEK LENGTE PootLengte Juist, de lengte van de variabele PootLengte is gelijk aan het resultaat van de module EMPTY (Hoofdstuk 11), en dus heeft PootLengte de structuur van een scalair. De dimensie van een scalair is nul. We kunnen de dimensie van een variabele controleren met behulp van de module DIMENSIE (Hoofdstuk 11). Bijvoorbeeld: 0 DIMENSIE PootLengte Juist, de dimensie van de variabele PootLengte is nul, en dus heeft PootLengte de structuur van een scalair!
3.6.2 Vector pagina 3.6-3 In een variabele kunnen we meer dan één waarde zetten. Zo'n variabele met een reeks getallen noemen we een vector. Een vector is een handige opslagmethode voor gegevens die bij elkaar horen. We kunnen hier een aantal veel gebruikte bewerkingen op uitvoeren (denk bv. aan het gemiddelde berekenen, de hoogste en laagste waarde selecteren, etc) In het algemeen zijn de gegevens waarop een algoritme werkt geen willekeurige verzameling van onsamenhangende delen, maar bestaan uit brokken informatie die op de een of andere manier met elkaar in verband staan. De Rij of Vector is een nuttige structuur voor gegevens die achter elkaar moeten worden bewerkt. In algemene vorm ziet dat er zo uit. Begin aan het begin van de Rij Zolang het einde van de Rij niet is bereikt Doe een bewerking op het volgende element Eindzolang Stel bijvoorbeeld dat we van verschillende zeehonden het percentage vet gemeten hebben. We hebben voor 12 zeehonden de volgende resultaten gevonden: 25, 40, 30, 25, 60, 55, 30, 25, 30, 28, 34, 45. Deze resultaten kunnen buiten een programma in een variabele Vet worden gezet met de volgende opdracht: Vet WORDT 25,40,30,25,60,55,30,25,30,28,34,45. In een programma gaat dat met de opdracht: Vet := 25,40,30,25,60,55,30,25,30,28,34,45. Met het volgende algoritme berekenen we het minimum, maximum, gemiddelde, en de range (het verschil tussen maximum en minimum) van Vet. We gebruiken voor enkele bewerkingen de beschrijving en niet de pseudotaal. [0] PROGRAM SimpelStat [1] {Enkele beschrijvers van een reeks getallen} [2] Vet:= 25,40,30,25,60,55,30,25,30,28,34,45 [3] Max:= hoogste van Vet [4] SCHRIJF Max [5] Min:= laagste van Vet [6] SCHRIJF Min [7] Gemiddelde := (som van Vet) GEDEEELD DOOR (aantal elementen Vet) [8] SCHRIJF Gemiddelde [9] Range := Max MIN Min [10] SCHRIJF Range Hoofdstuk 3.6
pagina 3.6-4 Vet, zien we in regel 2, bestaat uit een reeks getallen. De variabele Vet heeft dus de structuur van een vector. Een vector heeft als eigenschap dat zijn dimensie gelijk is aan 1. Dus we kunnen van Vet vragen: DIMENSIE Vet 1 en we zien als antwoord 1, waaruit we kunnen concluderen dat de variabele Vet inderdaad de structuur heeft van een vector. Hoe zit het dan met die andere eigenschap, lengte. Die kunnen we opvragen door: 12 LENGTE Vet Dit betekent dat er 12 getallen in de vector Vet zitten. In regel [7] van het bovenstaande algoritme kunnen we van die eigenschap gebruik maken bij het uitrekenen van de gemiddelde waarde van de variabele Vet. In het algoritme kunnen we in regel [7] de opdracht aantal elementen dus laten uitvoeren door de module LENGTE, en het berekenen van de som door de module SOM (zie Hoofdstuk 11). Regel [7] verandert dan in: 3.6.3 Matrix Gemiddelde := (SOM Vet) GEDEELD DOOR LENGTE Vet Het komt wel eens voor dat we meerdere groepen van gegevens hebben. Stel bijvoorbeeld dat we van een aantal zeehonden het gewicht, de lengte en de leeftijd hebben bepaald. Als we vier zeehonden hebben opgemeten hebben we in totaal 12 gegevens. In een tabel zouden we dat als volgt weergeven: Gewicht Lengte Leeftijd Zeehond 1 5 30 2 Zeehond 2 6 40 3 Zeehond 3 3 35 2 Zeehond 4 5 28 2 Een matrix is een opslagmethode voor een tabel. Een matrix bestaat uit een aantal rijen gegevens onder elkaar. De bovenstaande tabel wordt dan:
5 30 2 6 40 3 3 35 2 5 28 2 Dit is een matrix met 4 rijen en 3 kolommen. pagina 3.6-5 In pseudotaal maken we deze matrix met: Zeehond := (4,3) MATRIX 5,30,2,6,40,3,3,35,2,5,28,2. In het algemeen: matrix := rijen kolommen MATRIX getallen De getallen moeten wel in de volgorde van Rij na Rij worden aangeboden. De dimensie van een matrix is (tenminste) gelijk aan 2; een matrix heeft namelijk rijen en kolommen. Matrices kunnen nog komplekser van opbouw zijn. Een drie dimensionale matrix, bijvoorbeeld, heeft de vorm van een kubus, met lagen van rijen en kolommen. Met meer dan 3 dimensies kunnen we ze ook maken, alleen is het dan veel lastiger om ons daar een ruimtelijke voorstelling van te maken. De dimensie van een variabele kunnen we controleren door: 2 DIMENSIE Zeehond Zeehond heeft dus twee dimensies, een matrix met rijen en kolommen. Uit hoeveel rijen en kolommen de variabele Zeehond is opgebouwd kunnen we te weten komen door te vragen naar die andere eigenschap van gegevens-structuren, de lengte. Dus: 4 3 3.6.4 Index LENGTE Zeehond Juist, Zeehond is dus een matrix met 4 rijen en drie kolommen. Een index gebruiken we om te kunnen selecteren uit een serie gegevens. Een index bestaat uit een getal of een reeks getallen. Op het begrip index is dus het begrip gegevens-structuur van toepassing, net zoals dat voor alle reeksen getallen het geval is. Hoofdstuk 3.6
pagina 3.6-6 Met andere woorden een index heeft de vorm van een scalair, of een vector, of van een matrix. 3.6.4.1 Scalair Een index is op een scalair niet van toepassing. Dat komt een scalair per definitie een lengte heeft die leeg is. En uit een leeg aantal elementen kan niets geelecteerd worden. 3.6.4.2 Vector Als we één bepaalde waarde uit een vector willen halen, moeten we een manier vinden om de waarden uit een vector te onderscheiden. We doen dit met indices. Een index is het getal dat aangeeft op de hoeveelste plaats een waarde in een vector staat. Stel we hebben de volgende vector: A := 3 5 7 9 11. Als we de derde waarde willen hebben geven we dat aan met: A[3] Dit geeft dus de waarde 7 als antwoord. In het algemeen geldt dat we het indexgetal (tussen vierkante haken) gebruiken om een getal uit een vector te halen: vector [indexgetal]. In het SimpelStat algoritme op de vorige pagina zouden we de opdracht laagste van kunnen vervangen door gebruik te maken van de in de pseudo-taal aanwezige module SORTEER. Wanneer we bedenken dat SORTEER de elementen in een rij sorteert van laag naar hoog, dan komt het eerste element uit de gesorteerde rij vanzelfsprekend overeen met de laagste waarde. We kunnen nu gebruik maken van onze net verworven kennis over indices door die eerste waarde uit de gesorteerde vector te pakken. De volledige opdracht luidt dan: Min := (SORTEER Vet)[1] Op een vergelijkbare manier kunnen we ook de hoogste waarde vinden. Die komt overeen met het laatste element uit de gesorteerde rij. Het laatste element staat op een plaats waarvan de index overeenkomt met de lengte van de rij. We kunnen de opdracht in de pseudo-taal dus schrijven als: Max := (SORTEER Vet)[LENGTE Vet]
pagina 3.6-7 Om te vermijden dat we de module SORTEER twee maal moeten gebruiken kunnen we de gesorteerde rij eerst opbergen in een variabele. Samengevat zien de opdrachten er dan als volgt uit: Svet := SORTEER Vet Min := SVet[1] Max := SVet[LENGTE Vet] En aangezien we de lengte van de vector in regel [7] nog een keer nodig hebben kunnen we die lengte beter toekennen aan een variabele. LenVet := LENGTE Vet Het vernieuwde algoritme ziet er nu als volgt uit: [0] PROGRAM SimpelStat [1] {Enkele beschrijvers van een reeks getallen} [2] Vet:= 25,40,30,25,60,55,30,25,30,28,34,45 [3] LenVet := LENGTE Vet [4] Svet := SORTEER Vet [5] Max:= Svet[LenVet] [6] SCHRIJF Max [7] Min:= Svet[1] [8] SCHRIJF Min [9] Gemiddelde := (SOM Vet) GEDEEELD DOOR LenVet [10] SCHRIJF Gemiddelde [11] Range := Max MIN Min [12] SCHRIJF Range We kunnen indices ook gebruiken om elementen in een vector te vervangen door andere. We hebben bijvoorbeeld een fout gemaakt bij het invoeren van de waarden in de variabele Vet. Het derde element had de waarde 35 moeten hebben en niet 30. We kunnen die 30 op een simpele manier vervangen door middel van de opdracht: Vet[3] := 35 We kunnen ook meerdere elementen tegelijk vervangen; bijvoorbeeld zo: Vet[3,5,9] := 35,29,65 We moeten dus net zoveel waarden aanbieden (rechts van de :=) als er plaatsen in de index worden aangeduid (hier dus 3). Een uitzondering wordt gevormd door het geval waarin we op verschillende plaatsen hetzelfde getal neer willen zetten. Bijvoorbeeld: Vet[2,4,6,7] := 21 Het 2e, 4e, 6e, en 7e element in Vet wordt nu gelijk aan 21. Hoofdstuk 3.6
pagina 3.6-8 Wanneer we niet bij elkaar passende hoeveelheden getallen en indices hebben, treedt er een lengte fout op. Bijvoorbeeld: Vet[3,6] := 21,35,42 geeft een lengte fout omdat we teveel getallen (3) op te weinig plaatsen (2) willen persen. Dat gaat niet. Andersom gaat ook niet. Bijvoorbeeld: Vet[3,5,6,7] := 21,42 geeft ook een lengte fout omdat we nu te weinig getallen aanbieden in verhouding tot het aantal in de index aangeduide plaatsen. Een vector kan een lengte hebben die gelijk is aan 1. Dan zit er maar 1 element in die vector. Let op dat een vector met slechts 1 element iets heel anders is dan een scalair. De dimensie van zo n vector is toch 1 (scalair 0), maar zijn lengte is ook slechts 1 (scalair heeft een lege lengte). We kunnen een vector met slechts 1 element als volgt maken: Vet :=,32 Let op de komma. Die is hier cruciaal, want die zorgt er voor dat het getal 32 een bepaalde dimensie krijgt, nl 1, en een lengte 1. Het kan dus ook op de volgende manier: Vet := 32 Vet is nu een scalair: het getal 32 De volgende expressie maakt van Vet een vector: Vet :=,Vet Dus weer die komma, met als functie het veranderen van de dimensie. De komma heeft ook de funktie om verschillende vectoren aan elkaar te plakken. Als we over verschillende vectoren met vet gegevens beschikken, bijvoorbeeld met de namen Vet1, Vet2, Vet3, dan kunnen we de waarden in die vectoren vector op de volgende manier verzamelen in een totaal: 3.6.4.3 Matrix VetTot := Vet1, Vet2, Vet3 Om een waarde uit een matrix te selecteren gebruiken we ook indices. Hierbij moeten we echter aangeven op welke rij en in
pagina 3.6-9 welke kolom de waarde staat. Stel dat we de volgende matrix B hebben: 2 3 5 3 2 3 4 5 5 6 7 2 Als we het derde getal van de tweede rij willen hebben, vragen we dat op met B[2;3]. Dit geeft 4 als resultaat. Algemeen: een waarde uit een matrix vragen we op met: matrix[rij;kolom] Voorbeeld: Eieren in vectoren en matrices De bioloog die we in een eerdere opgave kievietsnesten hebben laten tellen, heeft ook van elk nest opgeschreven hoeveel eieren erin zitten. Met deze gegevens kan het effect van het maai-regime van de weilanden bekeken worden door het aantal eieren te vergelijken met het aantal groot gebrachte jongen. Om deze gegevens op te slaan en later makkelijk weer op te vragen, kan men gebruik maken van een vector. In een vector staan de gegevens in een rij achter elkaar. Om de gegevens snel terug te vinden heeft de bioloog de nesten allemaal een nummer gegeven en op een kaart aangetekend waar de nesten gevonden zijn. Weiland van boer Janssen: *8 *6 *7 *4 *1 *2 *3 *5 startpunt van zoektocht De vector wordt nu: Janssen := 2 3 1 3 4 2 1 3 (d.w.z. in nest 1 zaten 2 eieren, in nest 2 zaten er 3, enz.) Als we bijvoorbeeld willen weten hoeveel eieren in nest 7 zaten vragen we dat op met: Janssen[7] en dit geeft als resultaat: 1. Als de bioloog meerdere velden bekeken heeft (bv. van verschillende boeren) kan alles in 1 vector achter elkaar gezet worden, maar nu is een matrix handiger. In deze matrix kunnen we dan voor elk veld een rij gebruiken. We krijgen dan bijvoorbeeld de volgende matrix (4 velden bekeken): Kieviet = 2 3 1 3 4 2 1 3 2 3 3 1 1 0 0 0 1 2 4 3 1 2 3 0 4 4 1 3 4 3 4 2 Hoofdstuk 3.6
pagina 3.6-10 Omdat er niet in elk veld evenveel nesten zijn, worden de rijen (komt overeen met de velden) die minder dan 8 nesten hadden aangevuld met nullen. Als we nu willen weten hoeveel eieren in het 7de nest van het 1e veld zaten, kunnen we dat opvragen met: Kieviet[1;7]. Dit geeft het zevende getal op de eerste rij (en is dus 1). Het zesde nest van de tweede boer had Kieviet[2;6] (is dus 0) eieren en bestond (volgens onze methode) dus niet. Het zou natuurlijk ook kunnen voorkomen dat er in een nest 0 eieren zitten, in dat geval zouden we met deze methode zeggen dat het nest niet bestaat. In de volgende paragraaf wordt een andere opslagmethode behandeld waar dit probleem mee opgelost is.