F2: Algoritmanalys, sökning, sortering, recursive descent Carl Nettelblad

Relevanta dokument
F3: Recursive descent, tokenisering, avbildningar och undantag. Carl Nettelblad

Medan ni väntar. 2. Skriv metoden. 3. Skriv metoden. Naturligtvis rekursivt och utan användning av Javas standardmetoder.

Algoritmanalys. Inledning. Informationsteknologi Malin Källén, Tom Smedsaas 1 september 2016

F3: OU2, undantag, avbildningar, listor och träd. Carl Nettelblad

Obligatorisk uppgift: Numerisk kalkylator

Parsing med Recursive Descent, Avbildningsklasser. Syntaxdiagram. Syntaxdiagram och kodning expression. Betrakta följande uttryck

TDDI16 Datastrukturer och algoritmer. Algoritmanalys

Sökning och sortering

Obligatorisk uppgift: Numerisk kalkylator

Rekursion och induktion för algoritmkonstruktion

Det är principer och idéer som är viktiga. Skriv så att du övertygar rättaren om att du har förstått dessa även om detaljer kan vara felaktiga.

Klassen BST som definierar binära sökträd med tal som nycklar och enda data. Varje nyckel är unik dvs förekommer endast en

Obligatorisk uppgift: Numerisk kalkylator

if (n==null) { return null; } else { return new Node(n.data, copy(n.next));

Obligatorisk uppgift 5

Föreläsning 5 Innehåll

Anmälningskod: Lägg uppgifterna i ordning. Skriv uppgiftsnummer (gäller B-delen) och din kod överst i högra hörnet på alla papper

Objektorienterad programmering i Java Undantag Sven-Olof Nyström Uppsala Universitet Skansholm: Kapitel 11

Föreläsning 7 Innehåll. Rekursion. Rekursiv problemlösning. Rekursiv problemlösning Mönster för rekursiv algoritm. Rekursion. Rekursivt tänkande:

Inlämningsuppgift MiniPlotter

Föreläsning 5 Innehåll. Val av algoritm och datastruktur. Analys av algoritmer. Tidsåtgång och problemets storlek

Rekursion och induktion för algoritmkonstruktion

Idag. Exempel, version 2. Exempel, version 3. Ett lite större exempel

Det är principer och idéer som är viktiga. Skriv så att du övertygar rättaren om att du har förstått dessa även om detaljer kan vara felaktiga.

Lägg uppgifterna i ordning. Skriv uppgiftsnummer och din kod överst i högra hörnet på alla papper.

Programmering för språkteknologer II, HT2014. Rum

Algoritmer. Två gränssnitt

Tentamen Programmeringsteknik II Inledning. Anmälningskod:

Grafik, grafiska användargränssnitt och rörliga bilder

Objektorienterad programmering i Java Undantag Sven-Olof Nyström Uppsala Universitet Skansholm: Kapitel 11

Kompilering och exekvering. Föreläsning 1 Objektorienterad programmering DD1332. En kompilerbar och körbar java-kod. Kompilering och exekvering

Objektorienterad Programmering (TDDC77)

Rekursion och induktion för algoritmkonstruktion

Command line argumenter. Objektorienterad Programmering (TDDC77) Vad blir resultatet? Nu då? Ahmed Rezine. Hösttermin 2016

Objektorienterad programmering E. Algoritmer. Telefonboken, påminnelse (och litet tillägg), 1. Telefonboken, påminnelse (och litet tillägg), 2

Programmering A. Johan Eliasson

Föreläsning REPETITION & EXTENTA

F5: Debriefing OU2, repetition av listor, träd och hashtabeller. Carl Nettelblad

Föreläsning 3. Stack

Tentamen Programmeringsteknik II för STS Skrivtid: Inga hjälpmedel.

Föreläsning 11 Datastrukturer (DAT037)

Grundkurs i programmering, 6 hp (725G61) Dugga 2 tillfälle 2

public boolean containskey(string key) { return search(key, head)!= null; }

Algoritmer och effektivitet. Föreläsning 5 Innehåll. Analys av algoritmer. Analys av algoritmer Tidskomplexitet. Algoritmer och effektivitet

Länkade strukturer, parametriserade typer och undantag

Felhantering TDDD78, TDDE30, 729A

Obligatorisk uppgift 5: Symbolisk kalkylator

Lite om felhantering och Exceptions Mer om variabler och parametrar Fält (eng array) och klassen ArrayList.

Föreläsning 11 Innehåll

Föreläsning 1 Datastrukturer (DAT037)

F7: Rekursiv till iterativ, sammanfattning, genomgång av omtentan Carl Nettelblad

Föreläsning 3. Stack

Lägg uppgifterna i ordning. Skriv uppgiftsnummer och din anmälningskod överst i högra hörnet på alla papper.

Observera. Tentamen Programmeringsteknik II Skrivtid:

Algoritmanalys. Genomsnittligen behövs n/2 jämförelser vilket är proportionellt mot n, vi säger att vi har en O(n) algoritm.

List.java. List.java. Printed by Tom Smedsaas

TENTAMEN I PROGRAMMERING. På tentamen ges graderade betyg:. 3:a 24 poäng, 4:a 36 poäng och 5:a 48 poäng

JAVAUTVECKLING LEKTION 4

Föreläsning 12 Innehåll

Lägg uppgifterna i ordning. Skriv uppgiftsnummer och din kod överst i högra hörnet på alla papper.

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 5 Jonas Lindgren, Institutionen för Datavetenskap, LiU

Föreläsning 2 Datastrukturer (DAT037)

F9 - Polymorfism. ID1004 Objektorienterad programmering Fredrik Kilander

Föreläsning 3-4 Innehåll

Datatyper och kontrollstrukturer. Skansholm: Kapitel 2) De åtta primitiva typerna. Typ Innehåll Defaultvärde Storlek

