Algoritmen René Dohmen 1 Algoritmen René Dohmen eerste versie 2 november 2010 deze versie 1 september 2019 www.li-do.nl > informatica [email protected] 2 Inhoudsopgave 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Inleiding Appeltaart Domdom Paasdatum Kaarten schudden Mijnenveger Liftplanning Doolhoven Cellulaire automaten De uitgang vinden in een doolhof Levenshtein Meer over kaarten schudden Priemgetallen Traveling Salesman Worteltrekken Eerlijk delen Quicksort Cellulaire automaten voor speelwerelden 18 19 vuur en vlam De som is... Bijlage: 'wie het eerst drukt' Uitwerkingen 3 Anyway, Fluffy the rabbit just wanted to say “Hello”. 4 Inleiding Algoritmen zijn als recepten. Ze beschrijven precies wat je moet doen om iets voor elkaar te krijgen. Het gaat mis als je de stappen niet in de juiste volgorde uitvoert. Meestal beschrijft een stap wat je moet doen. Soms is het een controle, die bepaalt welke stap daarna wordt uitgevoerd. https://nl.wikipedia.org/wiki/Algoritme Een mens voert een recept niet altijd even strikt uit. Als een ingrediënt niet voorhanden is, kan er soms een vervanging gevonden worden. Als we tijdens een autorit merken dat een weg afgesloten is, zetten we de autonavigatie (TomTom) even uit en zoeken we zelf een manier om om de afsluiting heen te rijden.. Voor computers is dat anders. Je moet ze precies vertellen wat er gedaan moet worden om een bepaald resultaat te bereiken. Als je wil dat ze met uitzonderingen rekening houden, moet je precies vertellen wat de uitzonderingen zijn en hoe ze daar mee om moeten gaan. In dit boekje wordt een aantal algoritmen besproken. Het gaat er vooral om dat je leert welke exacte regels de computer moet volgen om een probleem op te lossen. Sommige algoritmen zullen helemaal nieuw voor je zijn. 5 Tijdschema Hieronder vind je een tijdschema dat je kunt volgen om dit lespakket door te werken les Paragraaf opgave Aantal opgaven per les 1 1 1.1 3 2 2.1 t/m 2.2 2 3 3.1 tm 3.2 2 4 4.1 t/m 4.3 7 5 5.1 t/m 5.4 4 Inclusief voorbespreken raamwerk mijnenveger Opg5.5 1 Het nabespreken van het programma mijnenveger kost een hele les extra 2 3 (extra) 4 6 6.1 t/m 6.7 7 5 7 7.1 t/m 7.3 4 6 8 8.1 tm 8.9 9 7 9 9.1 t/m 9.4 4 8 10 10.1 t/m 10.4 4 9 11+12 11.1, 13.1 t/m 12.7 8 10 13 13.1 t/m 13.4 4 11 14 14.1 t/m 14.3 3 12 15 13 16 14 opmerkingen Nabespreking Inclusief een uitgebreide bespreking van de mijnenveger zijn er dus 13 à 14 lessen nodig. Met een aantal opdrachten kun je micropunten: • • • • • • • • • 3.1 nsd 4.1 video-opname 4.2 nsd 4.5 programma in small basic 5.6 nsd 5.7 programma in small basic 6.2 scan van een zelfgemaakt doolhof 12.1 nsd 13.1 nsd 6 1 Appeltaart maken doelen Voorbeelden uit het dagelijks leven kunnen geven van recepten. Weten dat de volgorde van de stappen belangrijk is. Weten dat een computer alleen de voorgeschreven stappen uitvoert Als je appeltaart wil maken heb je een lijst met benodigdheden en ingrediënten nodig. Hieronder zie je een voorbeeld van zo een lijst. Benodigdheden ingrediënten een oven beslagkom mixer springvorm ca. 23 cm schilmes 1 pak appeltaartmix 125g boter 3 eieren 750g Elstar of Jonagold appels kaneel 2 zakjes vanillesuiker citroensap rozijnen Ga ervan uit dat je alleen de benodigdheden en ingrediënten hebt die in de lijst staan. Opdracht 1.1 Probeer aan de hand van deze lijstjes een mogelijk recept op te stellen waarmee appeltaart te bereiden is. 7 2 Domdom doelen Voorbeelden uit het dagelijks leven kunnen geven van recepten. Weten dat de volgorde van de stappen belangrijk is. Weten dat een computer alleen de voorgeschreven stappen uitvoert In mijn auto zit een kastje met een heel goede kaart van de wereld. Door middel van een GPS kan het apparaat bepalen waar ik ben als ik vertrek. Ook kan ik gemakkelijk de bestemming invoeren. Als ik dat gedaan heb, heeft het kastje twee punten op de aardbol die door middel van wegen verbonden zijn. De weg tussen begin- en eindpunt bepaalt het kastje met een eenvoudig algoritme: Beschrijving van het algoritme: DomDom Stap Beschrijving 1 Bepaal het beginpunt B 2 Bepaal het eindpunt E 3 Kies een willekeurige richting 4 HuidigPunt = B 5 Controleer of het eindpunt bereikt is (HuidigPunt=E) Zo ja, ga naar stap 8 6 Rijd naar het volgend punt (in de gekozen richting) P → HuidigPunt = P Controleer of het HuidigePunt een kruispunt is zo ja: kies een willekeurige richting 7 Ga naar stap 5 8 Einde 8 Opdracht 2.1 Maak een NSD voor het algoritme DomDom. Opdracht 2.2 LET OP: Je moet deze opdracht met z'n tweeën uitvoeren. Maak met Google Maps een screenshot van een deel van je woonwijk Label een beginpunt met de letter B en een eindpunt met de letter E. Er moeten minstens 3 kruispunten tussen deze punten zitten, volgens een – op het oog rechtstreekse weg. Doorloop nu het algoritme. Telkens als je op een kruispunt aankomt label je dat kruispunt met oplopende cijfers. Als je ooit terugkomt op een kruispunt krijgt dat kruispunt een extra nummer. Bijvoorbeeld 2 wordt 2,17 en daarna bijvoorbeeld 2,17,31 Als je na 50 stappen nog niet bij het eindpunt bent moet je stoppen. Tel ondertussen dat je het algoritme uitvoert: hoe vaak je op een kruispunt omkeert hoe vaak het voorkwam dat je 2, 3, 4, … keer over hetzelfde wegdeel reed (een wegdeel is het deel van een weg tussen twee kruisingen). Je kunt een willekeurige weg kiezen door met een dobbelsteen te gooien. Op internet is een dobbelsteen te vinden die ook meer of minder dan zes vlakken heeft. Bij een kruising met vier wegen kies je voor een dobbelsteen met vier vlakken. 9 http://dobbelsteen.virtuworld.net/ De één gooit de dobbelsteen en de andere kiest aan de hand van het gegooide getal de weg die ingeslagen wordt. P.S. Als je een doodlopende weg inloopt, moet je aan het eind natuurlijk omkeren. Als je het het algoritme exacte volgt moet je wachten tot je een 1 gooit. Maar op de genoemde website kun je geen dobbelsteen met 1 zijde kiezen (logisch!). Opdracht 2.2 Schrijf een programma in Small Basic waarmee je DomDom kunt simuleren in een rooster. Ga uit van woonwijk waar straten alleen in een rooster voorkomen, zoals in Manhattan (New York). Neem (0,0) als startpunt. Kies een bepaalde afstand (bijvoorbeeld 4). Kies een willekeurig roosterpunt dat 4 stappen van (0,0) verwijderd is (dat kan door met een dobbelsteen te gooien. Loop nu op de domdom methode door het rooster. Hoeveel stappen duurt het om bij het eindpunt aan te komen? Probeer voor verschillende afstanden (0,1,2,3,4) een gemiddeld aantal stappen te bepalen door het programma vaak te runnen. 10 11 3 Pasen In 1800 bedacht de Duitse geleerde Carl Friedrich Gauss een algoritme om de Paasdatum te berekenen. Pasen valt altijd op de eerste zondag na de eerste volle maan na het begin van de lente. Allereerst moet je natuurlijk het jaar kiezen waarvoor je de Paasdatum wilt berekenen. In de stappen daarna komt regelmatig de term 'geheeldelen' voor. Daarmee wordt bedoeld dat je na een deling alleen de gehele waarde gebruikt. Voorbeeld geheeldeel 11 door 2 → 11/2=5,5 → afkappen na de komma → 5 In een programmeertaal doe je dat als volgt: Small Basic X=11/2 X=Math.Floor(X) Lazarus // waarbij X een integer variabele moet zijn. X:=11 div 2; Let op: je moet dit algoritme tijdens een toets kunnen toepassen. Je hoeft het dus niet van buiten te kennen. Beschrijving van het algoritme: Paasdatum We nemen als jaar 1991. stap 1: Bepaal het gulden getal Deel het jaartal door 19, neem de rest en tel er 1 bij op (zoals Dionysius). Noem dit getal G. Voor het jaar 1991 geldt G = 16. stap 2: Bepaal het eeuwtal Geheeldeel het jaartal door 100 en tel daar 1 bij op. Noem dit getal C. Voor het jaar 1991 geldt C = 20. stap 3: Corrigeer vervolgens voor jaren die geen schrikkeljaar zijn Vermenigvuldig C met 3, geheeldeel het resultaat door 4 en trek er 12 van af. Noem dit getal X. Voor de twintigste en eenentwintigste eeuw geldt X = 3. stap 4: Maancorrectie Neem 8 maal C, tel er 5 bij op, geheeldeel dit door 25 en trek er 5 vanaf. Noem dit getal Y. Voor de twintigste en eenentwintigste eeuw geldt: Y = 1. 12 stap 5: Zoek de zondag Vermenigvuldig het jaartal met 5, geheeldeel de uitkomst door 4, trek er X en 10 vanaf, en noem dit getal Z. Voor 1991 geldt: Z = 2475. stap 6: Bepaal de epacta 11 maal G + 20 + Y. Trek daarvan X af, geheeldeel het resultaat door 30 en noem de rest E. Als E gelijk is aan 24, of als E gelijk is aan 25 en het gulden getal is groter dan 11, tel dan 1 bij E op. De Epacta voor 1991 is 14. stap 7: Bepaal de volle maan Trek E af van 44. Noem dit getal N. Als N kleiner is dan 21, tel er dan 30 bij op. Voor 1991 geldt: N = 30 stap 8: Nu door naar zondag Tel Z en N op. Geheeldeel het resultaat door 7 en trek de rest af van N+7. Noem dit getal P. Voor 1991 geldt: P = 31. Stap 9: Paasdatum Als P groter is dan 31, trek er dan 31 vanaf, en de paasdatum valt in April. Anders valt de paasdag P in Maart. Zo wordt voor 1991 gevonden 31 maart. Opdracht 3.1 Bereken op deze manier met pen, papier en een rekenmachine de paasdatum in 2016. Schrijf op wat de waarden voor G, C, X, Y, Z, E, N en P zijn. Op welke datum valt Pasen in 2014? Opdracht 3.2 Schrijf een programma in Small Basic waarmee je de paasdatum kunt berekenen met het algoritme van Gauss. Je kunt je afvragen hoe het genie Gauss op het idee gekomen is voor deze berekening. Je kunt daarover meer vinden op de volgende webpagina (er staat ook een lokale versie van dit bestand op de website). Je hoeft deze uitleg niet te kennen voor de toets. http://www.staff.science.uu.nl/~gent0113/hovo/downloads/text1_08b.pdf 13 4 Kaarten schudden In een spel kaarten zitten 52 kaarten. In een computer kun je die eenvoudigheidshalve gecodeerd met de nummers 1 tot en met 52. Deze nummers zijn in een array opgeslagen. Bij de start van het programma staan de nummers in oplopende volgorde gesorteerd in de rij. Small Basic rij[1]=1 rij[2]=2 rij[3]=3 etc. Lazarus rij[1]:=1; rij[2]:=2; rij[3]:=3; etc. Je kunt de rij gemakkelijk met een for-lus vullen: Small Basic for teller=1 to 52 rij[teller]=teller endfor Lazarus for teller:=1 to 52 do begin rij[teller]:=teller; end; Opdracht 4.1 Ontwerp een algoritme dat de nummers in de rij door elkaar gooit. Noem dit algoritme schudden. Verwerk het tot een NSD Opdracht 4.2 Verwerk je algoritme schudden tot een programma in Small Basic. Opdracht 4.3 Vul de code aan met een test die bepaalt hoeveel getallen na het schudden niet op een andere plek in de rij terecht gekomen zijn. Komt het vaak voor dat alle kaarten van hun plaatst geschud zijn als je 100x verwisseld? 14 Ga ervan uit dat er 52 kaarten in het spel zijn (dat is een kaartspel zonder jokers). 15 5 Mijnenveger Mijnenveger is een populair spel dat gratis bij Windows geleverd wordt. In een veld worden willekeurig mijnen geplaatst. Als je op een cel klikt bekijkt het programma of er een mijn ligt. Je bent af als dat zo is. Als er geen mijn ligt klapt het programma soms meerdere cellen open. In de afbeelding hieronder kun je die gebieden herkennen aan de grijze vakjes zonder getal. Hoe bepaalt de computer welke cellen er opengeklapt moeten worden? De gebruiker klikt op een cel van het speelveld. De cel heeft een x- en een y-coordinaat. De y-coördinaat wordt groter als je omlaag gaat, dat is het omgekeerd van een rooster in de wiskunde. Het algoritme CheckClick beschrijft wat er na de klik moet gebeuren. Beschrijving van het Algoritme CheckClick(X,Y) Stap Beschrijving 1 maak een lege lijst L 2 controleer of op cel (X,Y) een mijn ligt ja → einde spel nee → voeg deze cel (x,y) aan de lijst L toe 3 neem een cel (a,b) uit de lijst → Klap deze cel(a,b) open 4 tel het aantal mijnen rond cel(a,b) als het aantal nul is → klap alle aangrenzende hokjes open en voeg ze aan de lijst toe (*). als het aantal groter dan nul is schrijven we dat aantal in de cel. Met de aangrenzende cellen doen we niets. (*) Let op: als de cel al in de lijst stond voegen we het niet nóg eens toe. Cellen waar al een getal in staat worden ook niet aan de lijst toegevoegd. 5 ga naar stap 3 zolang de lijst niet leeg is 16 Volgorde waarin de buren van cel(a,b) worden gecontroleerd We spreken af dat de cellen rond (a,b) in de volgende volgorde aan de rij toegevoegd worden. 1 2 3 4 (a,b) 5 6 7 8 Aantal buren van cel(a,b) Als de cel (a,b) ergens aan de rand of in de hoek van het speelveld staat wordt maar een deel van deze cellen toegevoegd. Dit kunnen 3,5 of 8 cellen zijn. In de hoeken 3, aan de randen 5 en in het midden 8. Bijvoorbeeld (a,b) is de cel linksboven: alleen 5, 7 en 8 worden toegevoegd In het echte programma worden de routines StepBox, StepXY en CountBombs gebruikt 17 We gaan dit nu bekijken in het volgende voorbeeld. voorbeeld Bekijk het onderstaande speelveld. De cellen in het speelveld hebben (x,y)coördinaten. Als je naar beneden gaat wordt de y-coördinaat groter. Dus (1,1) is linksboven, (3,3) is rechts beneden. De B staat voor een bom. Er is nog geen enkel veld aangeklikt. De speler ziet natuurlijk niet waar bommen staan. Stel dat de speler op (1,1) klikt. (1,1) wordt dus in de lege lijst gezet. In de onderstaande afbeelding is dat aangegeven met een L. Stap 1 Stap 2 L 0 L L L B B B B De lijst bevat (1,1). De lijst bevat (2,1), (1,2) en (2,2). (1,1) wordt opengeklapt en uit de lijst gehaald. Er staat 0 bommen rond (1,1). De aangrenzende cellen van (1,1) worden in de lijst gezet: (2,1), (1,2) en (2,2). We nemen (2,1) uit de lijst. De aangrenzende cellen van (2,1) bevatten 1 bom, daarom markeren we (2,1) met het getal 1. Stap 3 Stap 4 0 1 0 L L L 1 2 B B B B De lijst bevat (1,2) en (2,2). De lijst bevat (2,2). We nemen (1,2) uit de lijst De aangrenzende cellen van (1,2) bevatten 1 bom, daarom markeren we (1,2) met het getal 1. We nemen (2,2) uit de lijst. De aangrenzende cellen van (2,2) bevatten 2 bommen, daarom markeren we (2,2) met het getal 2. De lijst is nu leeg. We kunnen daarom stoppen. Dit voorbeeld was eenvoudig om dat alleen bij het verwerken van de eerste cel nieuwe cellen toegevoegd werden. 18 Opdracht 5.1 Speel met z'n tweeën mijnenveger op papier door het algoritme toe te passen. Gebruik ruitjespapier. Speler 1 neemt een leeg veld van 5x5 en zet 5 mijnen in het veld, zó dat er minstens 1 cel is waar geen mijnen om heen staan. De cel linksboven is (1,1). De cel rechtsonder is (5,5). Speler 2 noemt een coördinaat (x,y) tot van elke cel bekend of er een mijn staat. Speler 1 pas het CheckClick algoritme toe op de genoemde cel. Speler 2 controleert of het algoritme goed toegepast wordt. Als je een video-opname van je spel opneemt en instuurt levert dat een micropunt op. Opdracht 5.2 Verwerk het algoritme van mijnenveger in een NSD. Vraag je eerst af welk soort lus je gaat gebruiken. Opdracht 5.3 Bekijk het onderstaande speelveld. De cellen in het speelveld hebben (x,y)-coördinaten, waarbij de y-coördinaat groter wordt als je omlaag gaat (dat is het omgekeerde van wat je tijdens de wiskunde les leert). Dus (1,1) is linksboven, (3,3) is rechts beneden. De B staat voor een bom. Er is nog geen enkel veld aangeklikt. De speler ziet natuurlijk niet waar bommen staan. Stel dat de speler op (1,1) klikt. (1,1) wordt dus in de lege lijst gezet. B Werk stap voor stap uit hoe het algoritme CheckClick de juiste cellen openklapt. 19 Opdracht 5.4 Bekijk het onderstaande speelveld. De cellen in het speelveld hebben (x,y)-coördinaten, waarbij de y-coördinaat groter wordt als je omlaag gaat (dat is het omgekeerde van wat je tijdens de wiskunde les leert). Dus (1,1) is linksboven, (5,5) is rechts beneden. De B staat voor een bom. Er is nog geen enkel veld aangeklikt. De speler ziet natuurlijk niet waar bommen staan. Stel dat de speler op (1,1) klikt. (1,1) wordt dus in de lege lijst gezet. B B B B B Pas het CheckClick algoritme toe op de genoemde cel. Opdracht 5.5 Schrijf het programma Mijnenveger in Small Basic. Maak het programma voor een veld van 10x10. Zet in dit veld tussen de 5 en 15 bommen. Bron: http://www.techuser.net/minecascade.html 20 6 Liftplanning Een lift moet zo efficiënt mogelijk mensen ophalen en afleveren op verschillende etages. Laten we om te beginnen eens kijken naar één lift die 5 etages moet bedienen. Bij elke etage is een knop waarmee de lift opgeroepen kan worden. Pas als je in de lift bent kun je de bestemming aangeven door op het nummer van die etage te drukken. Deze verzoeken worden in een buffer bewaard. De begane grond krijgt altijd nummer 0. Als er 5 etages zijn, heeft de vijfde etage dus nummer 5. Vijfde etage 5 4 3 2 Eerste etage 1 Begane grond 0 Opdracht 6.0 Lees het artikel 'wie het eerst drukt, het eerst de lift' in de bijlage. Beschrijving van het Algoritme: Shortest seek first Stap Beschrijving 1 Zolang er een etage in de buffer staat Kies de dichtstbijzijnde verdieping. Daarbij wisselt de bewegingsrichting indien nodig - steeds om. Het maakt dus niet uit of de dichtstbijzijnde etage boven of onder de lift ligt, het algoritme kiest steeds de dichtstbijzijnde. Het nadeel van Shortest Seek First is dat bij een druk bezette lift sommige verzoeken zeer lang (in theorie eeuwig) in de buffer kunnen blijven staan. Als twee etages evenver van de lift liggen, kies je er willekeurig een. 21 Voorbeeld Stel dat een flat 10 etages heeft en dat de volgende verzoeken in de buffer staan: 1, 4, 9. De lift staat aan het begin op etage 5. De lift staat op etage 5. De dichtstbijzijnde etage is 4. In de buffer staan 1, 9. De lift staat op etage 4. De dichtstbijzijnde etage is 1. In de buffer staat 9. De lift staat op etage 1. De dichtstbijzijnde etage is 9. De buffer is leeg. De lift staat op etage 9. De blijft op etage 9, want de buffer is leeg. Opdracht 6.1 Stel dat een flat 100 etages heeft en dat de volgende verzoeken in de buffer staan: 10,35,40,42,45,49,50,51,65. De lift staat aan het begin op etage 50. In welke volgorde worden de etages afgehandeld als er geen nieuwe verzoeken in de buffer geplaatst worden? Opdracht 6.2 In de tekst staat dat het in theorie eeuwig kan duren eer een etage aan de beurt komt. Bedenk een situatie waarin dat gebeurt. Het tweede liftalgoritme heet SCAN, dat bekijken we nu. Beschrijving van het Algoritme SCAN Als de lift in bedrijf is komen er nieuwe verzoeken binnen om bepaalde etages te bezoeken. Dat kan door op het knopje bij de deur te drukken of door in de lift een bestemming te kiezen. De verzoeken worden afgehandeld in de richting waarin de lift op dat moment beweegt. De lift beweegt dus tussen de onderste en de bovenste etage op en neer. Onderweg 22 wordt alleen gestopt als de etage in de buffer staat. Dus: als de lift omhoog beweegt worden eerst de verzoeken boven de huidige etage verwerkt. als de lift omlaag beweegt worden eerst de verzoeken onder de huidige etage verwerkt. als de lift stil staat (omdat alle eerdere verzoeken afgehandeld waren) beweegt de lift meteen in de richting van het eerste verzoek. de lift beweegt altijd door tot de onderste en bovenste etage, ook als daar geen verzoeken voor in de buffer stonden. Pas daar keert de lift om. het kan natuurlijk zijn dat er meer verzoeken in de buffer staan te wachten. Voorbeeld 1 opgave 0 Een flat heeft 10 etages en één lift. De lift bedient de etages met het SCANalgoritme. De volgende etages staan in de buffer: 1,2,9. De lift bevindt zich op de 8e etage en beweegt omhoog. In welke volgorde worden de etages bediend? uitwerking In de buffer staan 1,2 9 De lift beweegt omhoog. De eerste etage die wordt bediend is dus 9. Daarna beweegt de lift door naar de bovenste etage:10. De deuren worden hier niet geopend. Dan gaat de lift naar beneden en worden de etage in volgorde bediend. De lift stopt als de buffer leeg is. 0 In de onderstaande tabel is samengevat in welke volgorde de lift op etage stopt. Alleen de etages waar de lift stopt staan in de tabel. De asterisk bij etage 10 betekent dat de liftdeuren hier niet geopend worden. etage 1 2 9 10 volgorde 4 3 1 2* Voorbeeld 2 In dit voorbeeld bekijken we langs hoeveel etages SCAN gemiddeld moet bewegen om een aantal verzoeken al te handelen. We gaan uit van een flat met 100 etages. In de buffer staan de volgende verzoeken: 10,20,50,75,100. De lift begint op etage 35. De lift beweegt aan het begin omhoog. 23 We zetten de verzoeken in oplopende volgorde: 10, 20, 50, 75, 100. stap 1: beweegt van 35 naar 50 dat kost 15 etages stap 2: 50 → 75 = 25 etages stap 3: 75 → 100 = 25 etages stap 4: 100 → 20 = 80 etages stap 5: 20 → 10 = 10 etages In totaal moest de lift 15+25+25+80+10 = 155 etages passeren. Per etage is dat 155/5=31 etage per verzoek. Opdracht 6.3 Een flat heeft 100 etages en één lift. De lift bedient de etages met het SCAN-algoritme. De volgende etages staan in de buffer: 10,30,50,70,90. De lift bevindt zich op de 80e etage en beweegt omhoog. In welke volgorde worden de etages bediend? Noteer je antwoord in de vorm van de tabel zoals in voorbeeld 1. Opdracht 6.4 Kan het bij SCAN ook gebeuren dat etages eeuwig staan te wachten? Natuurlijk is er op het algoritme algoritme SCAN wel wat aan te merken. Het lijkt bijvoorbeeld erg overbodig om naar de bovenste en onderste etage door te bewegen. Daarom zijn er andere algoritmes ontwikkeld die de nadelen van SCAN niet hebben. Beschrijving van het algoritme C-SCAN (Circular Elevator Algorithm) De lift handelt de verzoeken nu nog maar in één richting af, dus óf naar boven, óf naar beneden. Laten we er vanuit gaan dat de lift de verzoeken alleen naar boven afgehandeld. Zodra de bovenste etage bereikt is beweegt de lift weer helemaal naar beneden zonder te stoppen. Daarna beweegt de lift weer omhoog. Dan worden ook weer reizigers afgezet of opgepikt. Als je van de derde etage naar de tweede etage moet gaat dat dus op een vreemde manier: eerste beweeg je naar de bovenste etage dan helemaal naar beneden en dan pas omhoog naar de tweede etage 24 Toch heeft het algoritme ook een voordeel: bij SCAN komen de middelste etages twee keer zo vaak aan bod als de bovenste en onderste etages. Bij C-SCAN worden alle etages even vaak bediend. Beschrijving van het algoritme LOOK Dit algoritme is hetzelfde als SCAN met dit verschil: de lift beweegt nu niet meer door tot de uiterste etages maar keert de bewegingsrichting meteen om als het laatste verzoek in de huidige richting afgehandeld is. Beschrijving van het algoritme C-LOOK Dit algoritme is hetzelfde als C-SCAN met het volgende verschil: de lift beweegt nu niet meer door tot de uiterste etages, maar keert de bewegingsrichting om als het laatste verzoek in de huidige richting afgehandeld is. Er is maar weer één richting waarin de verzoeken afgehandeld worden. Opdracht 6.5 Analyseer het C-SCAN algoritme op dezelfde manier als hierboven bij het SCAN-algoritme gedaan is. We gaan weer uit van een flat met 100 etages. In de buffer staan de volgende verzoeken: 90, 50, 10, 20, 75. De lift begint op etage 35. De lift beweegt nu omhoog en handelt alleen in opgaande richting verzoeken af. Bereken het gemiddelde aantal etages per verzoek. Doe daarna hetzelfde voor C-LOOK met dezelfde verzoeken in de buffer. Opdracht 6.6 Verwerk één van de algoritmes: Shortest Seek, SCAN, C-SCAN, LOOK of C-LOOK tot een NSD Spreek met je docent vooraf welke van deze vijf je gaat uitwerken. 25 Opdracht 6.7 Schrijf een programma in Small Basic om het Shortest Seek, SCAN, C-SCAN, LOOK of C-LOOK algoritme te demonstreren. Maak gebruik van een GraphicsWindow als je een mooie presentatie wilt maken. Gebruik de volgende variabelen: etage: om de huidige etage in te bewaren. min_etage = 0 max_etage: het nummer van de hoogste etage (bijvoorbeeld 100) richting: is -1 als de lift naar beneden beweegt, +1 als de lift omhoog beweegt en 0 als de lift stil staat Gebruik een Timer om de lift te laten bewegen, bijvoorbeeld 1 etage per seconde. Maak gebruik van Math.GetRandomNumber() om op een willekeurige manier verzoeken in de buffer te kunnen plaatsen. Bron: http://en.wikipedia.org/wiki/Elevator_algorithm 26 7 Doolhoven We stellen ons voor dat een doolhof een rechthoek is waarin met muren gangen gemaakt zijn. Hoe maak je een doolhof? Slecht algoritme Stel je voor dat je met een rooster begint waarin alle muren getekend staan. Met een gum wis je willekeurig wat muren weg. De kans is groot dat je met een lelijk doolhof eindigt, zoals in de rechter afbeelding hieronder. Het rechter doolhof is 'slecht' om dat er (af-)gesloten gebieden in voorkomen en omdat er pleintjes (onnodig grote ruimtes) in voorkomen. Met de groene en rode blokjes is het start- en eindpunt van het doolhof gemarkeerd. Opdracht 7.1 Schrijf een programma in Small Basic dat met het slecht algoritme een doolhof maakt. Wanneer noemen we een doolhof dan 'goed'? Definitie van een goed doolhof Een doolhof is goed als de twee onderstaande voorwaarden allebei gelden: 1 2 je kunt vanuit een hokje elk ander hokje bereiken. er loopt tussen twee hokjes maar één weg. Je kunt in een goed doolhof dus geen rondjes lopen. 27 Met het volgende algoritme maak je betere doolhoven: Beschrijving van het algoritme: Random divide Pas dit algoritme op roosterpapier met hokjes van 1x1cm toe. 1 Teken de rechthoek waarbinnen het doolhof moet komen. 2 Verdeel een/de rechthoek, door op een willekeurig plek twee loodrechte lijnen te teken (over de lijnen van het roosterpapier), die de oorspronkelijke rechthoek in vier rechthoekige kamers verdeeld. Zo ontstaan vier scheidingsmuren. 3 Kies 3 van de 4 muren om op een willekeurige plek een opening te maken. Elke opening heeft de breedte of hoogte van één hokje. 4 Ga naar stap 2, zolang er een kamer is die een breedte én hoogte heeft van meer dan 1 hokje. 5 Aan het eind maak je twee gaten in de buitenmuur. In de afbeeldingen hieronder zie je het algoritme als voorbeeld uitgewerkt. Tussen de derde en de vierde afbeelding ontbreken een aantal afbeeldingen. Doordat dit algoritme vaak lange rechte paden levert zoals in het voorbeeld hierboven is het bij deze doolhoven vaak gemakkelijk bepaalde wegen uit te sluiten. Het doolhof is pas compleet als er ook een in- en uitgang is. Deze maak je ergens willekeurig in de buitenmuren. Opdracht 7.2 Teken een rechthoek van 10 x 10 op ruitjespapier. Pas het Random Divide algoritme toe om een doolhof te tekenen. Scan je resultaat en mail het voor een micropunt. 28 Opdracht 7.3 In de onderstaande afbeelding zie je een doolhof. Leg uit met welk van de twee algoritmen dit doolhof gemaakt kan zijn. De eerste computers hadden alleen tekstschermen (dus alleen een textwindow, maar geen graphicswindow). Met alleen een / of een \ was op een eenvoudige manier een doolhof te maken. We kunnen dat in Small Basic ook doen met het volgende programma: Algoritme: Een doolhof maken met alleen / en \ GraphicsWindow.Width=600 GraphicsWindow.Height=600 For y=1 To 50 For x=1 To 50 richting=math.GetRandomNumber(2) If richting=1 Then TextWindow.Write("/") Else TextWindow.Write("\") endif EndFor TextWindow.WriteLine("") EndFor 29 Dat ziet er helaas niet zo goed uit als op de oude computers: Dat komt doordat de regels niet op elkaar aansluiten. Als we lijntjes tekenen op een GraphicsWindow kunnen er er wel voor zorgen dat de regels aansluiten. Doolhof met alleen / of \ (Graphics-versie) GraphicsWindow.Width=600 GraphicsWindow.Height=600 For y=1 To 50 For x=1 To 50 richting=math.GetRandomNumber(2) If richting=1 Then GraphicsWindow.DrawLine(x*10,y*10,x*10+10,y*10+10) Else GraphicsWindow.DrawLine(x*10,y*10+10,x*10+10,y*10) endif EndFor EndFor Dit algoritme maakt wel gesloten kamertjes, maar geen pleintjes. 30 Mogelijke verdieping: algoritme van Kruskal of Prim Bronnen http://en.wikipedia.org/wiki/Maze_generation_algorithm meer op: http://www.mazeworks.com/mazegen/mazetut/index.htm http://www.jamisbuck.org/presentations/rubyconf2011/index.html#title-page 31 8 The Game of Life Een van de leukste dingen die de combinatie van wiskunde en computers heeft opgeleverd zijn cellulaire automaten. Het principe ervan is simpel: je hebt een verzameling hokjes, die elk een cijfer bevatten. Vaak kan dat alleen een 1 of een 0 zijn. Daarnaast heb je regels die bepalen wat er met de vakjes gebeurt. Bijvoorbeeld: “Als ik een 0 ben en drie van mijn acht buren zijn een 1, dan word ik ook een 1. Een bekend voorbeeld is Game of Life, bedacht door James Conway. Bij deze automaat (een spel is het niet echt) zijn de vakjes met enen en nullen in een vierkant of rechthoek gerangschikt en gelden de volgende regels: Beschrijving van het algoritme: Game of Life per ronde voor alle cellen: Bij een 1: Als van je 8 buren er twee of drie ook een 1 zijn, blijf je een 1. Bij een 0: Als je precies drie buren hebt die 1 zijn wordt je een 1. In alle andere gevallen wordt je een 0. Let op: als een cel van kleur verandert telt dat pas in de volgende ronde! Deze regel wordt ook wel samengevat als B3/S23: Er wordt een nieuwe cel geboren (Birth) bij 3 buren Een cel overleeft (Survive) bij 2 of 3 buren. Een cel sterft als ze 0,1 of meer dan 3 buren heeft Of wel: een cel wordt een 1 als 3 buren een 1 zijn Een cel blijft een 1 als 2 of 3 buren een 1 zijn Een cel die 1 was wordt een 0 als 0,1 of meer dan 3 buren 1 zijn. 32 Als je alle vakjes met een 1 zwart inkleurt krijg je iets dat er als volgt uitziet: Op het volgende webadres kun je met the Game of Life experimenteren: http://vanjoolingen.nl/cells/life.html Let op: je hebt hiervoor een moderne browser zoals Firefox of Chrome nodig. Druk op Stap om de regels één keer toe te passen. Je ziet het patroon veranderen. Druk op Start om de regels keer op keer toe te passen. Een beetje afhankelijk van het beginpatroon kan een wild, dynamisch patroon ontstaan. Probeer ook het volgende eens uit. Druk op Stop, en dan op Schoon. Klik dan een paar vakjes aan tot je het volgende patroon ziet: En druk op Start. Je hebt leven gemaakt: dit patroon schuift in vier stappen schuin naar boven. Dit patroon heet – niet verrassend – een glider. Gliders zijn kwetsbaar, bij de kleinste verstoring gaan ze dood. Zet maar eens een zwart vakje op zijn route, of laat er twee botsen. Muziektip Speel ' imitation of life' van REM als je de animatie gebruikt. http://www.youtube.com/watch?v=0vqgdSsfqPs 33 Stappenplan Game of Life Hoe kun je nu het eenvoudigste de volgende generatie berekenen bij een spelregel? voorbeeld Stap 0 We gaan uit de het onderstaande speelveld. De spelregel is B2/S23 (let op: er staat B2, niet B3 zoals op de vorige bladzijde). Stap 1 Eerst bekijk je alle witte cellen. Als ze precies twee buren hebben, zet je er een B in. B B B B Stap 2 Dan kijken we naar de zwarte cellen. Als er 2 of 3 buren zijn, overleeft de cel. Je zet er dan een S in. Als er 0, 1 of meer dan 3 buren zijn sterft de cel: zet er dan een – (minteken) in. S S B S S B B B - Stap 3 Met zwarte en witte cellen ziet het er dan als volgt uit: 34 Opdracht 8.1 Bepaal hoe de volgende generatie van het voorbeeld er uit ziet door het stappenplan te volgen. Opdracht 8.2 Bepaal voor elk van de 25 cellen in de volgende afbeelding de kleur in de volgende generatie. Randen en hoeken Een bijzonder geval doet zich voor aan de randen en in de hoeken. Je kunt deze op twee manieren verwerken. Methode 1 Cellen die buiten het zichtbare gebied liggen zijn altijd dood. Een cel in een hoek kan nooit meer dan 3 buren hebben. Een cel aan de rand maximaal 5. methode 2 De randen zijn onzichtbaar verbonden met de andere kant. De linkerrand is dus met de rechterrand verbonden. De bovenkant met de onderkant. Zo hebben ook de cellen aan de randen en in de hoeken 8 buren. Opdracht 8.3 In het onderstaande speelveld zijn de randen niet doorverbonden met de andere kant. Markeer de buren van de zwarte cellen door er een kruisje in te zetten. 35 Opdracht 8.4 In het onderstaande speelveld zijn de randen wel doorverbonden met de andere kant. Markeer de buren van de zwarte cellen door er een kruisje in te zetten. Game of Life is interessant, omdat het laat zien dat je met simpele regels ingewikkeld gedrag kunt krijgen. Als je de wilde patronen zou zien zonder eerst het verhaal te lezen zou je waarschijnlijk niet vermoeden dat de onderliggende regels zo simpel zijn. Ondanks die simpele regels blijkt dat de Game of Life veel mogelijkheden biedt. Er zijn patronen die oneindig uitgroeien, gliders, ruimteschepen. Het is zelfs mogelijk een programmeerbare computer in Game of Life te maken. Opdracht 8.5 Schrijf een programma in Small Basic waarmee je Game of Life kunt 'spelen'. Kies een gebied van bijvoorbeeld 20x20 cellen. Welke methode gebruik je aan de randen? Meer voorbeelden kun je in de Engelstalige wikipedia-artikel over Game of Life vinden: http://en.wikipedia.org/wiki/Conway%27s_Game_of_Life Op de volgende website vind je een java-applet (werkt dus niet op een tablet): http://www.mirekw.com/ca/mjcell/mjcell.html Er bestaat ook een 3D-versie van Game of Life: https://www.youtube.com/watch?v=xg0PKAvL01Y Bron: http://vanjoolingen.nl/ 36 9 De uitgang vinden van een doolhof Je wordt ergens in een doolhof neergezet. Hoe vind je de weg naar de uitgang van een doolhof? De eerste vraag is natuurlijk: ís er wel een weg naar de uitgang? Je wilt dus weten of er wel een oplossing is. We gaan het volgende doolhof bekijken. De ingang is bij I de uitgang is bij U. I U 37 Algoritme: CheckMaze Stap 1 We beginnen bij de uitgang daar zetten we een open bolletje in. I o Nu herhalen we de volgende stappen, tot er geen open bolletjes meer zijn. Voor alle open bolletjes Maak het bolletje zwart Zet een bolletje in alle LEGE aangrenzende hokjes die niet door een muur afgescheiden zijn. Stap 2 De volgende stap is dus: I o Stap 3 I o Stap 4 I o 38 Stap 5 I o o Stap 6 I o o o o Stap 7 I o o o o Stap 8 I o o o o o o o Stap 9 I o 39 Stap 10 I o o Stap 11 o Stap 12 We zijn nu klaar omdat er geen open bolletjes meer zijn. Er vallen twee dingen op: – In alle hokjes staat een zwart bolletje. Dat betekent dat je vanuit alle hokjes naar de uitgang kunt lopen. – Er staat dus ook een zwart bolletje in het hokje waar we de ingang gemaakt hebben. 40 Opdracht 9.1 Pas het CheckMaze-algoritme toe op het volgende doolhof. I U De open bolletjes zijn na een aantal stappen altijd op. Maar het hoeft niet zo te zijn dat er uiteindelijk in elk hokjes een zwart bolletje staat. Opdracht 9.2 Teken een doolhof van 5x5. Markeer daarin de ingang met een I en de uitgang met een U. Kies de muren zo dat je niet vanuit alle hokjes naar de uitgang kunt lopen, maar wel vanuit de ingang. 41 In het algoritme staat nadrukkelijk: Zet een bolletje in alle LEGE aangrenzende hokjes die niet door een muur afgescheiden zijn. Als je de stappen hierboven naloopt zie je dat je NOOIT een aangrenzend hokje tegenkomt dat niet leeg is. Zijn er dan situaties waarin je wel een niet-leeg hokje tegenkomt? We nemen daarvoor het volgende doolhof. I U Stap 1 We beginnen weer bij de uitgang. I o Stap 2 I o o Stap 3 I o o Stap 4 I o o 42 Stap 5a We delen deze stap in twee delen op. Het eerste bolletje kunnen we wegwerken. I a o Stap 5b Als we nu het tweede bolletje willen wegwerken. Komen we echter het hokje tegen waarin de a staat. Dat hokje is niet leeg. Hoe komt het dat we nu wél een niet-leeg hokje tegenkomen? We zijn langs twee wegen bij hetzelfde hokje aangekomen. Dit kan alleen gebeuren als je ergens in het doolhof een rondje kunt lopen. I a o Kortom je kunt de algoritme MazeCheck niet alleen gebruiken om te bekijken of er weg van de ingang naar de uitgang is, maar zelfs om te zien of het een goed doolhof is. Als het een goed doolhof is, heb je aan het einde: – In elk hokje een zwart bolletje staan. – Ben je nooit een niet-leeg hokje tegengekomen. 43 Muurvolg-methode Er is een verrassend eenvoudige manier die bij sommige doolhoven werkt. Beschrijving van het algoritme: Muurvolg-methode • Je kiest een muur die je gaat volgen: de linker muur of de rechter muur. • Telkens als je een deur in die muur tegenkomt, ga je die in. Je volgt de muur dan aan de andere kant van de deur verder. • Als een gang eindigt blijf je de gekozen muur volgen. Als dat de linker muur is draai je dus naar rechts. Als je de rechter muur volgt draai je naar links. Opdracht 9.3 Je staat op de plek die met het kruisje gemarkeerd is. Volg de linker muur naar de uitgang. Opdracht 9.4 Je staat op de plek die met het kruisje gemarkeerd is. Volg de rechter muur naar de uitgang. Als je in een gang staat is het afhankelijk van je kijkrichting wat links en rechts is. Opdracht 9.5 Wat is voor de persoon die op de plaats van het kruisje staat de linker muur? 44 Daarom markeren we de plek niet met een kruisje maar met een pijltje dat de kijkrichting aangeeft. In een slecht doolhof kun je de uitgang vanuit sommige hokjes niet vinden met de 'muurvolg-methode'. Opdracht 9.6 In het onderstaande doolhof is de uitgang rechts. Wat gebeurt er als je met de muurvolgmethode de uitgang probeert te vinden vanuit het punt dat met een kruisje gemarkeerd is? Vanuit een afgesloten deel van een slecht doolhof kun je natuurlijk ook nooit bij de uitgang komen. x U 45 Cellulaire automaat Met cellulaire automaten kunnen we meer dan mooie patronen genereren. Ze vormen een krachtig gereedschap om allerlei processen en principes mee te simuleren. Je leert nu een cellulaire automaat waarmee je de weg uit een doolhof kunt vinden. Een belangrijk verschil met de Game of Life is dat de cellen nu op meerdere manieren gevuld kunnen zijn. Preciezer, een cel is niet alleen bezet of leeg, maar kan bezet zijn met verschillende kleuren. Het model van het doolhof bestaat uit een vierkant van cellen waarin de muren worden gevormd door bruine cellen. Bij de uitgang is een cel gevuld met groen. De persoon die probeert de ontsnappen staat in een rode cel. Net als bij de Game of Life geven we regels hoe de cellulaire automaat gaat veranderen. Het zal duidelijk zijn dat de verandering alleen zal plaatsvinden rond de rode cel. Daarom formuleren we de regels vanuit die cel. Alle regels gelden dus alleen als de cel rood is: Beschrijving van het algoritme: Cellulaire Automaat-methode • Ga naar de groene cel: Als een van je buurcellen (links, rechts, boven, onder) groen is: kleur die cel rood, en kleur je eigen cel geel. Stop daarna. • Ga naar een lege cel: Als een van je buurcellen leeg is, kleur die rood en kleur je eigen cel geel. • Volg het spoor terug: Als geen van je buurcellen leeg is, maar er is wel een gele, kleur die rood, en de cel waar je vandaan komt blauw. • In alle andere gevallen stop je. Op het volgende webadres vind je een werkende simulatie: http://vanjoolingen.nl/cells/maze.html Als je de simulatie start, zie je dat de rode cel beweegt naar lege cellen. Wanneer er geen lege cellen in de buurt zijn, volgt hij het gele spoor terug, tot er weer een lege cel is die geprobeerd kan worden, net zolang tot de uitgang is bereikt. Het gele spoor tekent de 46 gevolgde weg. De gele vakjes zou je daarom ook broodkruimels kunnen noemen. Het weer teruggaan naar eerder neergelegde broodkruimels noemen we backtracken. Opdracht 9.7 Onderzoek wat de automaat precies doet als hij bij een kruispunt van wegen aankomt. In welke volgorde worden de richtingen Links, Rechtdoor en Rechtsaf doorlopen? Opdracht 9.8 Bekijk het onderstaande doolhof. De zwarte cellen zijn muren. Je begint in S. De uitgang ligt bij U. Op een kruispunt wordt de richtingen in de volgende volgorde doorlopen: Links, Rechtdoor, Rechts. In welke volgorde worden de cellen 1, 2, 3, 4, 5 en 6 bezocht door de cellulaire automaat? Noteer de volgorde. Als een cel meer dan 1 keer bezocht wordt moet je het nummer van die cel bij elk bezoek opschrijven. Opdracht 9.9 Ontwerp zelf een doolhof dat minimaal 10x10 cellen groot is. Muren kleur je zwart. Markeer het startpunt met S en de uitgang met U. Markeer kruisingen en eindpunten van gangen met cijfers zoals in de vorige opdracht. In welke volgorde worden de met cijfers gemarkeerde punten doorzocht door de cellulaire automaat. Je kunt voor deze opdracht 3 micropunten verdienen: 47 – een doolhof van minimaal 10x10 cellen – een kopie van het doolhof met kruisingen en eindpunten gemarkeerd – een ingekleurde kopie met zwarte, gele, blauwe, groene, rode en witte cellen. Als je wilt kun je opnieuw beginnen en met muisklikken muren bijmaken of wegbreken. Onderzoek hoe de route verandert. Of maak een onmogelijk speelveld. Omdat de regels er voor zorgen dat er altijd maar één rode cel is, is het verleidelijk die een eigen identiteit te geven. Het is de representatie van de persoon die de uitweg zoekt. De gekleurde cel wordt een ding, met een eigen gedrag. Zoiets wordt een agent genoemd, en de simulatie wordt dan, in goed Nederlands, een agent-based simulation. Aardig van deze simulaties is dat ze laten zien dat je met weinig eenvoudige regels relatief ingewikkeld gedrag kunt begrijpen. Opdracht 9.10 Schrijf een programma in Small Basic waarmee je en cellulaire automaat de weg door een doolhof laat zoeken. Bron: http://vanjoolingen.nl/ 48 10 Levenshtein Soms verschillen woorden maar heel weinig van elkaar. Het algoritme van Levenshtein geeft een maat voor de afstand tussen twee strings. Je kunt dit gebruiken bij spellingscontrole, spraakherkenning, vergelijken van DNA, of het ontdekken van plagiaat. We kunnen het woord GUMBO veranderen in GAMBOL door de U in een A te veranderen en een L toe te voegen. Omdat dit twee veranderingen zijn zeggen we dat de afstand tussen de twee woorden 2 is. Er zijn drie soorten veranderingen: een letter toevoegen, wissen of veranderen. Het algoritme berekend de Levenshtein-afstand tussen twee strings s en t, Beschrijving van het algoritme Levenshtein Stap Beschrijving 1 n ← de lengte van s m ← de lengte van t // uitzonderingen worden hieronder afgehandeld als n=0 dan geef n als antwoord en stop als m=0 dan geef m als antwoord en stop maak een matrix met de rijen 0..m en de kolommen 0..n 2 Schrijf in de eerste rij de getallen 0..n Schrijf in de eerste kolom de getallen 0..m 3 Vergelijk elke teken van s (i loopt van 1 tot n) 4 Met elk teken van t (j loopt van 1 tot m) 5 Als s[i]=t[j] dan kosten=0 anders kosten=1 6 De cel(i,j) krijgt als waarde het minimum van de volgende drie mogelijkheden: – – – 7 de waarde van de cel erboven +1: d[i,j-1]+1 de waarde van de cel links +1: d[i-1,j]+1 de waarde van de cel linksboven + de kosten die in stap 5 berekend werden: d[i-1,j-1]+kosten Nadat de stappen 3,4,5 en 6 compleet doorlopen zijn, staat de afstand in cel(n,m). Dat is de cel rechtsonder. 49 We gaan dit bekijken aan de hand van een voorbeeld: voorbeeld Aan de hand van de woorden GUMBO en GAMBOL zullen we deze stappen nu doorlopen. Na stap 1 en 2 0 G 1 A 2 M 3 B 4 O 5 L 6 G U M B O 1 2 3 4 5 Na stap 3 tot en met 6 voor i=1 G U M B O 0 1 2 3 4 5 G 1 0 A 2 1 M 3 2 B 4 3 O 5 4 L 6 5 Na stap 3 tot en met 6 voor i=2 G U M B O 0 1 2 3 4 5 G 1 0 1 A 2 1 1 M 3 2 2 B 4 3 3 O 5 4 4 L 6 5 5 50 Na stap 3 tot en met 6 voor i=3 G U M B O 0 1 2 3 4 5 G 1 0 1 2 A 2 1 1 2 M 3 2 2 1 B 4 3 3 2 O 5 4 4 3 L 6 5 5 4 Na stap 3 tot en met 6 voor i=4 G U M B O 0 1 2 3 4 5 G 1 0 1 2 3 A 2 1 1 2 3 M 3 2 2 1 2 B 4 3 3 2 1 O 5 4 4 3 2 L 6 5 5 4 3 Na stap 3 tot en met 6 voor i=5 G U M B O 0 1 2 3 4 5 G 1 0 1 2 3 4 A 2 1 1 2 3 4 M 3 2 2 1 2 3 B 4 3 3 2 1 2 O 5 4 4 3 2 1 L 6 5 5 4 3 2 Stap 7 De afstand staat nu in de cel rechtsonder: 2. Er zijn dus twee veranderingen nodig om GUMBO in GAMBOL te veranderen (de U wordt een A en achteraan wordt een L toegevoegd) 51 Opdracht 10.1 Neem de onderstaande tabel over en bereken zoals in het voorbeeld de Levenshteinafstand tussen de woorden PAAS en HAAS. Dit voorbeeld is zo eenvoudig dat je vooraf al kunt zien dat de afstand 1 is, maar het gaat om het correct toepassen van het algoritme. 0 P 1 A 2 A 3 S 4 H A A S 1 2 3 4 Opdracht 10.2 Neem de onderstaande tabel over en bereken zoals in het voorbeeld de Levenshteinafstand tussen de woorden HAZEN en HAGEL. 0 H 1 A 2 Z 3 E 4 N 5 H A G E L 1 2 3 4 5 Opdracht 10.3 Schrijf een programma in Small Basic dat met het algoritme van Levenshtein de afstand tussen twee woorden berekent. 52 Opdracht 10.4 Pas het programma van 10.3 zo aan dat het twee DNA-codes kan vergelijken. Een DNA-code is een tekst met de letter C, T, A en G. Een ? Staat voor 1 onleesbare letter. Het programma moet twee willekeurige DNA codes met C, T, G, A en vraagtekens maken. Het programma's moet de afstand met het algoritme van Levenshtein uitrekenen. Als je deze opdracht te eenvoudig vind: Is het programma zo aan te passen dat een vraagteken voor 1 of meer onbekende tekens kan staan? Bron: http://www.merriampark.com/ld.htm 53 11 Meer over kaarten schudden In hoofdstuk 4 hebben we het schudden van kaarten bekeken. In de uitwerking vond je een oplossing die als volgt ging. – bepaal hoeveel rondes doorlopen moeten worden – kies in elke ronde twee kaarten en verwissel die Als je heel vaak verwisseld is dat een goede methode. Toch kan dit beter. Het kan toevallig zo zijn dat een paar kaarten niet gekozen worden! Je kunt er gegarandeerd voor zorgen dat elke kaart snel een keer aan de beurt komt. De volgende aanpassing (van Suraya) zorgt ervoor dat na 1 ronde alle kaarten aan de beurt geweest zijn. LET OP: Bij de volgende programma's ontbreekt het deel dat het array kaart met getallen vult. Dat moet je doen voor je de kaarten schudt. Voor de code moeten dus de volgende regels. For teller=1 to 52 Kaart[teller]=teller endfor Bovendien is er geen code waarmee je kan zien wat er na het schudden in het array staat. Dat gebeurt met de volgende code: For teller=1 to 52 textwindow.writeline(Kaart[teller]) endfor De code moet na de onderstaande code. Schudden: Suraya For teller=1 To 52 'kies één willekeurige kaart 'let op: dit kan toevallig dezelfde kaart als teller zijn! x=math.getrandomnumber(52) 'verwissel de twee kaarten temp=kaart[teller] kaart[teller]=kaart[x] kaart[x]=temp endfor Maar wat gebeurt er als in een bepaalde ronde x gelijk is aan teller? De kaart wordt met zichzelf verwisseld. Dat is op te lossen door te eisen dat x ongelijk moet zijn aan de teller. Schudden: gegarandeerde verwisseling For teller=1 To 52 'kies een willekeurige kaart 'ga hiermee door tot x ongelijk aan teller is 54 'maak x gelijk aan teller, dan moeten we zeker nog een keer een kaart kiezen x=teller while x=teller x=math.getrandomnumber(52) endwhile 'verwissel de twee kaarten temp=kaart[teller] kaart[teller]=kaart[x] kaart[x]=temp endfor Bij opgave 4.3 werd gekeken hoeveel kaarten op dezelfde plek bleven liggen. Het aantal kaarten dat niet verplaatst is, is een maat voor de kwaliteit van het schud algoritme. Hoe minder kaarten nog op hun oorspronkelijke plek liggen, des te beter het algoritme zijn werk deed. Toch is dat niet voldoende. Bekijk maar eens het volgende voorbeeld: voorbeeld Neem een stapel gesorteerde kaarten. Neem een deel aan de bovenkant van de stapel af, en schuif die aan de onderkant erbij. Alle kaarten zijn nu van nu plaats gewisseld, toch vind je waarschijnlijk dat de kaarten niet goed geschud zijn! Je kunt dat oplossen door te eisen dat er er geen opeenvolgende nummers in de lijst meer mogen staan. Opeenvolgend kan ook betekenen dat de rij afloopt: 8,7,6 is even erg als 6,7,8. Je wilt dus voorkomen dat het verschil tussen twee opeenvolgende getallen nooit 1 of -1 is. We doen dus het volgende: • • • we gebruiken het algoritme schudden met gegarandeerde verwisseling dan tellen we hoeveel kaarten nog op hun oude plaats liggen en hoeveel opeenvolgende kaarten er zijn als beide aantallen nul zijn stoppen we, anders schudden we nog een keer met gegarandeerde verwisseling Opdracht 11.1 Schrijf een Small Basic programma dat ervoor zorgt dat de kaarten zó schud worden dat ze: 55 – niet op hun oorspronkelijke plek meer staan, én – er geen opeenvolgende kaarten voorkomen (ook niet in aflopende volgorde) 56 12 Priemgetallen doelen Weten wat een priemgetal is. Het algoritme IsPriem kunnen reproduceren. Het NSD van het algoritme IsPriem kunnen reproduceren. De Small Basic-code van IsPriem kunnen reproduceren. Het Small Basic-programma regel voor regel kunnen verklaren. Weten dat elke getal minimaal 2 delers heeft. Weten dat een priemgetal precies twee delers heeft. Weten dat een getal met meer dan twee delers niet priem is. Weten wat een echte deler is. Weten hoe je het algoritme IsPriem kunt versnellen door alleen de even delers te controleren. alleen de delers tot de helft van n te controleren. alleen de even delers tot de helft van n te controleren. te stoppen als je de eerste deler gevonden hebt. alleen de even delers tot de wortel van n te controleren. Weten hoe je met het Algoritme van Erathostenes alle priemgetallen in een reeks vindt. Je ontdekt in deze paragraaf dat kleine veranderingen enorme verbeteringen kunnen zijn. We maken een algoritme met een kleine verandering veel sneller (bijvoorbeeld 1000x zo snel). Wat is een priemgetal? Priemgetallen zijn gehele positieve getallen groter dan 1, die alleen 1 en zichzelf als deler hebben. voorbeelden Eén is dus geen priemgetal, want het getal moet groter dan 1 zijn. Anderhalf is ook geen priemgetal, want het moet een geheel getal zijn. Vier is dus geen priemgetal, want het is behalve door 1 en 4 ook door 2 deelbaar. Twee is het enige even priemgetal, omdat alle andere even getallen door twee deelbaar zijn. De eerste priemgetallen zijn: 2,3,5,7,11,13,17,19,23,... Hoe kunnen we een computerprogramma nu laten onderzoeken of een getal priem is? 57 Beschrijving van het Algoritme IsPriem 1 Geef een geheel getal n 2 Neem aan dat het aantal delers 2 is. 3 Neem d=2 4 Controleer of getal d het getal n deelt (de rest na deling is dan nul). 5 Als het getal n wél door n deelbaar is, maken we het aantaldelers één hoger 6 Als d<n-1, ga naar stap 3 7 Controleer het aantal delers: als het aantal twee is, was het getal n priem. Beschrijving van het Algoritme IsPriem Geef een geheel getal n Neem aan dat het aantal delers 2 is. Doorloop alle getallen d van 2 tot en met n-1. Controleer of getal d het getal n deelt (de rest na deling is dan nul). Als het getal n wél door n deelbaar is, maken we het aantaldelers één hoger Controleer het aantal delers: als het aantal twee is, was het getal n priem. Het algoritme gaat er vanuit dat elk getal twee delers heeft: 1 en zichzelf. Voorbeeld 5 is deelbaar door 1 en 5, maar niet door andere gehele getallen 10 is behalve door 1 en 10 ook deelbaar door 2 en 5. De extra delers noemen we echte delers. Voorbeeld De echte delers van 10 zijn 2 en 5. Als een getal echte delers heeft is het niet priem. Opdracht 12.1 Verwerk het algoritme IsPriem tot een NSD. Opdracht 12.2 Schrijf een programma in Small Basic dat met behulp van het algoritme bepaalt of een getal priem is of niet. Noem dit programma PRIEM1 Gebruik Clock.EllapsedMilliseconds om de tijd te meten die het programma voor een getal nodig heeft. Hint: 58 starttijd=Clock.ElapsedMilliseconds 'hier moet de code die het getal test eindtijd=Clock.ElapsedMilliseconds TextWindow.WriteLine("De verstreken tijd is:"+(eindtijdstarttijd)/1000+"seconde") Gelukkig is het niet nodig om alle delers van 2 tot en met getal-1 te onderzoeken. Er zijn vier eenvoudige verbeteringen mogelijk. Verbetering 1: Voor alle getallen groter dan 2 kun je alle even delers overslaan. Twee is het enige even priemgetal. Alle andere priemgetallen zijn dus oneven en niet deelbaar door even getallen. In plaats van for deler=2 to getal -1 kunnen we dus beter het volgende gebruiken for deler=3 to getal -1 step 2 We moeten dan wel controleren dat getal groter dan 2 is. Dat kan op de volgende manier If math.remainder(getal,2)=0 then isPriem=0 else for deler=3 to getal -1 step 2 ' rest van de routine endfor endif Je bespaart hiermee (bijna) de helft van de controles. Verbetering 2: De grootst mogelijke deler is de helft van het getal. Als een getal deelbaar is (dus niet priem) heeft het vaak minstens twee echte delers. Bijvoorbeeld: 46 is deelbaar door 2, maar het heeft dan ook meteen 23 als deler. Immers 2x23=46. Omdat 2 de kleinste deler is, is getal de grootste deler. 2 59 In plaats van for deler=2 to getal-1 kunnen we dus beter het volgende gebruiken for deler=2 to getal/2 Je bespaart hiermee weer (bijna) de helft van de controles. Combinatie van 1 en 2: We kunnen de twee verbeteringen samenvoegen. Je krijgt dan het volgende: In plaats van for deler=2 to getal-1 gebruiken we for deler=3 to getal/2 step 2 Deze twee verbetering zorgen er samen voor ongeveer driekwart van de mogelijke delers wordt overgeslagen. Het programma wordt daardoor 4x zo snel. Verbetering 3: Bij sommige getallen vind je al snel een deler. Je kunt dan meteen stoppen. In plaats van een for-lus gebruik je een while-lus IsPriem=1 TextWindow.Write("Geef een getal:") Getal=TextWindow.ReadNumber() if Math.Remainder(getal,2)=0 Then deler=2 IsPriem=0 else deler=3 While (deler<getal / 2) And (IsPriem=1) if Math.Remainder(getal,deler)=0 Then IsPriem=0 endif deler=deler+2 'alleen oneven delers endWhile endif 'wat is het resultaat If isPriem=1 Then TextWindow.WriteLine(Getal +" is een priemgetal") Else TextWindow.WriteLine(Getal +" is deelbaar door "+deler) 60 endif 61 Verbetering 4: Maar het kan nog veel beter. Hieronder staan alle vermenigvuldigen die 16 als antwoord geven met getallen groter dan 1: product Kleinste deler 2x8 2 4x4 4 8x2 2 De grootste kleinste deler is 4. Hieronder staan alle vermenigvuldigen die 64 als antwoord geven met getallen groter dan 1: product Kleinste deler 2x32 2 4x16 4 8x8 8 16x4 4 32x2 2 De grootste kleinste deler is 8. Hieronder staan alle vermenigvuldigen die 12 als antwoord geven met getallen groter dan 1: product Kleinste deler 2x6 2 3x4 3 4x3 3 6x2 2 De kleinste deler is maximaal 3. Je hoeft zes niet meer te controleren, omdat je eerder al twee gehad hebt! Hetzelfde geldt voor vier. We hoeven voor het getal van 12 alleen 2 en 3 als deler te controleren. Dat scheelt de helft! 62 Opdracht 12.3 Wat is de grootste van alle kleinste delers die in een product van 36 voorkomt? Wat is de grootste van alle kleinste delers die in een product van 144 voorkomt? Wat is de grootste van alle kleinste delers die in een product van 256 voorkomt? Wat valt op aan deze getallen? Wat is de grootste van alle kleinste delers delers van 110? Klopt deze regel die je na de eerste drie getallen vond dus exact? Maar hoe weet je nu waar je bij grotere getallen kunt stoppen met delen? Je moet stoppen bij de grootste van alle kleinste delers die in een vermenigvuldiging voor dat getal voorkomt. Het was niet toevallig dat 4 de wortel van 16 is en 8 de wortel van 64. We kunnen dus stoppen als we bij de wortel van het getal zijn. Als de wortel niet een geheel getal is rond je naar beneden af. In Small Basic schrijven we de for-lus dan zó: for deler = 3 to math.Floor(Math.SquareRoot(getal)) Step 2 We gaan er dus vanuit dat het programma eerst kijkt of het getal deelbaar is door 2. Als dat niet zo is worden alleen oneven delers gecontroleerd. Opdracht 12.4 Pas het programma PRIEM1 zo aan dat je alleen de oneven delers tot de wortel van het getal test. Noem dit programma PRIEM2. Heeft deze verbetering het algoritme veel sneller gemaakt? Voor het getal 1001 moesten we eerst de delers 2 t/m 1000 testen. Dat zijn 999 getallen. Nu hoeven we nog maar 2 en de oneven getallen van 3 t/m 31 te testen. Dat zijn 15 999 ≈66 keer zo snel geworden. getallen. De test is daarom ruim 15 Bij grotere getallen is de winst nog veel groter. Opdracht 12.5 Meet met beide programma's de tijd die het nodig heeft voor de volgende getallen: 10,100,1000,10000,100000,1000000,10000000 (tot tien miljoen). 63 Opdracht 12.6 Pas het programma PRIEM2 zo aan dat je een reeks getallen kunt onderzoeken. Noem dit programma PRIEM3. Hint: Gebruik een for-lus om de reeks getallen te doorlopen. Zet de priemtest in een subroutine. Hoewel PRIEM2 veel sneller is dan PRIEM1 moet je het niet voor hele grote getallen gaan gebruiken. Zelfs voor een biljoen duurt het al veel te lang eer je het antwoord berekent hebt. Gelukkig zijn er nog sneller methode die je wel met succes bij grote getallen kunt gebruiken. Die methoden zijn echter te ingewikkeld om hier te bespreken. We kunnen echter wel alle vier de verbeteringen in het programma opnemen: IsPriem=1 TextWindow.Write("Geef een getal:") Getal=TextWindow.ReadNumber() if Math.Remainder(getal,2)=0 Then deler=2 IsPriem=0 else deler=3 isPriem=1 While (deler<=Math.SquareRoot(getal)) And (IsPriem=1) if Math.Remainder(getal,deler)=0 Then IsPriem=0 endif deler=deler+2 'alleen oneven delers endWhile endif 'wat is het resultaat If isPriem=1 Then TextWindow.WriteLine(Getal +" is een priemgetal") Else TextWindow.WriteLine(Getal +" is deelbaar door "+deler) endif 64 Alle priemgetallen in een reeks zoeken Als je een reeks priemgetallen wilt zoeken is er gelukkig wel een onverslaanbare methode. Die methode kon de Griekse geleerde Erathostenes meer dan 2000 jaar geleden al. http://nl.wikipedia.org/wiki/Eratosthenes Zijn methode heet dan ook de zeef van Erathostenes. http://nl.wikipedia.org/wiki/Zeef_van_Eratosthenes Je wilt bijvoorbeeld alle priemgetallen tot 25 vinden. • • • • • • • Allereerst streep je 1 af (want 1 is geen priemgetal) Het eerste niet-doorgestreepte getal is 2. Dit getal omcirkel je. Het getal is priem. Streep alle veelvouden van 2 af (2,4,6,8,...) 3 is het eerste getal dat niet doorgestreept of omcirkeld is. We omcirkelen drie: het is priem. Streep alle veelvouden van 3 af (3,6,9,12,...). Je komt daarbij getallen tegen die je in stap twee al afgestreept hebt. Je gaat hiermee door zolang het omcirkelde getal kleiner of gelijk is aan de wortel van het eindgetal. Er zijn nu nog getallen over: die zijn allemaal priem. We kunnen dit algoritme als volgt omschrijven. Algoritme Erathostenes Stap beschrijving 1 Bepaal tot welk getal je alle priemgetallen wilt weten. Dit getal noemen we max. 2 Bereken de wortel van max. 4 Streep 1 af. 5 omcirkel het eerste vrije getal, noem dit getal x. 6 streep alle veelvouden van x af. 7 ga naar stap 5 als x kleiner of gelijk is aan de wortel van max. 8 omcirkel alle overgebleven vrije getallen. 65 Voorbeeld opdracht: Zoek alle priemgetallen tot en met 12 We maken een tabel met de getallen 1 tot en met 12. De wortel van 12 is √12≈3,46 . 1 2 3 4 5 6 7 8 9 10 11 12 4 5 6 7 8 9 10 11 12 9 10 11 12 Streep 1 af 2 3 Omcirkel het eerste vrije getal (noem dit x): dit is 2. 2 3 4 5 6 7 8 Streep alle veelvouden van 2 af 2 3 5 7 9 11 7 9 11 Omcirkel het eerste vrije getal: 3 2 3 5 Streep alle veelvouden van 3 af 2 3 5 7 11 7 11 Omcirkel het eerste vrije getal: 5 2 3 5 Omdat vijf groter is dan de wortel van twaalf ( √ 12≈3,46 ) kunnen we stoppen. De resterende getallen zijn automatisch priem. In dit geval zijn 5, 7 en 11 dus ook priem. 2 3 5 7 66 11 Opdracht 12.6 Gebruik het algoritme van Erathostenes om de priemgetallen van 1 t/m 25 te vinden. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Opdracht 12.7 Schrijf een programma in Small Basic dat het algoritme van Erathostenes gebruikt om alle priemgetallen tot een bepaalde grens te vinden. Het programma moet de totale tijd en de tijd per priemgetal (=totale tijd gedeeld door het aantal priemgetallen) op het scherm schrijven. Noem dit programma PRIEM4. 67 13 Het Traveling Salesman-probleem (TSP) doelen Kunnen omschrijven wat het Traveling Salesman-probleem is. Weten wat een netwerk is. Weten hoe je n! (n faculteit) berekent. Weten met welke formulere je het aantal routes in een netwerk berekent. Weten hoe je berekent hoeveel tijd het kost om alle routes te berekenen, als gegeven is hoeveel tijd de berekening voor één route kost. Begrijpen dat het onmogelijk is om alle routes binnen een redelijke tijd te onderzoeken. Kunnen beschrijven hoe het Greedy-algoritme een korte route vind. De Small Basic-code van het Greedy-algoritme kunnen reproduceren. De Small Basic-code van het Greedy-algoritme egel voor regel kunnen uitleggen. In een route knelpunten kunnen vinden die op een eenvoudige manier een kortere route opleveren. Wat is het Traveling Salesman-probleem? Een zakenman wil tien steden bezoeken. Hij vertrekt 's morgens thuis en komt aan het eind van de dag ook weer thuis aan. Tussen elk paar steden is er een weg. In welke volgorde moet hij de steden bezoeken, als hij een zo kort mogelijke reisafstand wil hebben? Elke stad wordt maar één keer bezocht. Omdat er tussen elk paar steden een weg is, wordt een weg ook niet meer dan één keer gebruikt worden. Hieronder zie je een voorbeeld van het probleem. De zakenman woont in A en wil B, C en D bezoeken. Na afloop moet hij weer in A aankomen. Figuur 13.1 Opdracht 13.1 Vind de kortste route in het netwerk van Figuur 13.1 door alle routes die in A beginnen te controleren. 68 We noemen dit het handelreiziger-probleem. In het Engels: traveling Salesman-problem. Dit wordt afgekort als TSP. Beschrijving: Handelsreizigersprobleem / Travelings Salesman-problem Een aantal steden zijn met een netwerk van wegen met elkaar verbonden. Tussen elke paar steden loopt een weg. Een handelsreiziger wil op één dag vanuit een stad alle andere steden bezoeken. Daarbij wordt elke stad maar één keer bezocht. Aan het eind van de dag komt de handelsreiziger weer aan in de stad waar hij vertrok. Bepaal de kortste route langs alle steden. Aantal mogelijke routes door een netwerk Op hoeveel verschillende manieren kan de zakenman door het netwerk reizen? Stel dat er 4 punten in het netwerk zijn. De zakenman vertrekt in punt A. Dan zijn er 3 punten die hij moet bezoeken: B, C, D. Er zijn zes manieren waarop je drie punten in na elkaar kunt bezoeken. Voor het eerste punt kies je uit de 3 punten B, C en D. Voor het tweede punt zijn er nog 2 keuzes over. Voor het derde punt blijft vanzelf nog maar 1 punt over. Daarom zijn er 3x2x1=6 mogelijkheden. Er is een wiskundige notatie voor dit soort vermenigvuldigingen: 3x2x1 = 3! Je spreekt dit uit als drie faculteit. Voorbeelden 3! = 1 x 2 x 3 = 6 5! = 1 x 2 x 3 x 4 x 5 = 120 0!=1 (afspraak) Het aantal mogelijke routes in een netwerk neemt snel toe. Voorbeeld Bij 11 punten zijn er 10! = 3628800 mogelijke routes. Bij 21 punten zijn er 20! = 2432902008176640000 mogelijke routes. De zakenman weet pas zeker dat hij de kortste route heeft gevonden als hij alle mogelijk (n−1)! routes doorlopen heeft. Het aantal mogelijke routes bij n punten is . 2 Voorbeeld Stel dat je 1000 mogelijke routes per seconde kunt onderzoeken. Bij 21 punten heb je dan: 69 aantal routes= (21-1)!/2 = 1216451004088320000 zoektijd= 1216451004088320000 / 1000 = 1216451004088320 s = 1216451004088320 / (3600x24x365) = 38573408,3 jaar nodig! Zelfs met 1 miljoen computers heb je nog 38 jaar nodig. Kortom het is onmogelijk om alle routes te onderzoeken. Opdracht 13.2 Kun de -1 en /2 in de formules verklaren? Opdracht 13.3 Nederland heeft 31 steden met meer dan 100.000 inwoners (stand van 2016). https://nl.wikipedia.org/wiki/Lijst_van_grootste_gemeenten_in_Nederland Bereken hoe lang het duurt om alle routes tussen de 31 steden te berekenen. Eén route kost 0,001s. Geef het antwoord in jaren. 70 Een korte route zoeken, maar misschien niet de kortste. We weten nu dat het heel erg lang duurt om alle routes te onderzoeken. Zelfs als het aantal punten niet heel erg groot is. We concluderen dus dat het onmogelijk is om alle routes te onderzoeken. Is het dan toch mogelijk om een korte route in het netwerk te vinden? We moet accepteren dat die route waarschijnlijk niet de aller kortste route is. Er is heel veel onderzoek gedaan naar algoritmen die snel een oplossing voor het TSP vinden. We bespreken hieronder een mogelijk algoritme. Je zult aan het eind ook zien dat dit algoritme korte routes vind, die we nog kunnen verbeteren. Het Greedy-algoritme Vanuit het startpunt kies je steeds de kortste weg naar een punt waar je nog niet eerder geweest bent. Figuur 13.1 Opdracht 13.4 Bepaal de kortste route vanuit punt A met het Greedy-algoritme. Beschrijving van het algoritme: Greedy Stap Beschrijving 1 We kiezen een startpunt, dat is het actuele punt. 2 Het startpunt markeren we als 'gebruikt' of 'bezocht'. 3 Totale afstand = 0 4 Als alle punten bezocht zijn gaan we naar stap 10 5 We gaan steeds vanuit het actuele punt het dichtstbijzijnde punt kiezen dat 71 nog niet 'gebruikt' is. 6 Tel de afstand van het actuele punt naar het nieuwe punt bij de totale afstand 7 Het nieuwe punt wordt het actuele punt 8 Markeer het actuele punt als 'bezocht' 9 Ga naar stap 4 10 Einde Opdracht 13.5 Verwerk dit algoritme in een NSD. Opdracht 13.6 Bedenk een reden waarom dit algoritme misschien toch niet zo goed is al het in eerste instantie lijkt. Omdat je steeds probeert om grote afstanden te vermijden, heet dit algoritme het Greedy algoritme (greedy=gierig). Opdracht 13.7 Schrijf dit algoritme in Small Basic. Laat het programma 10 willekeurige punten kiezen. Het onderstaande codefragment kun je als start voor jouw uitwerking gebruiken. ' zet de punten in een lijst GraphicsWindow.width=450 GraphicsWindow.height=450 aantal=10 For teller=1 To aantal x[teller]=math.GetRandomNumber(400) y[teller]=math.GetRandomNumber(400) alinroute[teller]=0 GraphicsWindow.DrawEllipse(x[teller]-1,y[teller]-1,3,3) endfor 72 Het Greedy algoritme geeft wel eens hele mooie oplossingen zoals in de afbeelding hieronder. Figuur 13.2 Maar het gaat ook wel eens mis: Figuur 13.3 opdracht 13.8 Hoe kun je aan de route in figuur 13.3 zien dat dit zeker niet de kortste route is? 73 Als je het Greedy algoritme in een groot netwerk uitprobeert, geeft het best wel redelijke oplossingen, al zie al snel dat het veel beter moet kunnen. Er zitten onnodige lussen in de route en soms moet de handelsreiziger ineens naar een punt een heel eind verder op. Figuur 13.4: Het Greedy algoritme voor een netwerk met 200 punten Intermezzo: De langste route Stof tot nadenken: het is heel erg gemakkelijk om een heel erg lange route te vinden. Je gaat vanuit elk punt naar een punt dat het verst ligt (en nog niet bezocht is). Maar geeft dat ook gegarandeerd de langste route? 74 14 Worteltrekken We vinden het heel normaal dat je met een rekenmachine kunt worteltrekken. Maar hoe werkt dat eigenlijk? Is worteltrekken net zo eenvoudig als optellen of vermenigvuldigen? Stel dat je de wortel van een getal al weet, dan geldt het volgende: wortel ×wortel =getal Bijvoorbeeld: vier is de wortel van zestien, want 4×4=16 Je kunt dit ook schrijven als: getal =wortel wortel (1) Dit is een bijzonder geval van: getal =uitkomst deler (2) In het formule (1) zijn deler en uitkomst gelijk aan elkaar, in formule (2) hoeft dat niet zo te zijn. Stel nu dat je de wortel van 12 wilt berekenen. Neem dan voor deler een willekeurig getal. De waarde van de uitkomst is dan als volgt te berekenen. Voorbeeld getal = 12, deler = 2 → uitkomst = getal 12 = =6 deler 2 Zowel 2 als 6 zijn niet de juiste waarde van de wortel, maar we weten wel dat de waarde van de wortel tussen 2 en 6 in ligt. Daarom nemen we als nieuwe gok voor de deler het gemiddelde van 2 en 6. Voorbeeld getal = 12, deler =uitkomst = 2+6 =4 → 2 uitkomst = getal 12 = =3 deler 4 Ook 3 en 4 zijn niet de waarde van de wortel van 12. Maar de wortel van 12 ligt wél tussen 3 en 4 in. De volgende benadering van de wortel van 12 is weer het gemiddelde van 3 en 4. 75 De eerste stappen kunnen we als volgt in een tabel samenvatten: Stap deler uitkomst 1 2 6 2 4 3 3 3,5 3,43 4 3,46 ... Je ziet misschien al dat de waarde van getal in de vierde stap al dicht is de buurt van de werkelijke waarde van √ 12 komt: 3,4641016151377545870548926830117. Nu zijn er twee belangrijke vragen: – Maakt het uit met welk getal je begint? Je zou dit eigenlijk moeten bewijzen, maar wij zijn nu tevreden met het antwoord: Je mag beginnen met elk getal, zolang je maar met een positief getal begint. Nul en negatieve getallen mag je niet als startgetal gebruiken. Delen door nul kan immers niet en de afspraak is dat de wortel van een getal altijd positief is. – Wanneer kunnen we stoppen? In principe kun je dat zelf bepalen, maar je kunt de nauwkeurigheid vastleggen door te stoppen als het absolute verschil van getal en uitkomst kleiner is dan een kleine macht van 10. We stoppen dus bijvoorbeeld als: ∣uitkomst−deler∣< 0,000001 of als ∣uitkomst∗uitkomst− getal∣< 0,000001 De strepen || betekenen dat je van de waarde tussen de strepen de positieve waarde neemt: je laat het minteken weg. In plaats van 0,00001 kun je ook een andere macht van 10 nemen: 0,1 – 0,01 – 0,001 etc. In het onderstaande Small Basic-programma is dat uitgewerkt. 'worteltrekken TextWindow.Write("Geef het getal waarvan de wortel: ") getal= TextWindow.ReadNumber() 'kies een willekeurige deler (in dit voorbeeld 1) deler=1 uitkomst = getal / deler stap=1 'als het verschil kleiner is dan Grens, vinden we de benadering nauwkeruig genoeg ' 10^-10 --> Math.Power(10,-10) grens = Math.Power(10,-10) While math.Abs(uitkomst*uitkomst - getal) > grens 'Haal hieronder de apostrofe weg als je de tussenstappen wilt bekijken. 'TextWindow.WriteLine(uitkomst) stap=stap+1 deler = (deler+uitkomst)/2 uitkomst = getal / deler 76 endwhile 'schrijf de exacte waarde van Small Basic (dit is natuurlijk ook een benadering) textwindow.WriteLine("de wortel is : "+Math.SquareRoot(getal)) 'schrijf waarde die onze routine berekende TextWindow.WriteLine("de benadering is : "+uitkomst) 'schrijf het verschil verschil=Math.SquareRoot(getal) - uitkomst TextWindow.WriteLine("het verschil is : "+ verschil) 'schrijf het aantal benodige stappen TextWindow.WriteLine("aantal stappen TextWindow.Pause() : "+stap) opdracht 14.1 Onderzoek aan de hand van het voorbeeldprogramma of het inderdaad niets uitmaakt welk startgetal je voor de deler neemt (zolang het groter dan nul is). opdracht 14.2 Kies een getal tussen 100 en 1000000. Neem 1 als startwaarde voor de deler. Doorloop het algoritme handmatig tot een nauwkeurigheid van Maak de tabel indien nodig zelf langer. stap deler 10−6 uitkomst 77 opdracht 14.3 Kies 5 verschillende getallen tussen 0 en 1 miljard. Kies een nauwkeurigheid (bijvoorbeeld 10−6 ). Pas de nauwkeurigheid in het programma aan (of schrijf een extra regel die om de nauwkeurigheid vraagt). Bepaal hoeveel stappen het algoritme voor elke getal nodig heeft. Vul de gekozen getallen en het aantal stappen in de onderstaande tabel in. De laatste kolom heb je bij opdracht 14.4 nodig. Getal Nauwkeurigheid Aantal stappen Aantal eenvoudige bewerkingen Hoe complex is worteltrekken vergeleken met eenvoudige rekenkundige bewerkingen zoals +-*/? De kern van het voorbeeld-programma staat hieronder. uitkomst = getal / deler stap=1 'als het verschil kleiner is dan Grens, vinden we de benadering nauwkeruig genoeg ' 10^-10 --> Math.Power(10,-10) grens = Math.Power(10,-10) While math.Abs(uitkomst*uitkomst - getal) > grens 'Haal hieronder de apostrofe weg als je de tussenstappen wilt bekijken. 'TextWindow.WriteLine(uitkomst) stap=stap+1 deler = (deler+uitkomst)/2 uitkomst = getal / deler endwhile In plaats van Math.Power(10,-10) hadden we ook gewoon 0.0000000001 kunnen schrijven. Je hóeft hier dus geen macht uit te rekenen. In plaats van While math.Abs(uitkomst*uitkomst - getal) > grens Kun je ook While math.Abs(uitkomst - deler) > grens 78 Schrijven. Voor de lus staat 1 deling (uitkomst=getal/deler) In de controle van de lus staat een vermenigvuldiging (uitkomst*uitkomst-getal>grens) of aftrekking (uitkomst-deler > grens). In de lus is de regel stap=stap+1 alleen nodig als je later wilt kunnen zeggen hoeveel stappen er nodig waren. Voor de berekening van de wortel is deze regel niet nodig. De andere twee regels zijn wel essentieel. In deze regels staat een optelling en twee delingen. Het aantal rekenkundige bewerkingen is dus: aantal = 1 + stappen*4, als math.Abs(uitkomst - deler) > grens Of zelfs aantal = 1 + stappen*5, als math.Abs(uitkomst * uitkomst- getal) > grens Afhankelijk van het aantal stappen zijn er 1, 5, 9, 13, of meer eenvoudige rekenkundige bewerkingen nodig om de wortel te berekenen. Een wortelberekening duurt dus 'lang' vergeleken met een eenvoudige optelling. Voor de berekening van y=2⋅x⋅x +6⋅x−3 zijn 5 eenvoudige bewerkingen nodig. Dat is vaak nog minder dan 1 wortelberekening. opdracht 14.4 Hoe complex is worteltrekken vergeleken met eenvoudige bewerkingen (+-*/)? Vul in de tabel van opgave 14.3 het aantal eenvoudige rekenkundige bewerkingen in dat nodig was om wortel van elk getal te berekenen. 79 15 Eerlijk delen Al sinds de prehistorie is er een manier bekend waar men een hoeveelheid eerlijk kon verdelen tussen twee mensen. De één verdeelt de hoeveelheid, de ander mag als eerste een helft kiezen. http://nl.wikipedia.org/wiki/Kiezen_of_delen Degene die deelt zal zo precies mogelijk delen, omdat hij weet dat de ander zeker de grootste portie zal kiezen. Met drie personen gaat het op déze manier niet goed. Persoon A deelt in drie delen. Persoon B kiest als eerste een deel. Het kan dan zijn dat hij het stuk neemt dat persoon C het liefste had. C krijgt misschien zelfs een stuk dat minder dan een-derde deel is. Toch is er wel een oplossing. We leggen dat uit aan de hand van een cake. Persoon A beweegt het mes over de cake tot iemand 'stop' roept. Diegene vindt blijkbaar dat het mes 1/3e cake gaat afsnijden. De twee overgebleven personen verdelen het overgebleven deel daarna met 'kiezen of delen'. Opdracht 15.1 Waarom zijn de twee personen die het laatste deel verdelen tevreden met het deel dat persoon A krijgt? {todo: n>3} Bron: http://www-i1.informatik.rwth-aachen.de/~algorithmus/algo43.php Verder lezen (in het Engels) http://www.cs.cmu.edu/~arielpro/mfai_papers/BT95.pdf 80 16 Quicksort Snel kunnen sorteren is een zegen. Het kan dus geen kwaad om nóg maar eens naar een sorteer-algoritme te kijken. Aan de hand van de volgende ongesorteerde rij leggen we uit hoe het algoritme werkt. 8 5 1 6 3 7 2 4 We kiezen – willekeurig – een spil (Engels: pivot). In dit voorbeeld kiezen we steeds het laatste getal van een rij als spil. We kiezen dus 4 als spil. 8 5 1 6 3 7 2 [4] Nu gaan we kijken welke getallen kleiner zijn dan de spil, en welke getallen groter zijn dan de spil. We doorlopen de getallen voor de spil van voor naar achter. Als het getal groter dan de spil is, zetten we het achteraan in de rij. Als het kleiner dan de spil is, laten we het staan. De volgorde van de getallen verandert daarbij niet. Als alle getallen doorlopen zijn hebben we de volgende rij. 1 3 2 [4] 8 5 6 7 Er zijn nu twee deelrijen ontstaan. Voor de [4] staan de getallen kleiner dan 4, na de [4] staan de getallen groter dan 4. We kunnen deze twee deelrijen nu onafhankelijk van elkaar verder sorteren. Van elke deelrij nemen we weer het laatste getal als spil (hoewel je in principe elk getal als spil mag kiezen). 1 3 [2] [4] 8 5 6 [7] [7] 8 Nu sorteren we de twee deelrijen en krijgen: 1 [2] 3 [4] 5 6 We hebben nu vier deelrijen over {1}, {3}, {5,6} en {8}. Ook die sorteren we door het laatste getal als spil te kiezen. Met {1} zijn we snel klaar. Als we 1 als spil kiezen is er verder niets meer te sorteren. Ook met {3} zijn we zo klaar: er hoeft niets meer te gebeuren. Hetzelfde geldt voor {8}. In de rij {5,6} kiezen we 6 als spil. Aangezien 5 kleiner is dan [6] verandert er niets aan deze rij. [1] [2] [3] [4] 5 [6] [7] 81 [8] Na het sorteren van {5,6} is alleen 5 nog niet aan bod geweest. Omdat het een rijtje van 1 getal is, zijn we klaar. [1] [2] [3] [4] [5] [6] [7] [8] Nu weten we zeker dat de rij gesorteerd is. Dit algoritme heet Quicksort. Zonder alle uitleg erbij kunnen we stappen van hierboven op de volgende manier kort noteren: stap 0: stap 1: stap 2: stap 3: 8 1 1 [1] 5 3 [3] [3] 1 [2] [2] [2] 6 [4] [4] [4] 3 8 5 [5] 7 5 [6] [6] 2 6 8 [8] 4 [7] [7] [7] In elke stap worden de nieuwe spillen met haken genoteerd. We zijn al na 3 stappen klaar! AFSPRAAK Bij het voeren van Quicksort moet een spil gekozen worden. We spreken af dat je aan het begin zeg hoe je de spil kiest. Dat doe je daarna in elke stap op de afgesproken manier. In het voorbeeld hierboven nemen we het laatste getal in een (deel-)rij als spil. Maar je mag ook het eerste nemen. Het middelste getal bestaat natuurlijk niet altijd: als er een even aantal getallen in een (deel-)rij staan, is er geen middelste. Er zijn wel oplossingen te verzinnen: spil=⌊ 1+ aantal ⌋ 2 aantal is natuurlijk het aantal getallen in de rij. Voorbeeld 1 1+8 1+7 ⌋=⌊ ⌋=⌊4⌋=4 2 2 In een rij van 7 getallen is het 4e getal het middelste getal. spil=⌊ Als er 7 getallen in de rij staan kies je 8 2 5 [2] 9 1 3 Voorbeeld 2 1+8 1+7 ⌋=⌊ ⌋=⌊4⌋=4 2 2 In een rij van 10 getallen is het 5e getal het getal links van het midden. Als er 10 getallen in de rij staan kies je 7 3 5 2 [1] 4 8 6 9 10 82 spil=⌊ Opdracht 16.1 Sorteer de volgende rij getallen met quicksort: 8 9 14 16 11 12 13 10 20 1 2 3 17 18 19 4 7 5 6 15 Opdracht 16.2 Maak met het programma schudden met garantie een rij met de getallen 1 tot en met 25 die door elkaar staan. Neem de getallen over op papier en sorteer de de getallen met Quicksort. Stuur deze uitwerking naar [email protected]. Hoeveel stappen zijn het in het ergste geval nodig voor Quicksort? Als de rij al gesorteerd is ontstaat er in elke stap 1 deelrij: stap 0: stap 1: stap 2: stap 3: stap 4: stap 5: stap 6: stap 7: stap 8: 1 1 1 1 1 1 1 1 [1] 2 2 2 2 2 2 2 [2] [2] 3 3 3 3 3 3 [3] [3] [3] 4 4 4 4 4 [4] [4] [4] [4] 5 5 5 5 [5] [5] [5] [5] [5] 6 6 6 [6] [6] [6] [6] [6] [6] 7 7 [7] [7] [7] [7] [7] [7] [7] 8 [8] [8] [8] [8] [8] [8] [8] [8] Er zijn dan dus 8 stappen nodig. Als de rij n getallen lang is, zijn er in dat geval n stappen nodig. Opvallend is dus, dat er veel stappen nodig zijn als de rij al gesorteerd is. Opdracht 16.3 In welke andere situatie zijn er bij n getallen ook n stappen nodig? 83 In welke situatie zijn de minste stappen nodig? Als in elke stap elke deelrij in twee gelijke deelrijen opgesplitst wordt zijn we het snelste klaar: Opdracht 16.4 Schrijf een quicksort-programma in Small Basic of Lazarus. In de voorbeelden hierboven hebben we steeds getallenrijen bekeken, waarin elk getal maar een keer voorkomt. Quicksort werkt met elke rij, we spreken af dat we getallen die even groot zijn als de spil vóór de spil zetten. 1112191 1 1 1 2 1 9 [1] 1 1 1 [1] [1] 2 [9] 1 1 [1] [1] [1] [2] [9] 1 [1] [1] [1] [1] [2] [9] [1] [1] [1] [1] [1] [2] [9] 84 17 Cellulaire automaten voor speelwerelden Cellulaire automaten kunnen ook worden gebruikt om willekeurig speelwerelden te laten maken. Eerst maken we elke cel met een bepaalde kans blauw. Als we 35% kans op blauw nemen krijgen ze een plaatje zoals hieronder. 85 Daarna berekenen we 5 generaties met de regel B45678/S34. We krijgen dan een plaatje zoals hieronder. De blauwe gebieden kunnen bergen, meren of bossen zijn. Als je de kans op blauw in de eerste stap een beetje vergroot tot 45%, ontstaan er na 5 generaties grotten. Tussen de blauwe rotsen zie je witte gangen. 86 De code ziet er als volgt uit: sizeX = 75 sizeY = 75 startKans = 35 '35-> blauw zijn bergen of meren 45-> wit zijn grotten GraphicsWindow.width= sizeX*5+40 GraphicsWindow.height= sizeY*5+40 While "true" Initieer() For teller=1 to 5 'haal in de volgende regel het aanhalingsteken weg als je elke stap van 'de berekening wilt zien teken() VolgendeRonde() EndFor teken() Program.Delay(1000) endwhile ' Vul de tabel met blauwe cellen Sub Initieer For x=1 To sizeX For y=1 To sizeY getal=math.GetRandomNumber(100) If getal<startKans Then cel[x][y]=1 Else cel[x][y]=0 endif EndFor EndFor EndSub 'Bereken de volgende generatie Sub VolgendeRonde For x=2 To sizeX-1 For y=2 To sizeY-1 ' Tel het aantal blauwe cellen rond deze cel aantal=0 For dx=-1 To 1 For dy=-1 To 1 If (dx=0) And (dy=0) Then 'doe niets else aantal=aantal+cel[x+dx][y+dy] endif EndFor EndFor newcel[x][y]=0 'survive or birth If ((cel[x][y]=1) and (aantal>4)) Or ((cel[x][y]=0) and (aantal>5)) Then newcel[x][y]=1 endif 'birth EndFor endfor For x=1 To sizeX 87 For y=1 To sizeY cel[x][y]=newCel[x][y] EndFor EndFor EndSub ' Teken het veld op het scherm Sub teken For x=1 To sizeX For y=1 To sizeY If cel[x][y]=1 Then GraphicsWindow.BrushColor="blue" GraphicsWindow.FillRectangle(20+x*5,20+y*5,5,5) Else GraphicsWindow.BrushColor="white" GraphicsWindow.FillRectangle(20+x*5,20+y*5,5,5) endif EndFor EndFor EndSub 88 18 De som is... Je wilt een paar getallen hebben die samen 50 zijn. Hoe zou je dat aan kunnen pakken? Allereerst moet je bedenken uit welke getallen je mag kiezen. We kiezen voor de getallen 1 tot en met 10. Een eerste poging ziet er als volgt uit: // dit programma werkt niet correct totaal:=0; repeat getal:=randomrange(1,11); writeln(getal); totaal:=totaal+getal; until totaal=50; Het probleem is dat je maar getallen bij elkaar blijft tellen en hoopt dat je op 50 uitkomt. Dat is geen probleem, zolang het totaal kleiner dan 50 is. Maar als het totaal groter dan 50 geworden is, stopt het programma nooit meer... We moeten dus controleren of het totaal niet te groot wordt. Alleen als het nieuwe totaal (=totaal+getal) kleiner of gelijk aan 50 is, voegen we het getal toe // dit programma werkt WEL correct totaal:=0; repeat getal:=randomrange(1,11); if totaal+getal<=50 then begin writeln(getal); totaal:=totaal+getal; end; until totaal=50; We zorgen er dus voor dat het programma aan het eind getallen kiest die niet te groot zijn. We schrijven de getallen meteen op het scherm, maar het programma weet achteraf niet meer welke getallen er gekozen werden. Dat kunnen we wel toevoegen aan het programma. // dit programma onthoudt de gekozen getallen totaal:=0; aantal:=0; repeat getal:=randomrange(1,11); if totaal+getal<=50 then begin aantal:=aantal+1; rij[aantal]:=getal; totaal:=totaal+getal; end; until totaal=50; 89 Nu het programma de getallen onthoudt, kunt achteraf iets met de getallen doen. Je kunt ze gewoon op het scherm schrijven, maar dat kon het eenvoudige programma ook al. Nu je de getallen onthouden hebt, kun je de getallen ook eerst in volgorde zetten voor je ze op het scherm schrijft. // dit programma onthoudt de gekozen getallen totaal:=0; aantal:=0; repeat getal:=randomrange(1,11); if totaal+getal<=50 then begin aantal:=aantal+1; rij[aantal]:=getal; totaal:=totaal+getal; end; until totaal=50; // sorteer oplopend for I:=1 to aantal-1 do for J:=I+1 to aantal do if rij[I]>rij[J] then begin T:=rij[I]; rij[I]:=rij[J]; rij[J]:=T; end; // schrijf de getallen op het scherm for I:=1 to aantal do writeln(rij[I]); We hebben er eerder voor gezorgd dat het programma getallen kiest die niet te groot zijn. Dat kan ook op een andere manier. De functie min geeft het minimum van twee getallen (de laagste waarde): function min(a, b:integer):integer; begin if a<b then min:=a else min:=b; end; 90 We kunnen de functie min gebruiken om het programma op een andere manier te schijven. // dit programma onthoudt de gekozen getallen totaal:=0; aantal:=0; repeat // hoeveel mag er nog maximaal bij (wat is over)? over:=50-totaal; // als er minder dan 10 bij mag, passen we de bovengrens aan getal:=randomrange(1,min(over+1,11)) ); aantal:=aantal+1; rij[aantal]:=getal; totaal:=totaal+getal; end; until totaal=50; We gaan het programma nu aanpassen, zodat het alleen getallen kiest uit een verzameling die we vooraf bepalen. We willen bijvoorbeeld alleen de getallen (1,3,9 en 11 gebruiken). We vullen eerst een array met de getallen die gebruikt mogen worden. // vullen van een array met keuzes keuze[1]:=1; keuze[2]:=3; keuze[3]:=9; keuze[4]:=11; aantalkeuzes:=4; Bij het kiezen van een getal moeten we er nu voor zorgen dat een getal uit het array keuze gekozen wordt. // dit programma onthoudt de gekozen getallen totaal:=0; aantal:=0; repeat // kies een van de vier getallen welke :=randomrange(1,5); getal:=keuze[welke]; if totaal+getal<=50 then begin aantal:=aantal+1; rij[aantal]:=getal; totaal:=totaal+getal; end; until totaal=50; 91 19 Vuur en vlam Je kunt een cellulaire automaat ook gebruiken om vlammen te imiteren. Omdat het berekenen van de kleuren in de vlam heel veel rekenwerk kost, laat het voorbeeldprogramma in Small Basic een hele kleine vlam zien. We gebruiken daarvoor een rechthoekig vlak met cellen. In andere programmeertalen gaat het rekenwerk sneller. Een Lazarus-programma kun je ook op de website vinden. Je kunt de grootte van de vlam aanpassen voor de volgende variabelen een groter getal in te vullen. breedte=50 hoogte=50 Elke cel heeft een waarde, minimaal 0 maximaal 255. Bij elk getal hoort een kleur. Er zijn dus 256 kleuren. Bij het tekenen wordt een pallet (kleurtabel) gebruikt: 0 is zwart, 255 is wit. Daartussen verloopt de kleur langzaam: van zwart → (32) → blauw → (32) → rood → (32) → geel → (160) → wit Tussen haakjes staat het aantal stappen dat voor het kleurverloop gebruikt wordt. voorbeeld Er worden 32 stapjes gebruik voor het kleurverloop van zwart naar blauw. 92 Je kunt de complete kleurentabel hieronder zien. Er staan 256 kleuren naast elkaar. Waarschijnlijk herken je hierin de kleuren van een vlam al! Sub MaakKleuren For i=1 To 32 kleur[i]=GraphicsWindow.GetColorFromRGB(0,0,i*2) kleur[i+32]=GraphicsWindow.GetColorFromRGB(i*8,0,64-(i*2)) kleur[i+64]=GraphicsWindow.GetColorFromRGB(255,i*8,0) kleur[i+96]=GraphicsWindow.GetColorFromRGB(255,255,i*4) kleur[i+128]=GraphicsWindow.GetColorFromRGB(255,255,64+i*2) kleur[i+160]=GraphicsWindow.GetColorFromRGB(255,255,128+i*4) kleur[i+192]=GraphicsWindow.GetColorFromRGB(255,255,192+i) kleur[i+224]=GraphicsWindow.GetColorFromRGB(255,255,224+i) endfor endsub De vlam is een animatie. Voor elk beeldje wordt voor elk pixel een nieuwe kleurwaarde berekend. De cellen van de vlam worden vanaf de tweede regel tot de op een na laatste regel van links naar rechts doorlopen. 'bereken volgende stap van de vlam For y=2 To hoogte-1 For x=1 To breedte vuur[x][y-1]=Math.Round((vuur[x][y]+vuur[x][y-1]+vuur[x][y+1])/3) graphicswindow.SetPixel(x,y,kleur[vuur[x][y]]) EndFor EndFor De volgende regel bepaalt de nieuwe kleur: vuur[x][y-1]=Math.Round((vuur[x][y]+vuur[x][y-1]+vuur[x][y+1])/3) De nieuwe kleurwaarde is het gemiddelde van de drie cellen die boven elkaar liggen. Round zorgt dat het getal afgerond wordt. vuur[x][y-1] vuur[x][y] vuur[x][y+1] 93 voorbeeld Stel dat de drie kleurwaarden 100, 110 en 150 zijn. Dan wordt de nieuwe kleurwaarde: 100+110+150 = 360 → 360/3 = 120. We hoeven nu toevallig niet af te ronden. 100 110 150 Als we alleen dit zouden doen zou de vlam zwart blijven. Alle cellen hebben aan het begin van het programma namelijk de waarde 0. Daardoor is de waarde in de volgende stap ook weer 0 ( 0+0+0 = 0 → 0/3=0). We lossen dat op door de vlam van onderaf aan te wakkeren. Precies zoals dat bij een echte vlam ook gebeurt. De cellen op de onderste regel worden willekeurig met witte en zwarte cellen te vullen. Dit zijn de kleurwaarden 255 en 0. 'zorg dat er onderin de vlam vuur blijft om de vlam aan te wakkeren v=255 For x=1 To breedte r=math.GetRandomNumber(14) If r>10 Then If v=0 Then v=255 Else v=0 endif endif vuur[x][hoogte]=v graphicswindow.SetPixel(x,y,kleur[vuur[x][y]]) endfor 94 Het is bij een animatie beter om het tekenen uit te stellen tot de hele afbeelding berekend is. Dat moet door aan het eind alle pixels van de volgende afbeelding te tekenen. 'teken de vlam For y=1 To hoogte For x=1 To breedte graphicswindow.SetPixel(x,y,kleur[x][y]) vuur[x][y-1]=math.round(vuur[x][y]+vuur[x][y-1]+vuur[x][y+1])/3 EndFor EndFor Als je echter aandachtig gelezen hebt, heb je gezien dat bij het berekenen van de kleur en bij het aanwakkeren van de vlam, meteen een pixels getekend wordt. Dat gebeurt in de twee regels die vetgemaakt zijn. We kleuren de pixels meteen, omdat het berekenen van de vlam zo lang duurt. We hoeven ook niet te wachten tot 40ms voorbij is, omdat het berekenen van de volgende afbeelding veel langer dan 40ms duurt. Als je deze cellulaire automaat vergelijkt met de Life vallen een paar verschillen op. Life Vlammen Start Er worden in het begin een paar cellen aangezet. In elke ronde worden een paar cellen op de onderste regel willekeurig wit of zwart. Berekenen nieuwe waarde cel Alle cellen rond de cel worden gebruikt voor de berekening. Alleen de cellen boven en onder de cel worden in de berekening gebruikt. Kleur op het scherm Er zijn maar twee waarden mogelijk 0=wit, 1=zwart Er zijn 256 verschillende kleurwaarden mogelijk. De 'vertaling' van een getal naar een kleur gebeurt door middel van een pallet (tabel die aan een getal een kleurcode koppelt). 95 Bijlagen NRC, 27 november 2012 wie het eerst drukt, het eerst de lift Jeroen de Jong ergert zich allang aan kantoren waar ’s ochtends alle liften helemaal van de bovenste verdieping moeten komen, terwijl iedereen de werkdag beneden begint. Hij onderzocht hoe liften hun route het best kunnen plannen, ervan uit gaande dat de route tussentijds toch weer verandert. Woensdag 28 november promoveert De Jong, inmiddels werkzaam bij radarfabrikant Thales, aan de TU Delft. Hoe komt iemand terecht in de liftwiskunde? „Ik ben zo iemand die zich opwindt over wachttijden bij stoplichten en liften. ‘Laat mij maar even achter de knoppen’, denk ik dan. Na een studie Kunstmatige Intelligentie wilde ik iets praktisch doen, dus heb ik stage gelopen bij de Zwitserse liftfabrikant Schindler. Daar is dit onderzoek uit voortgekomen.” Hoe moeilijk kan het zijn? Je drukt op de knop, en de lift komt. „Het is een extreem lastig probleem. Stel, jij drukt op de begane grond. Een lift van boven gaat onderweg. Maar intussen willen zes mensen op de derde verdieping naar de tiende. De lift is daar dichterbij en zij zijn met zijn zessen, dus het is efficiënter om eerst die mensen naar boven te brengen. Maar dan wil ook iemand van de tiende omhoog. Dat zou ook weer efficiënter zijn, maar intussen sta jij dan al een onaanvaardbaar lange tijd te wachten. „Algoritmen in de liftbesturing, die vaak meerdere liften bedienen, moeten dus de beste route kiezen. Met maar een paar liften en een paar wachtenden in een beetje kantoorgebouw explodeert het aantal mogelijke routes al snel. Alle routes doorrekenen kost miljarden jaren, terwijl een lift in een seconde een beslissing moet nemen. „Bovendien is het ook nog eens een dynamisch probleem. Terwijl de lift bezig is, bestellen mensen nieuwe ritten. Hoe meer je een oude taak uitstelt omdat het even niet handig is, hoe groter de kans is dat er in de tussentijd nog weer iets bijkomt.” Wat valt eraan te doen? „Ik heb verschillende liftalgoritmen grondig vergeleken, en uitgetest op een databestand dat ik van Schindler gekregen heb: elfduizend echte passagiersbewegingen op een drukke doordeweekse dag in een onbekend gebouw in Parijs. „Het blijkt toch te lonen om de planningshorizon zo kort mogelijk te houden, en tot op zekere hoogte eerst te doen wat het eerst komt. Dat mag op de korte termijn wel eens onhandig uitpakken, zoals in het eerdere voorbeeld, maar de kans dat er intussen iets lastigs tussendoorkomt neemt wel weer af. „Nóg een optie is om op grond van eerdere ervaring te schatten hoe groot de kans op tussentijdse veranderingen is, en daar dan ook weer rekening meer te houden. Dat scheelt zo’n drie procent in reis- en wachttijd. Dat lijkt heel weinig, maar is best veel in de liftenwereld, waar het er echt om gaat het theezakje helemaal uit te knijpen.” Dus binnenkort werken alle liften zo? „Nou, misschien nu al. De liftenbranche is een nogal gesloten wereld, waar een paar fabrikanten de dienst uitmaken, en nauwelijks informatie naar buiten brengen. Zelfs om het gegevensbestand van mijn oude stage-adres Schindler heb ik bijna een jaar moeten zeuren. „Alle fabrikanten hebben flinke onderzoeksafdelingen, dus ik denk eerlijk gezegd dat ik dubbel werk heb zitten doen. Maar voorzover ik kon nagaan is dit wel de eerste keer dat het ook openlijk beschreven is.” Wat voor andere liftinnovaties kunnen we nog verwachten? „Een systeem waarin je meteen je bestemmingsverdieping kiest, terwijl je liftnummer pas op het laatst bekend wordt, kan nog eens 10 tot 15 procent wachttijd schelen. Zoiets kan vrij gemakkelijk ingevoerd worden. „En Schindler heeft een systeem aangekondigd waarbij liften ook opzij bewegen, met hulp van magnetische motoren zoals magnetische zweeftreinen die ook gebruiken. Dan zou je, net als op een snelweg, de binnenste schachten gebruiken voor de korte ritten, en de buitenste voor de snelle lange afstanden. Maar het is nog wel afwachten of liftpassagiers opzij bewegen niet te eng vinden.” 96 Uitwerkingen opdracht 1.1 Appeltaart [2] 1 Schil de appels en snijd ze in parten. Besprenkel ze met citroensap en bestrooi ze met vanillesuiker en kaneel naar smaak. Vet de bakvorm in en verwarm de oven voor op 175-180 graden (hetelucht 160). 2 Mix de roomboter op lage stand romig. Voeg de eieren en appeltaartmix toe en meng voorzichtig. Mix het geheel dan 2 minuten op de hoogste stand. 3 Giet het beslag in de vorm en strijk het glad. Verdeel de appelparten er overheen (zorg dat ze de rand niet raken!) en eventueel nog rozijnen of nootjes. Zet de vorm onder het midden van de oven en bak de appeltaart in ca. 55 minuten. Controleer daarna of de taart gaar is, haal hem meteen uit de vorm en laat hem afkoelen. Lekker met een toef slagroom! Bron: http://appeltaarten.web-log.nl/ 97 opdracht 2.1 doen opdracht 2.2 doen opdracht 2.3 TextWindow.Writeline("We beginnen vanuit (0,0) te lopen over een rooster") textwindow.writeline("Voer de bestemming (x,y) in.") TextWindow.Write("Geef x:") eindx=textwindow.ReadNumber() TextWindow.Write("Geef y:") eindy=textwindow.ReadNumber() ' omdat we met stappen van lengte 10 lopen nemen we 10*x en 10*y als eindpunt eindx=eindx*10 eindy=eindy*10 GraphicsWindow.width=300 GraphicsWindow.height=300 ' markeer de bestemming met een stip en een rondje GraphicsWindow.DrawEllipse(100+eindx-3,100+eindy-3,7,7) GraphicsWindow.SetPixel(100+eindx,100+eindy,"red") stappen=0 x=0 y=0 While "true" hoek=Math.GetRandomNumber(4)*90 'math.round is nodig om de hoeken op hele getallen af te ronden. nieuwx=x+Math.Round(10*math.Cos(Math.GetRadians(hoek))) nieuwy=y+Math.Round(10*math.Sin(Math.GetRadians(hoek))) GraphicsWindow.title=math.round(nieuwx)+","+math.round(nieuwy) GraphicsWindow.DrawLine(100+x,100+y,100+nieuwx,100+nieuwy) x=nieuwx y=nieuwy stappen=stappen+1 98 If (x=eindx) And (y=eindy) Then GraphicsWindow.ShowMessage("klaar in "+stappen+" stappen","") Program.End() endif Program.Delay(1) endwhile opdracht 3.1 Voor jaar=2014 vind je de volgende waarden: G=1 C=21 X=3 Y=1 Z=2504 E=29 N=45 P=51 P>31, dus Pasen valt op 51-31=20 april 2014 opdracht 3.2 TextWindow.WriteLine("Bereken de Paasdatum met de methode van Gauss") TextWindow.WriteLine("=============================================") TextWindow.Write("Voor welk jaar wil je de datum van Pasen berekenen? ") jaar=textwindow.ReadNumber() 'gulden getal g=math.Remainder(jaar,19)+1 TextWindow.WriteLine("g="+g) 'eeuwdeel c=math.floor(jaar/100)+1 TextWindow.WriteLine("c="+c) 'correctie voor schrikkeljaren x=math.Floor(c*3/4)-12 TextWindow.WriteLine("x="+x) 'maancorrectie y=(c*8+5)/25 - 5 'LET OP: hier moet blijkbaar omlaag afgerond worden 'anders komt er in 1991 1,6 uit 99 y=math.Floor(y) TextWindow.WriteLine("y="+y) 'zoek de zondag z=math.Floor(jaar*5/4)-x-10 TextWindow.WriteLine("z="+z) 'epacta e=Math.remainder(11*g+20+y-x,30) TextWindow.WriteLine("e="+e) 'volle maan n=44-e If n<21 Then n=n+30 endif TextWindow.WriteLine("n="+n) 'bepaal de zondag na de volle maan p=n+7-math.Remainder(z+n,7) TextWindow.WriteLine("p="+p) 'bepaal de datum If p>31 Then p=p-31 TextWindow.WriteLine("Pasen valt op "+p+" april in "+jaar) Else TextWindow.WriteLine("Pasen valt op "+p+" maart in "+jaar) endif 100 opdracht 4.1 inleveropdracht voor een micropunt opdracht 4.2 'schudden TextWindow.Writeline("Demonstratieprogramma: Schudden") 'zet de getallen 1 tot en met 52 in een array for teller=1 To 52 kaart[teller]=teller endfor 'lees in hoe vaak de kaarten verwissel moeten worden TextWindow.Write("Hoevaak moeten de kaarten verwisseld worden? ") aantalverwisselingen=textwindow.ReadNumber() 'we verwisselen aantalverwisselingen keer twee willekeurige kaarten For teller=1 To aantalverwisselingen 'kies twee willekeurige kaarten 'let op: dit kan toevallig 2x dezelfde kaart zijn! x=math.getrandomnumber(52) y=math.getrandomnumber(52) 'verwissel de twee kaarten temp=kaart[x] kaart[x]=kaart[y] kaart[y]=temp endfor opdracht 4.3 Voeg bijvoorbeeld de volgende code na de code van 3.2 in. '4.3 'tellen hoeveel kaarten niet verwisseld zijn aantal=0 For teller=1 To 52 'als de kaart niet op een andere plek staat maken we 'aantal' één hoger If kaart[teller]=teller Then aantal=aantal+1 EndIf endfor TextWindow.WriteLine("Er staan nog "+aantal+" kaarten op dezelfde plek") TextWindow.WriteLine("Het aantal kaarten dat niet verplaatst werd is:"+aantal+" (dat is "+aantal*100/52+"%)") 101 opdracht 5.1 doen: micropunt voor een video-opname opdracht 5.2 inleveropdracht: micropunt opdracht 5.3 Na het aanklikken van (1,1) staat alleen deze cel in de lijst L B De lijst bevat (1,1). We nemen (1,1) uit de lijst De aangrenzende cellen van (1,1) bevatten 0 bommen. We zetten (1,2), (2,2) en (2,1) in de lijst. (1,1) markeren we met een 0. 0 L L L B De lijst bevat (1,2), (2,2), (2,1) We nemen (2,1) uit de lijst De aangrenzende cellen van (2,1) bevatten 0 bommen. We zetten (3,1), (3,2) en (2,1) in de lijst. (2,1) markeren we met een 0. 0 0 L L L L B De lijst bevat (1,2), (2,2), (3,1), (3,2) We nemen (3,2) uit de lijst De aangrenzende cellen van (3,2) bevatten 1 bom (3,2) markeren we met een 1. 102 0 0 L L L 1 B De lijst bevat (1,2), (2,2), (3,1) We nemen (3,1) uit de lijst De aangrenzende cellen van (3,1) bevatten 0 bommen (3,1) markeren we met een 0. 0 0 0 L L 1 B De lijst bevat (1,2), (2,2) We nemen (2,2) uit de lijst De aangrenzende cellen van (2,2) bevatten 1 bom (2,2) markeren we met een 1. 0 0 0 L 1 1 B De lijst bevat (1,2) We nemen (1,2) uit de lijst De aangrenzende cellen van (1,2) bevatten 0 bommen We zetten (1,3) en (2,3) in de lijst. (1,2) markeren we met een 0. 0 0 0 0 1 1 L L B De lijst bevat (1,3) en (2,3) We nemen (2,3) uit de lijst De aangrenzende cellen van (2,3) bevatten 1 bom (2,3) markeren we met een 1. 103 0 0 0 0 1 1 L 1 B De lijst bevat (1,3) We nemen (1,3) uit de lijst De aangrenzende cellen van (1,3) bevatten 0 bommen Er zijn geen aangrenzende cellen van (1,3) meer die niet eerder aan de beurt kwamen. (1,3) markeren we met een 0. 0 0 0 0 1 1 0 1 B De lijst is nu leeg. Daarom kunnen we stoppen opdracht 5.4 a (1,2), (2,1) en (2,2) b 0 1 0 2 0 1 1 2 B B B B B opdracht 5.5 Toon jouw werkende code voor een micropunt. De code van de docent krijg je tijdens de les waarop je deze moest inleveren. 104 opdracht 6.1 Aan het begin is de buffer gevuld met: 10,35,40,42,45,49,50,51,65 De lift begint op de 50e etage. Resterende buffer: 10,35,40,42,45,49,50,51,65 49 en 51 zijn even ver. Kies willekeurig een etage. We kiezen 49. Resterende buffer: 10,35,40,42,45,51,65 De dichtstbijzijnde etage is 51. Resterende buffer: 10,35,40,42,45,65 De dichtstbijzijnde etage is 45. Resterende buffer: 10,35,40,42,65 De dichtstbijzijnde etage is 42. Resterende buffer: 10,35,40,65 De dichtstbijzijnde etage is 40. Resterende buffer: 10,35,65 De dichtstbijzijnde etage is 35. Resterende buffer: 10,65 De dichtstbijzijnde etage is 10. Resterende buffer: 65 Er is nog maar één verzoek in de buffer. Dus reizen we nu naar etage 65. Nu is de buffer leeg. Ook als we in eerst naar etage 51 waren gegaan, was etage 65 pas als laatste aan de beurt gekomen. We kunnen dit lange verhaal korter opschrijven door in de eerste regel van een tabel de buffer te noteren in de tweede regel noteren we de volgorde. buffer 10 volgorde 8 35 40 42 45 49 50 51 65 7 6 5 4 2 1 3 9 De enige alternatieve volgorde is: buffer 10 volgorde 8 35 40 42 45 49 50 51 65 7 6 5 4 3 1 2 9 opdracht 6.2 In de buffer zit 10,65. De lift staat op etage 50. De dichtstbijzijnde etage is dus 65. Terwijl de lift naar boven gaat druk iemand op de etage 45. Er staat dan 10,45,65 Als de lift op de 65e etage geweest is staat er 10,45 in de buffer. De lift reist naar etage 45. Ondertussen drukt iemand op etage 65. Er staat dan weer 10,45, 65 in de buffer. Als de lift op etage 45 is geweest staat er 10,65 in de buffer. Dus reist de lift weer naar etage 65. Op deze manier kan etage 10 eeuwig in de buffer blijven staan. 105 Opdracht 6.3 De lift beweegt vanuit etage 80 omhoog. Eerst beweegt de lift naar etage 90. In de buffer staat dan nog: 10,30,50,70. Daarna beweegt de lift naar etage 100, ook al staat die niet in de buffer. Nu draait de richting van de lift om. Hij gaat nu naar beneden. De resterende etage worden nu in de volgende volgorde afgehandeld: 70,50,30,10. Met een tabel is dit ook overzichtelijk aan te geven. Omdat SCAN ook de onderste en bovenste etage aan kan doen moet je deze ook in de tabel opnemen. De asterisk bij etage 100 geeft aan de dat lift hier niet stopt. etage 0 10 30 50 70 90 100 volgorde niet 6 5 4 3 1 2* Opdracht 6.4 De lift handelt altijd eerst de verzoeken in één richting af. Als er dan nog verzoeken over zijn keert de lift om (via de bovenste of onderste etage). Elke etage komt dus een keer aan de beurt. Niemand staat dus eeuwig te wachten. Opdracht 6.5 Eerst sorteren we de buffer: 10,20,50,75,90 De lift begint op etage 35 en handelt alleen in opgaande richting verzoeken af. 50,75,90,100,0,10,20. De honderd en nul horen erbij omdat C-SCAN altijd doorgaat tot de uiterste etages. We bewegen dus eerst van 35 → 100: 65 etages. Dan naar de begane grond 100 → 0: 100 etages Dan 0 → 20 : 20 etages In totaal zijn er dus 65+100+20=173 etages bezocht. Dat is 175/5 = 34,8 etages per verzoek. C-LOOK zou dat zuiniger doen: 50,75,90,10,20 We bewegen dus eerst van 35 → 90: 55 etages. Dan naar de begane grond 90 → 10: 80 etages Dan 10 → 20 : 10 etages samen: 55+80+10 = 145 etage. Dat is 29 etages per verzoek. 106 Opdracht 6.6 inleveropdracht voor een micropunt Opdracht 6.7 inleveropdracht voor een micropunt opdracht 7.1 GraphicsWindow.Show() ' teken een rooster for waarde=0 To 100 step 10 GraphicsWindow.DrawLine(10,10+waarde,110,10+waarde) GraphicsWindow.DrawLine(10+waarde,10,10+waarde,110) endfor ' veeg nu willekeurige lijnstukjes uit binnen het rooster For teller=1 To 200 ' kies een richting: 1=horizontaal, 2=verticaal richting=math.GetRandomNumber(2) ' kies een x en y waar het lijnstuk begint x=(math.GetRandomNumber(10))*10 y=(math.GetRandomNumber(10))*10 ' teken een witte lijn van x,y in de gekozen richting GraphicsWindow.PenColor="white" If richting=1 Then GraphicsWindow.DrawLine(x,y,x+10,y) Else GraphicsWindow.DrawLine(x,y,x,y+10) endif endfor opdracht 7.2 inleveropdracht voor een micropunt opdracht 7.3 Dit doolhof kan toevallig het resultaat zijn van het slechte algoritme. Het kan niet het resultaat zijn van random divide. Random divide maakt immers altijd 3 openingen in de vier scheidingsmuren. Daardoor kunnen er niet twee afgesloten gebieden 107 ontstaan zoals in het getekende doolhof. Opdracht 8.1 Eerst kijken we welke cellen er geboren worden: B Daarna zoeken we cellen die sterven en overleven S - - - S - B S - Het plaatje wordt dus: S - - - S - B S - Opdracht 8.2 108 Opdracht 8.3 In het speelveld zijn de randen niet doorverbonden met de andere kant. x x x x x x x x x x x Opdracht 8.4 De randen zijn doorverbonden met de andere kant van het speelveld. Daardoor zijn in dit kleine speelveld alle andere cellen een buur van de zwarte cel. x x x x x x x x opdracht 8.5 inleveropdracht voor een micropunt Opdracht 9.1 Je staat op de plek die met het kruisje gemarkeerd is. Volg de linker muur naar de uitgang. 109 Opdracht 9.2 Je staat op de plek die met het kruisje gemarkeerd is. Volg de rechter muur naar de uitgang. Opdracht 9.3 Dat is afhankelijk van de kijkrichting. Als de neus naar links staat, is de onderste lijn de linker muur. opdracht 9.4 Je blijft steeds de binnenmuren volgen. Je komt nooit bij de deur waarmee je het doolhof kunt verlaten. Opdracht 9.5 Bij een kruispunt gaat de automaat eerst rechtdoor. Als de agent later terugkomt gaat hij links (gezien vanuit de oorspronkelijke aanlooprichting) en tenslotte rechts. 110 Opdracht 9.6 Bekijk het onderstaande doolhof. De zwarte cellen zijn muren. Je begint in S. De uitgang ligt bij U. Op een kruispunt wordt de richtingen in de volgende volgorde doorlopen: Links, Rechtdoor, Rechts. In welke volgorde worden de cellen 1,2, 3, 4, 5 en 6 bezocht door de cellulaire automaat? Noteer de volgorde. Als een cel meer dan 1 keer bezocht wordt moet je het nummer van die cel bij elk bezoek opschrijven. De met cijfers gemarkeerde cellen worden in deze volgorde bezocht: 2 1 2 4 5 6. 3 wordt niet bezocht omdat na 6 al de uitgang gevonden wordt. Opdracht 9.7 inleveropdracht voor maximaal drie micropunten Opdracht 9.8 inleveropdracht 111 Opdracht 10.1 Neem de onderstaande tabel over en bereken zoals in het voorbeeld de Levenshteinafstand tussen de woorden PAAS en HAAS. Dit voorbeeld is zo eenvoudig dat je vooraf al kunt zien dat de afstand 1 is, maar het gaat om het correct toepassen van het algoritme. H A A S 0 1 2 3 4 P 1 1 2 3 4 A 2 2 1 2 3 A 3 3 2 1 2 S 4 4 3 2 1 De cel rechtsonder bevat de Levenshtein-afstand: 1 Er is inderdaad één wijziging nodig: de P verandert in een H. Opdracht 10.2 Neem de onderstaande tabel over en bereken zoals in het voorbeeld de Levenshteinafstand tussen de woorden HAZEN en HAGEL. H A G E L 0 1 2 3 4 5 H 1 0 1 2 3 4 A 2 1 0 1 2 3 Z 3 2 1 1 2 3 E 4 3 2 2 1 2 N 5 4 3 3 2 2 De cel rechtsonder bevat de Levenshtein-afstand: 2. Dat is ook wel te begrijpen. Als je van HAZEN het woord HAGEL wilt maken, vervang je de Z door een G en de N door een L. Dat zijn twee wijzigingen. 112 Opdracht 10.3 TextWindow.WriteLine("Levenshtein DEMO") TextWindow.WriteLine("") ' lees eerste de twee woorden in TextWindow.Write("Voer a.u.b. het eerste woord in:") woord1=textwindow.Read() TextWindow.Write("Voer a.u.b. het tweede woord in:") woord2=textwindow.Read() ' zet de tekens van het eerste woord in de eerste rij For i=1 To Text.getlength(woord1) d[i][0]=i endfor ' zet de tekens van het tweede woord in de eerste kolom For i=1 To Text.getlength(woord2) d[0][i]=i endfor For i=0 To Text.GetLength(woord1) For j=0 To Text.GetLength(woord2) ' bepaal de kosten If Text.GetSubText(woord1,i,1)=Text.GetSubText(woord2,j,1) Then cost=0 else cost=1 endif 'bepaal het minimum voor d[i][j], dat gaat een beetje omslachtig min=d[i-1][j]+1 'wissen van een letter If d[i][j-1]+1<min Then min=d[i][j-1]+1 'toevoegen van een letter EndIf If d[i-1][j-1]+cost<min Then min=d[i-1][j-1]+cost 'wijzigen van een letter EndIf d[i][j]=min endfor endfor ' Zet de tabel d nu op het scherm TextWindow.Write(" ") TextWindow.WriteLine(woord2) For i=0 To Text.GetLength(woord1) if i=0 then TextWindow.Write(" ") else TextWindow.Write(Text.GetSubText(woord1,i,1)) EndIf For j=0 To Text.GetLength(woord2) TextWindow.Write(d[i][j]) EndFor TextWindow.WriteLine("") EndFor 113 Opdracht 10.4 De kern van het programma blijft hetzelfde. Dat de tekst uit CTAG en vraagtekens bestaat is niet anders dan dat de tekst alleen uit letters bestaat. De enige aanpassing is nu dat er in plaats van invoer voor een willekeurige reeks van CTAG en vraagtekens gezorgd wordt. In deze code heb ik ervoor gekozen om de DNA reeks maximaal 10 tekens te laten zijn. Als je veel langere reeksen maakt kan de afstand groter dan 10 worden. Je hebt dan 2 plaatsen in de tabel nodig per cel. TextWindow.WriteLine("Levenshtein DNA DEMO") TextWindow.WriteLine("") ' maak twee willekeurige strings met CTAG? maakDNA() woord1=s maakDNA() woord2=s TextWindow.WriteLine("De twee DNA reeksen die vergeleken worden zijn:") TextWindow.Writeline("Reeks 1: "+woord1) TextWindow.Writeline("Reeks 2: "+woord2) ' zet de tekens van het eerste woord in de eerste rij For i=1 To Text.getlength(woord1) d[i][0]=i endfor ' zet de tekens van het tweede woord in de eerste kolom For i=1 To Text.getlength(woord2) d[0][i]=i endfor For i=0 To Text.GetLength(woord1) For j=0 To Text.GetLength(woord2) ' bepaal de kosten If Text.GetSubText(woord1,i,1)=Text.GetSubText(woord2,j,1) Then cost=0 else cost=1 endif 'bepaal het minimum voor d[i][j], dat gaat een beetje omslachtig min=d[i-1][j]+1 If d[i][j-1]+1<min Then min=d[i][j-1]+1 EndIf If d[i-1][j-1]+cost<min Then min=d[i-1][j-1]+cost EndIf d[i][j]=min endfor endfor ' Zet de tabel d nu op het scherm 114 TextWindow.Write(" ") TextWindow.WriteLine(woord2) For i=0 To Text.GetLength(woord1) if i=0 then TextWindow.Write(" ") else TextWindow.Write(Text.GetSubText(woord1,i,1)) EndIf For j=0 To Text.GetLength(woord2) TextWindow.Write(d[i][j]) EndFor TextWindow.WriteLine("") EndFor 'deze routine maakt een sting met CTAG? en met deze in s Sub maakDNA s="" t="CTGA?" For teller=1 To 10 x=Math.GetRandomNumber(5) s=s+Text.GetSubText(t,x,1) EndFor endsub 115 opdracht 11.1 'schudden TextWindow.Writeline("Demonstratieprogramma: Schudden") 'zet de getallen 1 tot en met 52 in een array for teller=1 To 52 kaart[teller]=teller endfor aantalopplek=52 aantalopeenvolgend=51 While aantalopplek>0 or aantalopeenvolgend>0 'we verwisselen aantalverwisselingen keer twee willekeurige kaarten For teller=1 To 52 'kies een willekeurige kaart 'ga hiermee door tot x ongelijk aan teller is 'maak x gelijk aan teller, dan moeten we zeker nog een keer een kaart kiezen x=teller while x=teller x=math.getrandomnumber(52) endwhile 'verwissel de twee kaarten temp=kaart[teller] kaart[teller]=kaart[x] kaart[x]=temp endfor 'tellen hoeveel kaarten niet verwisseld zijn aantalopplek=0 For teller=1 To 52 'als de kaart niet op een andere plek staat maken we 'aantalopplek' één hoger If kaart[teller]=teller Then aantalopplek=aantalopplek+1 EndIf endfor 'tellen hoeveel kaarten naast een buur staan 'we beginnen bij 2 aantalopeenvolgend=0 For teller=2 To 52 'als de kaart niet op een andere plek staat maken we 'aantalopplek' één hoger verschil=kaart[teller]-kaart[teller-1] If (verschil=1) or (verschil=-1) Then aantalopeenvolgend=aantalopeenvolgend+1 EndIf endfor endwhile ' schrijf de lijst ter controle 116 For teller=1 To 52 TextWindow.Write(kaart[teller]+",") EndFor TextWindow.WriteLine("") 117 Opdracht 12.1 inleveropdracht voor een micropunt Opdracht 12.2 ' PRIEM1 TextWindow.WriteLine("PRIEM 1: onderzoek van een getal of het priem is") TextWindow.Write("Welk getal wil je onderzoeken:") getal=textwindow.ReadNumber() starttijd=Clock.ElapsedMilliseconds aantaldelers=2 For deler=2 To getal-1 rest=math.Remainder(getal,deler) If rest=0 Then aantaldelers=aantaldelers+1 endif endfor eindtijd=Clock.ElapsedMilliseconds TextWindow.WriteLine("De verstreken tijd is:"+(eindtijdstarttijd)/1000+"seconde") If aantaldelers=2 Then TextWindow.WriteLine(getal+" is priem") Else TextWindow.WriteLine(getal+" is NIET priem") endif 118 Opdracht 12.3 36 144 256 2x18 → 2 3x12 → 3 4x9 → 4 6x6 → 6 9x4 –> 4 12x3 → 3 18x2 → 2 2x72 3x48 6x24 9x16 12x12 16x9 24x6 48x3 72x2 … … 16x16 … ... De grootste van alle kleinste delers zijn dus respectievelijk: 6, 12, 16. Dit is steeds de wortel van het getal. De grootste van alle kleinste deler van 110 is 10. Dat is niet exact de wortel van 110, maar wel het grootste gehele getal dat kleiner (of gelijk aan) de wortel van 110 is. Opdracht 12.4 Vervang de regel: For deler=2 To getal-1 door For deler=2 To Math.SquareRoot(getal) Opdracht 12.5 Hieronder staan de tijden in seconden 10 100 1000 10000 100000 1000000 10000000 PRIEM1 0 0 0 0,08 0,45 4,2 41,48 PRIEM2 0 0 0 0 0 0 0,05 Bij PRIEM2 gaat berekening zelfs voor grote getallen al zo snel dat we pas bij 10 miljoen een enigszins meetbare vertraging kunnen meten. Voor 1 miljard had de door mij gebruikte computer 0,2 seconde nodig. 119 opdracht 12.5 De 25 getallen staan in de tabel. De wortel van 25 is exact 5. Streep 1 af. 2 is het eerste priemgetal, de tweevouden erna strepen we af. 3 is het volgende priemgetal, de drievouden erna strepen we af. 5 is nog niet groter dan de wortel. 5 is dus het volgende priemgetal. De vijfvouden erna strepen we af. De getallen groter dan 5 die nu nog in de tabel staan zijn ook priem: 7,11,13,17,19,23. 2 3 5 7 11 13 17 19 23 Opdracht 12.6 ' PRIEM3 TextWindow.WriteLine("PRIEM 3: onderzoek van een reeks getallen of ze priem zijn") start: TextWindow.Write("startgetal:") startgetal=textwindow.ReadNumber() TextWindow.Write("eindgetal:") eindgetal=textwindow.ReadNumber() starttijd=Clock.ElapsedMilliseconds For getal=startgetal To eindgetal aantaldelers=2 For deler=2 To Math.SquareRoot(getal) rest=math.Remainder(getal,deler) If rest=0 Then aantaldelers=aantaldelers+1 endif endfor If aantaldelers=2 Then TextWindow.WriteLine(getal+" is priem") Else TextWindow.WriteLine(getal+" is NIET priem") endif 120 endfor eindtijd=Clock.ElapsedMilliseconds TextWindow.WriteLine("De totale verstreken tijd is:"+(eindtijdstarttijd)/1000+"seconde") TextWindow.WriteLine("De verstreken tijd per getal is:"+(eindtijdstarttijd)/((eindgetal-startgetal)*1000)+"seconde") Op de website staat een tweede versie van dit programma dat zo aangepast is dat ze alleen het aantal getallen telt. 121 Opdracht 12.7 ' PRIEM4 TextWindow.WriteLine("PRIEM 4: De zeef van Erathostenes") TextWindow.Write("Tot welk getal wil je zoeken:") eindgetal=textwindow.ReadNumber() TextWindow.WriteLine("Klaarzetten van alle getallen.") 'maak een rij getallen klaar ' dat dit zo lang duurt ligt aan small basic... rij[1]=0 For teller=2 To eindgetal rij[teller]=1 TextWindow.CursorLeft=1 TextWindow.Write(teller) endfor TextWindow.WriteLine("Alle getallen staan klaar, begin met zeven") 'we beginnen pas te tellen als alle getallen klaar staan. starttijd=Clock.ElapsedMilliseconds aantalpriemgetallen=0 getal=2 While getal<math.SquareRoot(eindgetal) 'zoek het eerste vrije getal While rij[getal]=0 getal=getal+1 endwhile TextWindow.Write(getal+", ") ' getal is nu het eerste vrije getal, dat is priem aantalpriemgetallen=aantalpriemgetallen+1 'streep alle veelvouden weg weg=getal+getal While weg<=eindgetal rij[weg]=0 weg=weg+getal EndWhile getal=getal+1 EndWhile 'tel nog alle vrije getallen while getal<=eindgetal If rij[getal]=1 Then aantalpriemgetallen=aantalpriemgetallen+1 endif getal=getal+1 EndWhile TextWindow.WriteLine("Klaar met zeven") eindtijd=Clock.ElapsedMilliseconds 122 TextWindow.WriteLine("De verstreken tijd is:"+(eindtijdstarttijd)/1000+"seconde") TextWindow.WriteLine("Het aantal gevonden priemgetallen is:"+aantalpriemgetallen) TextWindow.WriteLine("Hier volgen alle gevonden priemgetallen:") For teller=1 To eindgetal If rij[teller]=1 Then TextWindow.Write(teller+", ") endif endfor opdracht 13.1 opdracht 13.2 opdracht 13.3 inleveropdracht opdracht 13.4 Het algoritme moet zijn 'inhaligheid' bekopen door later onnodig een punt verderop te moeten kiezen, omdat de punten dichtbij al aan de beurt zijn geweest. opdracht 13.5 Uitwerking TS ' zet de punten in een lijst GraphicsWindow.width=450 GraphicsWindow.height=450 aantal=10 For teller=1 To aantal x[teller]=math.GetRandomNumber(400) y[teller]=math.GetRandomNumber(400) alinroute[teller]=0 GraphicsWindow.DrawEllipse(x[teller]-1,y[teller]-1,3,3) endfor x[aantal+1]=x[1] y[aantal+1]=y[1] totaleafstand=0 aantalpunteninroute=1 punt[1]=1 laatstepunt=1 While aantalpunteninroute<=aantal minafstand=99999999999 For teller =1 to aantal dx=x[teller]-x[laatstepunt] dy=y[teller]-y[laatstepunt] 123 afstand=math.SquareRoot(dx*dx+dy*dy) If (afstand<minafstand) and (alinroute[teller]=0) Then minafstand=afstand dichtstbijzijndepunt=teller endif endfor totaleafstand=totaleafstand+minafstand GraphicsWindow.DrawLine(x[laatstepunt],y[laatstepunt],x[dichtstbijzijndepun t],y[dichtstbijzijndepunt]) aantalpunteninroute=aantalpunteninroute+1 punt[aantalpunteninroute]=dichtstbijzijndepunt laatstepunt=dichtstbijzijndepunt alinroute[dichtstbijzijndepunt]=1 Program.Delay(10) endwhile GraphicsWindow.DrawLine(x[laatstepunt],y[laatstepunt],x[1],y[1]) GraphicsWindow.DrawBoundText(10,410,450,totaleafstand) opdracht 13.6 In de gevonden oplossing zitten kruisende wegen. Met rood is hieronder aangegeven hoe de kortere route loopt. opdracht 14.1 doen opdracht 14.2 Als uitwerking het volgende voorbeeld: We nemen als getal 12345 en als startwaarde voor de deler 1. Met het voorbeeld-programma kunnen we de stappen snel controleren. 124 opdracht 14.3 geen standaard antwoord opdracht 15.1 Persoon A stopt het mes al persoon B of persoon C 'stop' roept. Neem aan dat persoon B 'stop' roept. Persoon B is dan tevreden, hij riep immers 'stop'. Persoon C is ook tevreden, omdat hij van plan was om later 'stop' te roepen. Het deel dat overblijft – nadat persoon A zijn deel afgesneden heeft – is groter dan persoon C in gedachten had. Opdracht 16.1 8 9 14 16 11 12 13 10 20 1 2 3 17 18 19 4 7 5 6 15 8 9 14 16 11 12 13 10 20 1 2 3 17 18 19 4 7 5 6 [15] 8 9 14 11 12 13 10 1 2 3 4 7 5 6 [15] 16 20 17 18 19 8 9 14 11 12 13 10 1 2 3 4 7 5 [6] [15] 16 20 17 18 [19] 1 2 3 4 5 [6] 8 9 14 11 12 13 10 7 [15] 16 17 18 [19] 20 maak de resterende stappen zelf af. Opdracht 16.2 geen standaard antwoord 125 opdracht 16.3 Als de rij aflopende gesorteerd is, bijvoorbeeld: 54321 opdracht 16.4 doen 126 BIJLAGE: Levenshtein Deze bijlage is geen onderdeel van de toetsstof Het algoritme van Levenshtein geschreven in Java: public class Distance { //**************************** // Get minimum of three values //**************************** private int Minimum (int a, int b, int c) { int mi; mi = a; if (b < mi) { mi = b; } if (c < mi) { mi = c; } return mi; } //***************************** // Compute Levenshtein distance //***************************** public int LD (String s, String t) { int d[][]; // matrix int n; // length of s int m; // length of t int i; // iterates through s int j; // iterates through t char s_i; // ith character of s char t_j; // jth character of t int cost; // cost // Step 1 n = s.length (); m = t.length (); if (n == 0) { return m; } if (m == 0) { return n; } d = new int[n+1][m+1]; // Step 2 for (i = 0; i <= n; i++) { d[i][0] = i; } for (j = 0; j <= m; j++) { d[0][j] = j; } 127 // Step 3 for (i = 1; i <= n; i++) { s_i = s.charAt (i - 1); // Step 4 for (j = 1; j <= m; j++) { t_j = t.charAt (j - 1); // Step 5 if (s_i == t_j) { cost = 0; } else { cost = 1; } // Step 6 d[i][j] = Minimum (d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1] + cost); } } } // Step 7 return d[n][m]; } 128 BIJLAGE: een ander algoritme om goede doolhoven te maken Deze bijlage is geen onderdeel van de toetsstof create a CellStack (LIFO) to hold a list of cell locations set TotalCells = number of cells in grid choose a cell at random and call it CurrentCell set VisitedCells = 1 while VisitedCells < TotalCells find all neighbors of CurrentCell with all walls intact if one or more found choose one at random knock down the wall between it and CurrentCell push CurrentCell location on the CellStack make the new cell CurrentCell add 1 to VisitedCells else pop the most recent cell entry off the CellStack make it CurrentCell endIf endWhile 129 TODO's Langton's mier https://www.youtube.com/watch?v=1X-gtr4pEBU Hoe algoritmes onze wereld bepalen https://www.ted.com/talks/kevin_slavin_how_algorithms_shape_our_world?language=nl artikel volkskrant https://www.volkskrant.nl/nieuws-achtergrond/wie-is-er-bang-voor-hetalgoritme~b06c4c02/ artikelenserie NRC steen papier schaar automaat https://www.youtube.com/watch?v=M4cV0nCIZoc http://www.gamedev.net/blog/844/entry-2249737-another-cellular-automaton-video/ https://www.mediawijsheid.nl/video/zo-bepalen-algoritmes-jouw-wereldbeeld/ pancakesort https://www.youtube.com/watch?v=oDzauRFiWFU boids https://www.youtube.com/watch?v=M028vafB0l8 eerlijk delen (zie jaar van het algoritme) cirkel tekenen met lossen pixels toevalsgetallen (al klaar) verbeteringen van TS (punten verwisselen en tabu-search) hashing weg zoeken in doolhof (observable gamma=1, partial observable breadth first, clusteren steden 130 131