SI-möte #10, Programkonstruktion och Datastrukturer Lösngsförslag Elias Castegren & Kristia Ausmees elca7381@student.uu.se krau6498@student.uu.se 1. (* prtlist(l) TYPE: strg list -> unit PRE: () POST: () SIDE-EFFECTS: alla strängar i l skrivs ut på vars rad på termalen EXAMPLES: prtlist(["hej","på","dig"]) = () ("Hej\npå\ndig" skrivs ut på termalen) (* VARIANT: anta element i l fun prtlist([]) = () prtlist(f::r) = (prt (f ˆ "\n"); prtlist(r)); Två varianter med funktioner ur List-biblioteket: fun prtlist(l) = foldl (fn (s,_) => prt(s ˆ "\n")) () l; fun prtlist(l) = ((map (fn (s) => prt(s ˆ "\n")) l); ()); Notera att map-uttrycket ovan returnerar en unit-lista med lika många element som l. För att uppfylla typen i specifikationen returneras därför () efter att map-uttrycket beräknats. Det fns en byggd högre ordngens funktion i ML som heter app, med typen ( a -> unit) -> a list -> unit. Den applicerar en funktion utan eftervillkor på varje element i en lista och returnerar till sist unit. Med den hade man kunnat skriva funktionen så här: fun prtlist(l) = app (fn (s) => (prt(s ˆ "\n"))) l; 1
2. Med While-loop: Här använder vi en -sats för att öppna strömmarna och itiera en radräknar-referens i, sen loopar vi så länge vi te stöter på slutet på strömmen och avslutar med att stänga båda strömmarna. open TextIO; (* lenumbers(f) TYPE: strg->unit PRE: f är namnet på en fil som går att läsa POST: () SIDE-EFFECTS: En ström öppnas från filen f och en utström öppnas till filen f.numbered. Alla rader i f skrivs till f.numbered men med radnummer före. Alla strömmar stängs. EXAMPLES: lenumbers("fil") = () Om "fil" nhöll raderna Inte utan m ML-tolk! nehåller nu "fil.numbered" 1. Inte 2. utan 3. m 4. ML-tolk! fun lenumbers(f) = val s = openin(f) val os = openout(fˆ".numbered") val i = ref 1 (while(not (endofstream(s))) do (output(os, Int.toStrg(!i) ˆ ". " ˆ valof(putle(s))); i := (!i + 1)); closein(s); closeout(os)) 2
Med rekursion: Här använder vi en hjälpfunktion transferles som givet en ström och en utström kopierar strömmen och numrerar raderna. Huvudfunktionen öppnar strömmarna, skickar dem till transferles och stänger sedan strömmarna. open TextIO; fun lenumbers(f) = (* transferles(s, os, i) TYPE: stream * outstream * t -> unit PRE: s och os är läs- respektive skrivbara strömmar POST: () SIDE-EFFECTS: Alla rader i s skrivs till os, numrerade från i och uppåt (*VARIANT: Anta rader i s nan filslut fun transferles(s, os, i) = if endofstream(s) then () (output(os, Int.toStrg(i) ˆ ". " ˆ valof(putle(s))); transferles(s, os, i+1)) val s = openin(f) val os = openout(f ˆ ".numbered") (transferles(s, os, 1); closein(s); closeout(os)) 3
3. Poly/ML har get bibliotek för slumpade tal så för att testa funktionen kan man antgen skriva en funktion i stil med fun randomrange(a,b) = (a+b) div 2 eller använda någon annan ML-dialekt som har sluptalsmöjligheter (SML New Jersey och Moscow ML fungerar båda). Först skriver vi en hjälpfunktion som jämför två tal och skriver ut ifall det ena är större, mdre eller lika med det andra. Genom att ge den returtypen bool kan vi senare använda den i en if-sats för att avgöra om användaren har matat rätt gissng. (* comparenumbers(guess, correct) TYPE: t * t -> bool PRE: () POST: true om guess = correct, annars false SIDE-EFFECTS: skriver ut "Mdre!", "Storre!" eller "Ratt!" beroende på hur correct förhåller sig till guess. EXAMPLES: comparenumbers(45, 50) = false <"Storre!" skrivs ut på termalen> fun comparenumbers(guess, correct) = if guess > correct then (TextIO.prt "Mdre!\n"; false) if guess < correct then (TextIO.prt "Storre!\n"; false) (TextIO.prt "Ratt!\n"; true); Sedan skriver vi en funktion som låter användaren gissa ett givet tal (vi väntar alltså med slumpngen). (* guessnumber (ans) TYPE: t -> t PRE: () POST: ans SIDE-EFFECTS: låter användaren mata tal i termalen tills hon matar ans eller en tom rad. Instruktionerna "Mdre!", "Storre!", "Ratt!" och "Du måste skriva ett heltal!" skrivs till termalen vid behov. EXAMPLES: guessnumber (50) = 50 <Användaren får gissa tills hon matar 50 eller en tom rad> 4
(* VARIANT: Anta gissngar tills användaren matar ans fun guessnumber (ans) = val put = TextIO.putLe(TextIO.stdIn) val guess = Int.fromStrg(valOf(put)) if put = SOME "\n" then ans if guess = NONE then (prt "Du maste skriva ett heltal!\n"; guessnumber (ans)) if comparenumbers(valof(guess), ans) then ans guessnumber (ans) Slutligen skriver vi en funktion som slumpar fram ett tal och sätter igång spe (genom att anropa hjälpfunktionen guessnumber ). (* guessnumber(n) TYPE: t -> t PRE: n>0 POST: ett slumpat tal mellan 0 och n SIDE-EFFECTS: "Gissa ta!" skrivs ut till termalen. Sedan får användaren spela ett parti "gissa ta", där svaret är mellan 0 och n, i termalen. EXAMPLES: guessnumber(100) = 50 <Användaren får gissa tal tills hon gissar rätt eller ger upp> fun guessnumber(max) = val rand = randomrange(0, max) (TextIO.prt("Gissa ta!\n");guessnumber (rand)) 5
Här följer en helt imperativ version av guessnumber, som blir några rader kortare mest för att det är färre fun- och -satser. (* guessnumber(n) TYPE: t -> t PRE: n>0 POST: ett slumpat tal mellan 0 och n SIDE-EFFECTS: Skriver ut "Gissa ta!" och låter sedan användaren mata tal i termalen tills hon gissar rätt eller matar en tom rad. Instruktionerna "Mdre!", "Storre!", "Ratt!" och "Du måste skriva ett heltal!" skrivs till termalen vid behov. EXAMPLES: guessnumber(100) = 50 <Användaren får gissa tills hon matar 50 eller en tom rad> fun guessnumber(n) = val ans = randomrange(0,n) val put = ref NONE val guess = ref NONE val contue = ref true (prt "Gissa ta!\n"; while (!contue) do (put := TextIO.putLe(TextIO.stdIn); guess := Int.fromStrg(valOf(!put)); if!put = SOME "\n" then contue := false if!guess = NONE then prt "Du maste skriva ett heltal!\n" if valof(!guess) > ans then prt "Mdre!\n" if valof(!guess) < ans then prt "Storre!\n" (prt "Ratt!\n"; contue := false) ); ans) 6
4. 7
5. 8
6. Ett bomialträd med rang n har 2 n noder. En bomial heap nehåller alltså t 0 2 0 + t 1 2 1 + t 2 2 2 + + t m 2 m noder, där t i är ett eller noll beroende på om heapen har ett träd med rang i eller te. Ett bärt tal d m d m 1 d m 2... d 0 blir decimalt d 0 2 0 + d 1 2 1 + d 2 2 2 + + d m 2 m där d i är ett eller noll. Anta noder i ett bomialträd är alltså detsamma som ett bärt tal enligt ovan där en etta betyder att heapen har ett träd med motsvarande rang. 1. (52) 10 = (110100) 2 - rangerna hos träden i heapen är alltså 2, 4 och 5 (dex från höger för ettorna i det bära ta) 2. (53) 10 = (110101) 2 - rangerna är 0, 2, 4 och 5 3. (127) 10 = (1111111) 2 - heapen nehåller träd av alla ranger mellan 0 och 7 4. (128) 10 = (10000000) 2 - heaper nehåller ett träd av rang 8 När anta noder är 2 k 1 måste man slå ihop k stycken träd om man lägger ett nytt element (se övergången från 127 till 128 noder ovan). Det tar alltså längre tid att lägga nya element när man har många ettor i följd i den bära representationen av anta noder. 7. heapsort(l) : t list t list 1. Skapa en tom max-heap H 2. Sätt alla element i l i H, ett och ett 3. Ta ut det största elementet ur H och lägg det först i en lista l 4. Är H tom? Gå tillbaka till steg 3, annars returnera l Antag att listan som ska sorteras har l element. Vi gör först l stycken sättngar och sen l stycken extrahergar (båda operationerna med komplexitet Θ(log n), där n är anta noder i heapen). Den totala komplexiteten blir alltså l Θ(log l ) + l Θ(log l ) = Θ( l log l ) Med andra ord är den jämförbar med mergesort och quicksort! 9
En ML-implementation med en m-heap följer nedan. Heapimplementationen kommer från föreläsngarna och fns att hämta från kurshemsidan. (* sort(l) TYPE: t list -> t list PRE: () POST: l sorterad EXAMPLES: sort([5,3,4,2,1]) = [1,2,3,4,5] fun sort(l) = (* extractall(h) TYPE: boheap -> t list PRE: h uppfyller varianten för bomiala m-heapar POST: en sorterad lista med alla element i h EXAMPLES: sort({en heap med elementen <3,4,5>}) = [3,4,5] (* VARIANT: Anta element i heapen h fun extractall(h) = if null(h) then [] val (e, h ) = extractm(h) e::extractall(h ) end val H = foldr (fn(e, h) => sert(h, e)) [] l extractall(h) Tänk på att heapen är defierad som: type boheap = botree list En tom heap är alltså detsamma som en tom lista (se foldr-anropet). Därför kan man också använda listfunktioner som null för att se om en heap är tom. 10