Datastrukturer, algoritmer och programkonstruktion (DVA104, VT 2015) Föreläsning 6

Tentamen i Algoritmer & Datastrukturer i Java

Föreläsning 3-4 Innehåll. Diskutera. Metod. Programexempel med metod

1 Uppgift 1. a) Skapar ett Company-objekt med hjälp av den överlagrade konstruktorn. Du kan själv välja värden på instansvariablerna.

Magnus Nielsen, IDA, Linköpings universitet

Lägg uppgifterna i ordning. Skriv uppgiftsnummer och din kod överst i högra hörnet på alla papper.

Tommy Färnqvist, IDA, Linköpings universitet. 2 Rekursion i C Implementation av rekursion Svansrekursion En till övning...

public static void mystery(int n) { if (n > 0){ mystery(n-1); System.out.print(n * 4); mystery(n-1); } }

Programkonstruktion och Datastrukturer

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 6 Jonas Lindgren, Institutionen för Datavetenskap, LiU

Tentamen i Algoritmer & Datastrukturer i Java

Repetition av OOP- och Javabegrepp

Programmering för språkteknologer II, HT2014. Rum

Föreläsning 2 Datastrukturer (DAT037)

Programmeringsteknik II

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 4 Erik Nilsson, Institutionen för Datavetenskap, LiU

F4. programmeringsteknik och Matlab

Föreläsning Datastrukturer (DAT037)

Repetition av OOP- och Javabegrepp

Föreläsning 4 Datastrukturer (DAT037)

Exempel på listor (klassen ArrayList). Ett exempel med fält. Avbildning är en speciell typ av lista HashMap.

Det är principer och idéer som är viktiga. Skriv så att du övertygar rättaren att du har förstått dessa även om detaljer kan vara felaktiga.

Föreläsning 13 Datastrukturer (DAT037)

SMD 134 Objektorienterad programmering

Anmälningskod: Lägg uppgifterna i ordning. Skriv uppgiftsnummer (gäller B-delen) och din kod överst i högra hörnet på alla papper

Översikt. Stegvis förfining. Stegvis förfining. Dekomposition. Algoritmer. Metod för att skapa ett program från ett analyserat problem

Lägg uppgifterna i ordning. Skriv uppgiftsnummer och din anmälningskod överst i högra hörnet på alla papper.

Objektorienterad Programkonstruktion. Föreläsning 4 8 nov 2016

Lite mer om Javas stöd för fält. Programmering. Exempel: vad är det största talet? hh.se/db2004. Fält samt Input/Output

Del A (obligatorisk för alla)

Metodanrop - primitiva typer. Föreläsning 4. Metodanrop - referenstyper. Metodanrop - primitiva typer

Testning av program. Verklig modell för programutveckling

Föreläsning 3 Innehåll. Generiska klasser. Icke-generisk lista ArrayList, skiss av implementering. Icke-generisk lista Risk för fel

TDDE10 m.fl. Objektorienterad programmering i Java Föreläsning 6 Erik Nilsson, Institutionen för Datavetenskap, LiU

Transkript:

F2: Algoritmanalys, sökning, sortering, recursive descent Carl Nettelblad 2019-04-01

Repetition Kursmål Felsökning (med debugger) Rekursion

Säg vad du vill göra Varför skriver vi kod? För att få datorn att göra det vi vill För att själva läsa koden För att andra ska läsa koden

Debugger Vad gör vi när vårt program inte fungerar som det ska? Arbeta vetenskapligt/ingenjörsmässigt! Bilda hypoteser Experiment Lättläst och modulär kod leder till bättre resonemang Gör förändringar Upprepa

Vad kan debuggern ge oss? Brytpunkter (breakpoints) Stanna körningen av koden varje gång den kommer till viss kodrad Stega (step into/step over) Kör nästa rad, med/utan att även göra stegning i de metoder som anropas Anropsstack Se vilka metoder som ledde oss hit Watches Titta på variabelvärden, eller andra uttryck Interaktion Ändra variabelvärden, anropa metoder

Rekursion Generellt: Dela upp problemet i ett eller flera delproblem av samma slag Lös delproblemen (rekursivt) Sätt ihop lösningarna för delproblemen för att lösa urpsrungsproblemet Utan basfall blir rekursionen oändlig programmet kraschar. Reset-knappen i DrJava kan vara praktisk

Hjälpmetoder Blir lätt om vi lägger till en parameter för antal element att kontrollera. Utgångsläget är hela längden, basfallet 0 element. public static int search(int x, int[] a) { } return search(x, a, a.length); private static int search(int x, int[] a, int n) { if (n == 0) { return -1; } else if (x == a[n-1]) { return n-1; } else { return search(x, a, n-1); } }

Vid redovisning Kör de automatiska testerna innan du redovisar Testerna hjälper dig att hitta att något är fel, ibland inte en tydlig ledtråd om vad Koden ska följa stilguiden och inte vara onödigt komplicerad Du ska kunna förklara vad olika delar av koden gör, hur de fungerar tillsammans, vad som skulle hända om man ändrade eller tog bort någon del, o.s.v. Vi prioriterar hjälp för nuvarande uppgift, särskilt vid deadline Funkar inte att komma på deadline för OU1 och be om att få redovisa hela OU1-OU5

Vilken kurva vinner i längden? Algoritmanalys

Algoritmanalys Prestanda för program kan ofta handla om två saker En effektiv algoritm Hur mycket arbete ska utföras? Hur mycket information behöver lagras i minnet? En effektiv implementation Hur utförs det arbetet så effektivt som möjligt? Hur lagras informationen så kompakt som möjligt? Om vi mäter tid eller minne konkret mäter vi kombinationen! Om vi mäter hur det växer med större och större fall mäter vi algoritmens asymptotiska effektivitet

Söka efter element Från förra veckan: public static int search(int x, int[] a) { } return search(x, a, a.length); private static int search(int x, int[] a, int n) { if (n == 0) { return -1; } else if (x == a[n-1]) { return n-1; } else { return search(x, a, n-1); } }

Hur kan vi söka bättre? Den här algoritmen kan behöva titta på varje element. Precis som i largest i OU1. Om vi vet att listan är sorterad går den att göra mer effektiv: Om talet x < värdet i mitten Fortsätt söka i vänstra halvan annars Fortsätt söka i högra halvan Binärsökning Variant på bisektionsmetoden för att hitta nollställen till kontinuerliga funktioner Går lätt att formulera både rekursivt och iterativt

Kod för binärsökning private static int binarysearch(int x, int[] a, int low, int high) { if (high <= low + 1) { if (high > low && a[low] == x) { return low; } else { return -1; } } else { int mid = (low + high) / 2; if (x < a[mid]) { return binarysearch(x, a, low, mid); } else { return binarysearch(x, a, mid, high); } } }

Publik metod Samma metodhuvud som tidigare! public static int search(int x, int[] a) { } return binarysearch(x, a, 0, a.length);

Effektivitet för binärsökning Vi har n element i första steget. Hur mycket minskar antalet element till anrop k? n n n n n 2 4 8 2 k 1 2 k Basfall när vi når n 2 k = 1 När är det? n = 2 k log 2 n = k Hur många rekursiva anrop krävs det då för att söka i en sorterad array med en miljon (1E6), en miljard (1E9), en biljon (1E12) element? Tips: 2 10 = 1024 1000 = 10 3

Olika sätt att beräkna x n Enklast, ett steg i taget: x n 1 n = 0 = ቊ x x n 1 n > 0 Två steg i taget: 1 n = 0 x n = ቐ x n = 1 x 2 x n 2 n 2 k steg i taget: 1 n = 0 x n = ቐ 0 < n < k x k x n k n k

Olika sätt att beräkna x n Om n jämnt: x n = 1 n = 0 x nτ2 2 n > 0, jämna n Om n udda är n 1 jämnt: 1 n = 0 x n = x nτ2 2 x x n 1 Är detta snabbare? n > 0, jämna n n > 0, udda n

Hur många multiplikationer? Om n är exakt en tvåpotens, n = 2 k : Exakt k multiplikationer, halvering varje gång som i binärsökning Om n inte är en jämn 2-potens? Vid varje steg kan det krävas en extra multiplikation (nuvarande n udda) innan man får en halvering, så 2 multiplikationer ber halveringssteg, 2 log n

Algoritmanalys Vi vill veta hur tiden för en algoritm beror av indata. Eller minnesanvändning eller någon annan begränsad resurs. Resultat av typen tiden växer proportionellt mot kvadraten på antalet element tiden är konstant oberoende av indata tiden beror exponentiellt på storleken på problemet

Asymptotisk notation Asymptotisk notation används för att beskriva hur en funktion t(n) utvecklas för stora värden på n En övergripande trend Definition: En funktion t(n) sägs vara O(f(n)) ( ordo f av n ) om det finns två konstanter c och n 0 sådana att t(n) < c f(n) för alla n > n 0 För ett tillräckligt stort n 0 begränsas t(n) av f(n) skalat med c

Asymptotisk notation Exempel: Funktionen f n = 5n 2 + 100n + 90000 är O n 2 Varför? Om n > 1 gäller f n = 5n 2 + 100n + 90000 < n 2 + 100 n n + 90000 n n = 90105 n 2 Alltså kan vi välja c = 90105, n 0 = 1 I normalfallet bryr vi oss inte om värdena, vi behöver bara visa/veta att det går att hitta dem. Ordonotationen ger en övre gräns för hur snabbt funktionen växer för stora värden på n.

Asymptotisk notation Ordonotationen ger en övre begränsning. Exempel: alla funktioner som är O n 2 är också O n 3 Thetanotationen är mer precis: Funktionen är Θ n 2 betyder att den växer just så fort. Omeganotationen är motsatsen till ordo, en undre begränsning. Funktionen är Ω n 2 betyder att den minst växer så fort. (Om man visar ordo och omega med samma argument följer theta.)

Den funktion vi är intresserad av är tid, minnesanvändning o.s.v. Asymptotisk notation i Exempel: Antal multiplikation som görs av kvadreringsalgoritmen för x n är Θ(log n). algoritmanalys Borde man inte skriva ut att det är log 2? Nej, omvandling mellan logaritmbaser ger bara konstantfaktor. Ibland säger man ordo fast man borde mena theta.

Några typiska exempel Vanliga funktioner i asymptotisk notation kan vara: 1, log n, n, n log n, n 2, 2 n För olika operationer: Θ(1) Θ(log n) Θ(n) Θ(n log n) Indexering i array Binärsökning, x n med kvadreringsalgoritmen add(0, x) i en ArrayList med n värden sedan tidigare Snabba sorteringsmetoder Θ(n 2 ) Enkla sorteringsmetoder

Olika fall Det är inte sjävklart att samma algoritm alltid tar lika lång tid för ett visst n. Exempel: Sortering kan bero på hur listan ser ut innan. Finns algoritmer med inbyggd slump!

Olika fall Instickssortering (togs upp i Prog1) tar: Θ(n) tid/operationer i bästa fall Θ(n 2 ) tid/operationer i genomsnitt Θ(n 2 ) tid/operationer i värsta fall Obs! Alla är theta, exakta begränsningar.

Linjär sökning for (int i=0; i<n; i++) { if (x==a[i]) { return i; } } return -1; Tittar på alla element tills den hittar rätt. Tid för lyckad sökning (elementet finns): Θ 1 i bästa fall Θ n i genomsnitt Θ n i värsta fall Konstantfaktor mellan genomsnitt och värsta fall? Misslyckad linjär sökning alltid Θ(n)

Uppskatta tid i praktiken Om du vet att tiden t(n) för en algoritm är Θ(f(n)) kan man för stora värden uppskatta tiden som: t n = c f n Konstanten c kommer att vara beroende av implementation och system (programmerarens kod, dator, programmeringsspråk, operativsystem, ) Man kan uppskatta c genom att mäta praktiskt på ett program som implementerar algoritmen. Ofta mäta på ett ganska stort n för att uppskatta för ett mycket stort n

Exempel Θ(n log n) Det har tagit 10 sekunder att köra ett program som implementerar en Θ(n log n)-algoritm när n = 1000. Uppskatta tidsanvädningen för n = 100000. t n = c log n t 1000 = c 1000 log 1000 = 10 c = 1 300 = 0.00333 t 100000 = 0.00333 100000 log 100000 = 333 5 = 1666 s 27 min Tips! Enkla beräkningar om vi tänker på att vi själva väljer vilken logaritmbas vi arbetar i.

Sortering Grundläggande algoritm Vi har sett hur sortera en gång gör senare sökningar billiga (binärsökning) Ibland kan andra algoritmer också förstås som sortering: Välj elproduktion efter viktning av kostnad och koldioxidutsläpp Lösning: Sortera på den viktade kostnaden, välj de n första för att täcka behovet

Instickssortering Grundtanke sortera de n första elementen genom att se till att de n-1 första är sorterade och sedan bara hitta rätt plats för det n:te Rekursivt, två algoritmer: Sortera a[0] a[n-1]: Sortera a[0] till a[n-2] Infoga a[n-1] bland de sorterade elementen Infoga x i a[0]...a[m]: Om m = 0 eller x >= a[m-1] Lägg x på plats m Annars Flytta a[m-1] till a[m] Infoga x i a[0] a[m-1] // Plats m antas vara ledig

Instickssortering Förloppet för den yttersta nivån av sortering, inklusive infogningen.

Rekursiv instickssortering i kod: I /** * Sorts the elements a[0], a[1], a[2],... a[n-1] * @param a the array to be sorted * @param n number of elements to be sorted (starting from index 0) */ public static void sort(int[] a, int n) { if (n > 1) { sort(a, n-1); insert(a[n-1], a, n-1); } }

Rekursiv instickssortering i kod: II /** * Inserts x in the array a[0],..., a[m]. * * Precondition: a[0] <= a[1] <=... <= a[m-1] * Postcondition: a[0] <= a[1] <=... <= a[m-1] <= a[m] * The old value of a[m] is destroyed */ public static void insert(int x, int[] a, int m) { assert a.length > m; if (m == 0 x >= a[m-1]) { a[m] = x; } else { a[m] = a[m-1]; insert(x, a, m-1); } }

Nya begrepp Förvillkor/precondition: Tillstånd som ska gälla när koden anropas Eftervillkor/postcondition: Tillstånd som ska gälla när koden är klar Kan tydliggöra när kod används rätt/fel utan att man behöver sätta sig in i allt koden gör på vägen.

Algoritmanalys för insert Vi kan fokusera på jämförelsen (m == 0 x >= a[m-1]) Låt s(n) representera antalet gånger den raden utförs vid anropet insert(x, a, n) Bästa fall: s(n) = 1 Värsta fall: s(n) = s(n 1) + 1 = s(n 2) + 1 + 1 = = s(0) + n = 1 + n I genomsnitt? s n 1 + n/2

Algoritmanalys för sort Låt t(n) vara antalet gånger jämförelsen inne i insert görs. Då får vi: t n = t n 1 + s n 1 Bästa fall: t(n) = t(n 1) + 1 = t(n 2) + 1 + 1 = = t(1) + n 1 = n 1 Värsta fall: t n = t n 1 + n = t n 2 + n 1 + n = n n 1 = 1 + 2 + 3 + + n 1 = 2 Genomsnitt: t n = n(n 1)/4

Konkret exempel Om det tar 1 s att sortera 10 3 slumptal med instickssortering. Hur lång tid tar det då att sortera 10 4, 10 5 eller 10 6 tal? Svar: 10 2, 10 4, 10 6 sekunder. 1000000 sekunder är ungefär 11 dygn

Urvalssortering Två delalgoritmer även här, men hitta största elementet före sorteringsrekursionen: Sortera a[0] a[n-1]: Byt plats på a[n-1] och det största elementet i a[0] a[n-1] Sortera a[0] a[n-2] Man kan inte veta vilket det största elementet är i en osorterad lista utan att titta på alla

Urvalssortering Måste alltid gå igenom hela listan, så värsta fall = bästa fall = genomsnitt = Θ n 2 jämförelser

Kan det gå bättre? Vi såg att kvadreringsalgoritmen vann genom att dela upp i mycket mindre delproblem i varje steg Kan vi göra det för sortering? Ja, flera exempel. Mergesort: 1. Dela mängden i två lika stora delar 2. Sortera dessa båda var för sig 3. Sammanfoga delarna Behöver bara jämföra ett element från vardera listan för att finna nästa

Illustration Mergesort

Dela upp

Sortera

Sammanfoga

Sammanfoga

Klart

Algoritmanalys mergesort Problem av storlek n löses genom uppdelning till två problem av storlek n/2, följt av sammanfogning. Tiden för sammanfogning är proportionell mot n. Varje jämförelse leder till ett nytt element i resultatet. Låt t(n) beteckna tiden att sortera n element. Vi får: c, n = 0 t n = ቐ 2t n + dn, n > 0 2 c och d är obekanta konstanter.

Algoritmanalys av mergesort Analys enklast om n exakt 2-potens, n = 2 k t(n) = 2t n 2 + dn = 2 2t n 4 + dn 2 + dn = 4t n + dn + dn = = 4 2 k n t + dnk = 2k nt 1 + dn log n d.v.s. tiden är Θ(n log n), mycket bättre än kvadratisk! (Kan visas även för andra n.)

Quicksort Idé: Dela upp genom att flytta små element till vänster och stora element till höger. Sortera de två avsnitten var för sig (rekursivt). Skillnaden mot mergesort är att dela upp först. De två avsnitten kan bli olika stora. Nackdel: Arbetsammare att skapa delproblemen. Fördel: Ingen sammanfogning.

Quicksort Vad är ett litet element? Välj element på måfå som delningselement p (pivotelement). Grunden är alltså denna partitionering (quicksort kallas också partition sort). Bra om k blir nära n/2 och dåligt om k blir nära 1 eller n. Partitioneringen appliceras rekursivt på de små respektive de stora talen.

Illustration av quicksort

Kommentarer quicksort Ofta snabb i praktiken! Svårt att välja pivotelement Θ(n log n) i genomsnitt Θ(n log n) i bästa fall Θ(n 2 ) i värsta fall Hur gör man när partitionerna blir små? (Gäller även mergesort.) Inte stabil, två ekvivalenta element kan byta plats Grund för effektiva metoder för att beräkna median, kvartil

Hur vet man om en rekursiv lösning är effektiv? Rekursiva lösningar kan vara mycket effektiva, men också ineffektiva Beräkningsarbetet beror på Arbetet att dela upp i delproblem Antalet delproblem Delproblemens storlek (jämför instickssortering och mergesort) Arbetet som krävs för att kombinera delproblemens lösningar till slutresultat Det finns problem som saknar effektiva, exakta lösningar. Att hitta den kortaste vägen som besöker n platser i någon ordning är ett av dem! Grenad rekursion är OK om delproblemen på varje nivå blir klart mindre än ursprungsproblemet.

Effektiva algoritmer, effektiva implementationer Datorer och mobiler är enormt snabba. Spelar effektiva algoritmer roll? Större minne gör att man kan ha större n än tidigare På 80-talet var en miljon element en väldigt lång array En miljon intar använder 4 MB Vi såg hur fort Θ n 2 växer, absolut problem om n > 1000000 På OU1 får ni se fall med Θ 2 n, grenad rekursion med nästan lika stora delproblem, absolut problem om n > 30 Andra problem kan vara Θ n!, absolut problem om n > 12 Effektiva algoritmer behövs

Effektiva implementationer Ofta mindre viktigt att koden som implementerar algoritmen är så snabb som möjligt Olika språk kan göra skillnad Undvika att göra saker två gånger Olika ordning som stegen utförs i Särskilt om läsning/skrivning i minnet kan göras effektivare Är tid pengar? Tid är i alla fall ström Viktigt för mobiler eller datacenter (UPPMAX drar 350 kw)

Effektiva implementationer Bra om väderprognoser blir klara innan vädret blir av Sätter gränser för simuleringar och dataanalys i biologi, kemi, fysik Realtidssytem: robotar, självkörande bilar, mobilmaster, ljud och video, spel Hård realtid och mjuk realtid fokus på värsta fall eller genomsnitt Robothandel på finansmarknader Jätteboomen i AI kom mycket av att algoritmer som förut var för tunga nu går effektivt att göra på grafikkort, med rätt programvara

Algoritmanalys Vilken kurva är vilken? Θ x Θ x log x Θ x 2 För små värden avgör konstanterna helt (här upp till x = 100)

Obligatorisk uppgift 2 Numerisk kalkylator 7 * (5 + 9) 7 * 5 + 9 1 + (2 + (3 + (4 + (5 + 6)))) 1=x (variabeltilldelning åt höger, inte som Java) x + 3 sin(0.5=x)=y y ans (alltid svaret på föregående operation)

Vad ska göras? Läs en rad med ett uttryck Tolka det uttrycket Parsing, vi använder recursive descent som angreppssätt Använd en tokenizer för att dela upp uttrycket i ord Hantera fel systematiskt, med undantag (exceptions) Evaluera uttrycket Med de variabler som finns definierade Spara nya variabeltilldelningar Med avbildningsklasser (Map, TreeMap, HashMap)

Evaluering av aritmetiska uttryck a + (b 1) d e (f + g h)/4 Med prioritetsregler (vänster till höger, multiplikation före addition, parenteser har företräde), delas detta upp i följande uttryck: t 1 = b 1 t 2 = t 1 d t 3 = g h t 4 = f + t 3 t 5 = e t 4 t 6 = t 5 /4 t 7 = a + t 2 t 8 = t 7 t 6 Hur ska vi skriva ett program som hittar den ordningen?

Recursive descent a + (b 1) d e (f + g h)/4 Beskriv uttrycket utifrån, från lägst prioritet Tre termer a b 1 d e (f + g h)/4 Termerna ska beräknas för sig och adderas/subtraheras

Recursive descent Fortsätt nedåt Vi fortsätter att dela upp uttrycket i delar med högre och högre prioritet Nästa steg är faktorer, varje term består av en eller flera Exempel: Termen e (f + g h)/4 Ingående faktorer: e variabel f + g h uttryck 4 konstant Faktorerna beräknas var för sig och multipliceras/divideras sedan tillsammans.

Sammanfattning Ett uttryck En sekvens av en eller flera termer, åtskilda med + eller En term En sekvens av en eller flera faktorer, åtskilda med * eller / En faktor Ett tal eller ett uttryck omgivet av parenteser Rekursion!

Beskriva syntax BNF (Backus-Naur-form) expression ::= term [( + / - ) term]* Syntaxdiagram

Beskriva syntax Pseudokod ( nästan Java) double expression() double sum = term() while ( nästa tecken + eller - ) Läs förbi tecknet if ( det var ett + ) else sum = sum + term() sum = sum - term() Syntaxdiagram return sum Ett uttryck är en sekvens av en eller flera termer, åtskilda med + eller

Vad är viktigt här? Metoden term får inte läsa för långt term måste själv veta när den ska sluta Men som vi sett byggs en term av factor som i sin tur kan vara vilket uttryck (expression) som helst inom parentes Löser rekursionen det åt oss?

Term Pseudokod double term() double prod = factor() while ( nästa tecken * eller / ) Läs förbi tecknet if ( det var ett * ) else prod = prod*factor() prod = prod/factor() Syntaxdiagram return prod En term är en sekvens av en eller flera faktorer, åtskilda med * eller /

Factor Pseudokod double factor() if ( nästa tecken ( ) Läs förbi tecknet result = expression() Syntaxdiagram if ( nästa tecken inte ) ) FEL! return result else return number() En faktor är ett tal eller ett uttryck omgivet av parenteser

Kommentarer Det är alltid nästa symbol som avgör vilken väg som ska väljas Inte titta i förväg Vi blandar inläsning av tecken och tal - man vet inte i förväg om man ska läsa ett tecken eller ett tal. Koden skall hoppa över blanktecken Det finns ingen syntaktisk beskrivning av tal Övning: konstruera kod/diagram för number

Kommentarer Hur vet man när ett uttryck är slut? Vad är basfallet/basfallen i rekursionen? Vad händer om man ger felaktiga indata? Vilka typer av fel kan förekomma?

Dela upp indata Vi vill läsa ord, tal, operatorer strunta i blanksteg 5+3, 5 + 3, 5+ 3, 5 + 3 ska se likadana ut för koden En fil eller det du skriver på tangentbordet blir en ström av tecken En tokeniser går från teckenströmmen till en ström av ord, tokens Vi har redan stött på Scanner Vad som är ett giltigt ord beror lite på tillämpningen. Är kod-exempel ett eller två ord?

Stokenizer Vi tillhandahåller en färdig klass Stokenizer som är en anpassning av standardklassen StreamTokenizer. Den är anpassad för det vi behöver i den här uppgiften. Följande olika typer av tokens/ord finns: Tal (number): börjar med siffra Ord (word): börjar med bokstav Radslut (EOL, end of line) Slut på strömmen (EOS, slut på infil/insträng) Övriga tecken

Stokenizer Tre viktiga konstruktorer: public Stokenizer() Läs från standard input (tangentbordet) public Stokenizer(String line) Läs strängen line public Stokenizer(FileReader r) Läs från den redan öppnade filen som representeras av r Ett objekt som väl skapats användas överallt där ett sådant objekt behövs Oavsett vilken konstruktor som användes

Tre huvudfamiljer av metoder int nexttoken() Gå framåt i strömmen boolean isxxx() Är aktuellt token av typen XXX (Number/Word/EOL/EOS) typ getxxx() Returnerar aktuellt tokeninnehåll. Ger fel om det inte matchar aktuell tokentyp. Går inte att ta reda på tal om det är ett ord, o.s.v.

Övriga metoder String tostring() Beskriver aktuellt token i textform med diverse information String gettoken() Aktuellt token som sträng String getprevioustoken() Närmast föregående token som sträng

Använda Stokenizer Det går lätt att hämta Stokenizer Dokumentation: http://www.it.uu.se/edu/course/homepage/prog2/publiccode/stokenizer/doc Källkod: http://www.it.uu.se/edu/course/homepage/prog2/publiccode/stokenizer/stokenizer.java Demonstrationsprogram http://www.it.uu.se/edu/course/homepage/prog2/publiccode/stokenizer/stokenizertest.java Med exempel på läsning från tangentbord, sträng och fil

Felhantering Olika språk och olika problem har olika filosofi om felhantering Är det viktigaste att programmet fortsätter köra? Kanske en bra idé att bara köra vidare, även om fel inträffar Inte säkert att det går/ger något rimligt resultat Är det viktigaste att programmet ger rätt svar? Se till att fel alltid rapporteras/hanteras Hantering kan innebära låt programmet krascha

Hur kan vi hitta ett fel? Metoder kan returnera när något går fel if (x.move()!= STATUS_OK) { return false; } if (x.eat()!= STATUS_OK) { return false; } if (x.sleep()!= STATUS_OK) { } return false; Om vi glömmer att kolla feltillstånd, eller gör det på fel sätt, kan det vara väldigt svårt att hitta grundorsaken Det här sättet att hantera fel är vanligt i lågnivåspråk, som C Många farliga säkerhetsbuggar kommer av felaktig felhantering

Varför blir detta svårt? Flera lager av metoder kan anropa varandra Om kalkylatorn är rekursiv i flera led och använder Stokenizer, som använder Java-biblioteken för att läsa från en fil som finns på en USB-sticka som du tog ur datorn Vilken del av koden vet vad som är rätt sak att göra? Felet måste bubbla upp i flera lager av anrop Varje metod skulle behöva kunna returnera en beskrivning av vilka fel som inträffade Bökigt, ändå risk att någon glömmer

Javas lösning - Undantag En metod i Java kommunicerar med anroparen genom returvärdet och förändringar av de objekt som skickades in och genom att potentiellt kasta ett undantag (Exception) Eller mer exakt en Throwable (basklass till Exception) Vissa typer av drastiska fel som OutOfMemoryError och StackOverFlowError är Throwable, men inte Exception

Vad är då ett undantag? En instans av en klass Egentligen som vilken klass som helst Ärver (direkt/indirekt) från java.lang.exception Man talar om en subklass till Exception Klassens typ och innehåll beskriver ett fel som har inträffat Arv är mycket vanligt i objektorienterad programmering Det viktigaste är att en instans av en subklass kan användas överallt där basklassen används Varje FileNotFoundException är ett Exception. Men varje Exception är inte ett FileNotFoundException.

Klasshierarki java.lang.object java.lang.throwable java.lang.exception java.io.ioexception» java.io.filenotfoundexception java.lang.runtimeexception java.lang.error java.lang.virtualmachineerror» Java.lang.StackOverFlowError Naturligtvis många fler Mycket vanligt att skapa egna undantagsklasser

Inte vilken klass som helst Det är bra att vi har ett sätt att kategorisera fel Java har inbyggt stöd i språket för att hantera undantag Undantag kan kastas När ett fel inträffar Metoden som kastar slutar köras Undantag propageras Från den metod som kastar, till den metod som anropade den, till den metod som anropade den

Fånga undantag Undantag kan fångas Den metod i anropskedjan som begriper sig på felet kan fånga det Slutar propagera Ibland vill man återkasta (vi kunde inte hantera hela felet, men vi städar upp lite på vägen) Då propagerar det vidare Ett undantag som inte fångas någonstans slutar i att hela programmet slutar köras och att ett fel skrivs ut Det har vi sett många gånger

Vad gör man när man fångar? Olika svar kan vara rätt Ovanligt: Fånga utan att göra någonting Ger fel som är svåra att felsöka Vanligt: Skriva ut (på skärmen eller i loggfil) att ett fel inträffade Försöka igen Använda någon typ av reservlösning För kalkylatorn: Rapportera felet och låt användaren göra om

Tillbaka lite till SpecialStack Vi har redan kastat RuntimeException throw new RuntimeException("Stack is empty."); throw är det nya här, resten är helt vanlig Javakod som vi redan sett Skapa ett nytt objekt, anropa konstruktorn som tar en sträng Många undantagsklasser har konstruktorer som tar emot en sträng som fungerar som felmeddelande

Hur borde vi ha gjort? Egen undantagsklass public class SpecialStackException extends RuntimeException { } public SpecialStackException(String msg) { } super(msg); // anropar basklassens konstruktor Inte mer än så! Bara en konstruktor som tar felmeddelandet.

Undantag i kalkylatorn I OU2 ska vi ha två egna undantagsklasser: public class SyntaxException extends RuntimeException { } public SyntaxException(String msg) { } super(msg); public class EvaluationException extends RuntimeException { } public EvaluationException(String msg) { } super(msg);

RuntimeException eller Exception Java skiljer egentligen på RuntimeException och Exception RuntimeException kan kastas var som helst Andra subklasser till Exception får bara kastas om de fångas eller om metoden anger throws i metodhuvudet Om SyntaxException ärvde från Exception skulle exempelvis metoden term behöva se ut något så här double term(stokenizer t) throws SyntaxException Vi skippar detta och väljer därför att ärva från RuntimeException

Var upptäcks syntaxfel i Inte där! kalkylatorn? Inte där heller! Men här! Däremot kan expression sluta för tidigt, om meningen var att läsa en hel rad

Vad kan gå fel? Slutparentes saknas Första token är varken startparentes eller ett tal Vad göra? Kasta SyntaxException!

Exempel i kod if (tokenizer.getchar()!= ) ) { throw new SyntaxException("Expected ) but found " + tokenizer.gettoken()); } Detta propageras tills det fångas

Fånga i kod Om ett uttryck är ogiltigt är det koden som bad om att parsa uttrycket som kan hantera felet I en tänkt main-metod i lösningen while (true) { try { System.out.print("? "); double value = expression(); System.out.println("Value: " + value); } catch (SyntaxException se) { System.out.println("Syntax error: " + se.getmessage(); // Skip to end of line } }... Koden under catch körs bara om ett SyntaxException inträffar. Om inget undantag inträffar, körs den inte. Om ett annat undantag inträffar, fortsätter det att propageras. Varför är det viktigt att ordna Skip to end of line?

Fånga undantag Varför skriver vi inte catch (Exception e) i stället? Då hanterar vi alla fel. Men vi vet inte vad felet egentligen är. Är åtgärden att läsa till slutet av raden och sedan frågan efter nästa uttryck rätt i alla situationer?

Undantag eller assert Undantag är ett sätt att hantera fel Fel som det går att undvika, som antas kunna hända i normal drift assert är ett sätt att uttrycka saker som alltid ska gälla Kan användas av automatiska verktyg för att analysera koden Om man kör koden med vissa inställningar kastas ett Error (inte Exception) Inte meningen att det ska fångas eller något man ska lita på, bara ett sätt att identifiera misstag under utveckling Skillnad på fel under körning och logiska fel/misstag i koden

Avbildningar - Map Vi har använt arrayer och listor (ArrayList) Java har inbyggt stöd för fler datastrukturer Avbildningar bland de mest användbara Nycklar av en datatyp Högst ett värde per nyckel i avbildningen Samma värde kan förekomma för flera nycklar Vanligt att nycklarna är strängar Namn och tillhörande värden I kalkylatorn kan det vara variabelnamn och deras värden

I kod import java.util.treemap; import java.util.hashmap; HashMap<String, double> vars = new HashMap<String, double>(); vars.put("x", 15.0); vars.put("y", vars.get("x")); if (!vars.containskey("y")) { System.out.println("Strange, key just added does not exist."); }

Försöka läsa nyckel som inte finns if (vars.get("z") == null) { System.out.println("Getting a key which does not exist yields null, not an Exception"); } Fel som bara indikeras med nullvärde, stor risk att man får ett NullPointerException när det värde man plockade ut ska användas Svårt att se att det berodde på att det saknades i avbildningen Att spara det utplockade värdet i en double ger detta Värdet som lagras är ett objekt av klassen Double som sedan ska konverteras till primitiva typen double

Sammanfattning Kod ska vara tydlig Nästa uppgift: numerisk kalkylator Rekursiv parsning av tokens Undantag hjälper oss att skriva rak kod Utan att missa att hantera felen Avbildningar är flexibla datastrukturer