Rekursion och induktion för algoritmkonstruktion

Relevanta dokument
Rekursion och induktion för algoritmkonstruktion

Rekursion och induktion för algoritmkonstruktion

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

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

SCB :-0. Uno Holmer, Chalmers, höger 2 Ex. Induktiv definition av lista. // Basfall

Introduktion till algoritmer - Lektion 4 Matematikgymnasiet, Läsåret Lektion 4

FÖRELÄSNING 2, TDDC74, VT2018 BEGREPP PROBLEMLÖSNING MED HJÄLP AV FALLANALYS PROBLEMLÖSNING MED HJÄLP AV REKURSION

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

Föreläsning 13. Rekursion

Föreläsning 11: Rekursion

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

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

Föreläsning 1, vecka 7: Rekursion

Föreläsning 9 Innehåll. Söndra och härska. Fibonaccitalen. Söndra och härska. Divide and conquer teknik för att konstruera rekursiva algoritmer.

Magnus Nielsen, IDA, Linköpings universitet

BEGREPP HITTILLS FÖRELÄSNING 2 SAMMANSATTA UTTRYCK - SCHEME DATORSPRÅK

Sökning och sortering

Övningshäfte 2: Induktion och rekursion

Föreläsning 12. Söndra och härska

Rekursion. Koffman & Wolfgang kapitel 5

Föreläsning 9 Innehåll. Söndra och härska. Fibonaccitalen. Söndra och härska. Divide and conquer teknik för att konstruera rekursiva algoritmer.

Tommy Färnqvist, IDA, Linköpings universitet

Exempel: Förel Rekursion III Nr 14. Uno Holmer, Chalmers,

Föreläsning 5 Innehåll

Rekursion. Att tänka rekursivt Att programmera rekursivt i Java Exempel. Programmeringsmetodik -Java 254

Lösning av några vanliga rekurrensekvationer

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

TDDI16 Datastrukturer och algoritmer. Algoritmanalys

Algoritmer, datastrukturer och komplexitet

Algoritmer, datastrukturer och komplexitet

Föreläsning 8 Innehåll

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

Föreläsning 5. Rekursion

Algoritmer och datastrukturer H I HÅKAN S T R Ö M B E R G N I C K L A S B R A N D E F E L T

Tentamen i Programmering

Programkonstruktion och Datastrukturer

Lösningar till utvalda uppgifter i kapitel 4

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

Tommy Färnqvist, IDA, Linköpings universitet

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

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

Föreläsning 12. Söndra och härska

Dekomposition och dynamisk programmering

Procedurer och villkor. Rekursiva procedurer. Exempel: n-fakultet

Procedurer och villkor

Föreläsning 13. Dynamisk programmering

Våra enkla funktioner eller procedurer

Rekursiva algoritmer sortering sökning mönstermatchning

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

Övningshäfte 1: Induktion, rekursion och summor

Rekursion. 1. Inledning. vara en fot bred.

Introduktion till programmering D0009E. Föreläsning 5: Fruktbara funktioner

Övning 2. (Länkade) Listor, noder

Programmeringsmetodik DV1 Programkonstruktion 1. Moment 4 Om rekursion. PK1&PM1 HT-06 moment 4 Sida 1 Uppdaterad

TDDC74 Programmering: Abstraktion och modellering Tentamen, onsdag 19 oktober 2016, kl 14 18

6 Rekursion. 6.1 Rekursionens fyra principer. 6.2 Några vanliga användningsområden för rekursion. Problem löses genom:

Föreläsning 1 Datastrukturer (DAT037)

DD1361 Programmeringsparadigm. Carina Edlund

Datastrukturer D. Föreläsning 2

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

C++ Funktioner 1. int summa( int a, int b) //funktionshuvud { return a+b; //funktionskropp } Värmdö Gymnasium Programmering B ++ Datainstitutionen

Summera godtyckligt antal tal. Programkonstruktion. Moment 4 Om rekursion. Fullständigt resonemang för summeringen. Analys av summeringsproblemet

MA2047 Algebra och diskret matematik

Föreläsning 3: Dekomposition. Dekomposition

Föreläsning 9: Talteori

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

Repetition i Python 3. Exemplen fac. Exemplen fac motivering. Exemplen fac i Python

inte följa någon enkel eller fiffig princip, vad man nu skulle mena med det. All right, men

MS-A409 Grundkurs i diskret matematik Appendix, del I

Instuderingsfrågor, del D

MA2047 Algebra och diskret matematik

Föreläsning 2 Datastrukturer (DAT037)

Datastrukturer. föreläsning 2

Föreläsning 5: Dynamisk programmering

a = a a a a a a ± ± ± ±500

String [] argv. Dagens Agenda. Mer om arrayer. Mer om arrayer forts. String [] argv. argv är variabelnamnet. Arrayer och Strängar fortsättning

Föreläsning 11 Datastrukturer (DAT037)

Introduktion till programmering SMD180. Föreläsning 9: Tupler

Objektorienterad programmering E. Back to Basics. En annan version av printtable. Ett enkelt exempel. Föreläsning 10

Länkade listor kan ingå som en del av språket, dock ej i C Länkade listor är ett alternativ till:

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

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

Algoritmer, datastrukturer och komplexitet

TENTAMEN PROGRAMMERINGSMETODIK MOMENT 2 - JAVA, 4P

Föreläsning 5: Kardinalitet. Funktioners tillväxt

Föreläsning 10 Innehåll. Prioritetsköer och heapar. ADT Prioritetskö. Interface för Prioritetskö. Exempel på vad du ska kunna

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

Objektorienterad programmering Föreläsning 8. Copyright Mahmud Al Hakim Agenda (halvdag)

Datastrukturer och algoritmer

Programmering II (ID1019) :00-11:00

1, 2, 3, 4, 5, 6,...

Linjärt minne. Sammanhängande minne är ej flexibelt. Effektivt

Klassdeklaration. Metoddeklaration. Parameteröverföring

Föreläsning 2 Datastrukturer (DAT037)

Tentamen i Grundläggande Programvaruutveckling, TDA548

1. Inledning, som visar att man inte skall tro på allt man ser. Betrakta denna följd av tal, där varje tal är dubbelt så stort som närmast föregående

MMA132: Laboration 2 Matriser i MATLAB

Iteration while-satsen

Transkript:

Informationsteknologi Tom Smedsaas 22 januari 2006 Rekursion och induktion för algoritmkonstruktion Att lösa ett problem rekursivt innebär att man uttrycker lösningen i termer av samma typ av problem som dock måste vara i någon mening enklare. Man delar alltså upp problemet i ett eller flera delproblem (av samma typ), löser dessa (på samma sätt) och sedan kombinerar sedan lösningarna av delproblemen till en lösning av ursprungsproblemet. Exempel: Beräkning av fakultet Iterativ definition: Iterativ beräkning: n! = n(n 1)(n 2)... 2 1 0! = 1 public static int fac(int n) { int p = 1; for (; n>1; n--) p = p*n; return p; Rekursiv definition: n! = { 1 om n = 0 n(n 1)! om n > 0 Rekursiv beräkning: public static int fac(int n) { if (n > 0) return (n*fac(n-1)); else return 1; Ett eller flera rekursionsterminerande fall måste definieras för att förhindra oändlig rekursion I detta exempel blir den rekursiva metoden knappast enklare eller effektivare än den iterativa men rekursiva resonemang är ett ändå kraftfullt sätt att hitta effektiva algoritmer. Att uttrycka sig rekursivt är ofta naturligt i matematiken. Deriveringsreglerna ( derivatan av en summa är summan av derivatorna ) är ett exempel på detta. 1

Inte desto mindre brukar rekursion betraktas som svårt när man börjar med det i programmering. Orsaken till detta är säkert att man i de flesta fall lärt sig programmera med iterationer. Frågor att besvara vid konstruktion av en rekursiv algoritm: 1. Hur kan jag dela upp ursprungsproblemet i mindre problem av samma slag? 2. Hur kombinerar jag lösningarna till delproblemen till en lösning på ursprungsproblemet? 3. Vilka specialfall är lämpliga? Kommer de alltid att nås, oberoende av indata? Detta är mycket besläktat med induktionsbevis: Vi antar att vi kan lösa problemet för ett eller flera mindre problem. Sedan visar man hur man med hjälp av dessa lösningar kan lösa ursprungsproblemet. Precis som i induktionsbevis så måste man ha ett eller flera basfall. Exempel: Skriv ut en sträng med n tecken i omvänd ordning. Hur definiera problemet i termer av sig självt? Induktionsantagande: Antag att vi kan lösa problemet för n 1 tecken. Basfall: Att skriva ut ett tecken i omvänd ordning. Trivialt. Vi kan välja ut de n 1 tecknen på n olika sätt men två av dessa förefaller naturligast: 1. de n 1 första tecknen i strängen eller 2. de n 1 sista tecknen i strängen. Pröva först alternativ 1 d.v.s. vi antar att vi kan skriva ut alla utom det sista tecknet i omvänd ordning. Eftersom det sista tecknet skall vara först måste vi börja med att skriva det. Algoritm skriv strängen i omvänd ordning om längden är 1 eller mindre så skriv strängen annars skriv det sista tecknet skriv alla utom det sista i omvänd ordning Övning 1: Implementera ovanstående algoritm. 2

Alternativ algoritm Pröva i stället alternativ 2, d.v.s. vi antar att vi kan skriva ut alla tecken utom det första i omvänd ordning. Eftersom det första skall ut sist så börjar vi med de n 1 sista. skriv strängen i omvänd ordning om längden är 1 eller mindre så skriv strängen annars skriv alla utom det första i omvänd ordning skriv det första tecknet Även detta fungerar! Första tecknet skrivs ju sist efter alla andra tecken. Övning 2: Skriv en rekursiv metod void printb(int x, int b) som skriver ut x i basen b (Förutsätt, för enkelhetens skull att b< 10) Övning 3: Skriv en metod som läser hela tal från standard input och skriver ut talen i omvänd ordning UTAN att använda listor eller arrayer. Inläsningen avbryts när 0 läses. Det finns ofta möjlighet att välja de mindre problemen på olika sätt. I ovanstående exempel hade vi två naturliga val men vilket vi valde spelar egentligen ingen roll. I andra fall så kan vissa val ge bättre (effektivare) algoritmer än andra. Exempel: Beräkning av x n Vi skall beräkna x n, n heltal 0, med upprepade multiplikationer. Första försök Den rekursiva definitionen x n = kan realiseras med följande metod { 1 om n = 0 xx n 1 om n > 0 public static float power(float x, int n) { if (n > 0) return x*power(x,n-1); else return 1; Anropet pow(x,1000) genererar 1000 multiplikationer. 3

Andra försök Vi kan utgå från följande rekursiva definition 1 om n = 0 x n = (x n/2 ) 2 om n > 0 och n jämn xx n 1 om n > 0 och n udda public static float power(float x, int n) { if (n == 0) return 1; else if (n % 2 == 0) //n jämn return( sqr(power(x,n/2)); else //n udda return( x*power(x,n-1) ); (Anm: sqr måste definieras) Anropet pow(x,1000) kommer att generera följande sekvens av anrop: power(x,1000) power(x,500) power(x,250) power(x,125) power(x,124) power(x,62) power(x,31) power(x,30) power(x,15) power(x,14) power(x,7) power(x,6) power(x,3) power(x,2) power(x,1) power(x,0) Varje uppväckning av pow utom den sista innehåller en multiplikation (kvadrering eller multiplikation med x) vilket innebär att resultatet beräknas med sammanlagt 15 multiplikationer. Övning 4: Implementera ovanstående algoritm utan att använda rekursion. Exempel: Polynomevaluering Problem: Beräkna värdet av polynomet p n (x) = a 0 + a 1 x + a 2 x 2 +... + a n x n 4

Algoritm 1 Induktionsantagande: Antag att vi kan beräkna värdet av p n 1 (x) Basfall: Beräkna p 0 (x) - Trivialt Induktionssteg: Vi beräknar p n (x) som p n (x) = p n 1 (x) + a n x n Induktionssteget kan göras med en addition och n multiplikationer vilket totalt ger n additioner och cirka n 2 multiplikationer. (Vi har ovan sett att det går att beräkna x n med färre multiplikationer men vi skall i stället söka en annan algoritm) Algoritm 2 I första försöket beräknar vi x n från scratch vilket naturligtvis är onödigt eftersom vi i steget innan beräknade x n 1. Genom att lägga till detta i vårt antagande kan vi få en bättre algoritm Induktionsantagande: Antag att vi kan beräkna värdet av p n 1 (x) och x n 1 Basfall: Beräkna p 0 (x) - Trivialt Induktionssteg: Vi beräknar p n (x) som p n (x) = p n 1 (x) + a n xx n 1 Induktionssteget kräver nu en addition och två multiplikationer dvs totalt n additioner och 2n multiplikationer. Algoritm 3 I algoritm 1 och 2 tog vi bort den sista koefficienten när vi skulle göra ett mindre problem. Man kan också ta bort den första koefficienten: Induktionsantagande: Antag att vi kan beräkna värdet av p n 1 (x) där p n 1(x) = a 1 + a 2 x + a 3 x 2 +... + a n x n 1 Basfall: Beräkna p 0 (x) - Trivialt Induktionssteg: Vi beräknar p n (x) som p n (x) = xp n 1(x) + a 0 Induktionssteget kräver nu en addition och endast en multiplikation dvs totalt n additioner och n multiplikationer. Denna algoritm, som brukar kallas Horners schema, är standard för att evaluera polynom. Den implementeras vanligen iterativt dvs 5

public static double p( int n, float x ) { double y = a[n]; for ( ; n>0; n-- ) y = y*x + a[n-1]; return y; Exempel: Hanois torn En mängd med n brickor av olika storlek, alla med hål i mitten, ligger travade på en pinne (A) i storleksordning med den största underst. Problemet går ut på att flytta hela traven till en annan pinne (C) under iakttagande av följande regler: 1. Endast en bricka får flyttas per gång. 2. En större bricka får aldrig läggas på en mindre. Till hjälp har man ytterligare en pinne (B) som får användas för mellanlagring. Induktionsantagande: Vi kan lösa problemet med n 1 brickor. Basfall: Flytta en bricka. Trivialt. Induktionssteg: Vi gör på följande sätt 1. Flytta de n 1 översta brickorna till B. 2. Flytta den kvarvarande från A till C. 3. Flytta de n 1 brickorna på B till C. Problemet löses således rekursivt genom att lösa två problem av storlek n 1. Totala antalet brickförflyttningar b(n) ges av följande differensekvation: { 1 om n = 1, b(n) = b(n 1) + 1 + b(n 1) om n > 1. som har lösningen b(n) = 2 n 1 (Lösningen kan erhållas antingen genom att expandera ekvationen eller genom någon standardteknik för att lösa linjära differensekvationer.) Algoritmen är således mycket tidskrävande om n är stort men det är den bästa möjliga! Det är lätt att inse att lösningen till ett problem av storlek n faktiskt kräver lösning av två problem av storlek n 1 först för att frilägga understa brickan och sedan för att få tillbaka alla brickor på den understa på en ny pinne. 6

Övning 5: Implementera en metod Hanoi(char from, char to, char help, int n) som skriver ut hur flyttningen av n brickor från from till to med hjälp av help skall göras. Exempel: Fibonaccitalen Fibonaccitalen F n definieras enligt 0 om n = 0, F n = 1 om n = 1, F n 1 + F n 2 om n > 1. Definitionen ovan kan användas för att skriva en metod som returnerar det n:te Fibonaccitalet: public static int fib(int n) { if ( n==0 ) return 0; else if ( n==1 ) return 1; else return fib(n-1) + fib(n-2); Detta är en korrekt metod men den är hopplöst ineffektiv för stora värden på n. För att inse det kan vi räkna hur många additioner anropet fib(n) utför. För detta antal T (n) gäller: { 0 om n 1, T (n) = 1 + T (n 1) + T (n 2) om n > 1. Observera likheten med Fibonaccitalen! Detta är en linjär differensekvation och den homogena ekvationen (som är lika med Fibonaccitalen!) har karaktäristiska ekvationen r 2 r 1 = 0, som har lösningen r 1,2 = 1 ± 5, 2 dvs. den homogena ekvationen har lösningen F (n) = ar n 1 + br n 2, där a och b bestämmes ur begynnelsevillkoren. 7

Eftersom T (n) = 1 är en partikulärlösning, kan den allmänna lösningen skrivas T (n) = ar n 1 + br n 2 1. Genom att använda begynnelsevillkoren kan a och b bestämmas och ger lösningen a =(1 r 2 )/(r 1 r 2 ) b = (1 r 1 )/(r 1 r 2 ) Eftersom r 1 1.618 och r 2 0.618 < 1 så ser man att, för stora n, gäller T (n) 1.618 n Antalet additioner växer således exponentiellt. Övning 6: Antag att additionen och det övriga arbetet i ibonacci-metoden tar 1 µs. Hur lång tid tar fib(50) respektive fib(100)? Som framgår av övningen är programmet helt oanvändbart för stora n. Programmet är trädrekursivt det vill säga varje anrop resulterar i två nya anrop. Detta kan potentiellt ge orimliga exekveringstider. Som följande exempel visar så är det dock inte alltid så. I själva verket är många klassiska effektiva algoritmer trädrekursiva (t.ex. sorteringsalgoritmer, snabb Fouriertransform etc.). Exempel: Horisontlinjeproblemet Problem: Konstruera horisontlinjen till n rektangulära byggnader. Varje byggnad representeras av en trippel bestående av x-koordinat för vänster respektive höger vägg samt byggnadens höjd. Induktionsantagande: Antag att vi kan lösa problemet för n 1 byggnader. Induktionssteg: Man stoppar in en ny byggnad i horisontlinjen. Detta kräver O(n) operationer. Sammantaget: Ger en algoritm som kräver storleksordningen cn 2. operationer. Alternativt induktionsantagande: Vi kan konstruera horisontlinjen för n/2 byggnader. Induktionssteg: Om vi har horisontlinjerna för två olika mängder med hus med vardera n/2 byggnader, kan vi bygga den gemensamma horisontlinjen genom en sammanfogning av dessa (som kräver storleksordningen cn operationer) Sammantaget: Om vi delar de n byggnaderna i två lika stora mängder och konstruerar horisontlinjerna för dessa var för sig och sedan sammanfogar dessa så får vi en algoritm som kräver cn log n operationer. 8

Exempel: Sortering Problemet att sortera n element i storleksordning kan även det lösas på flera olika sätt. Algoritm 1 Induktionsantagande: Vi kan sortera n 1 element. Basfall: Vi kan sortera 1 element. Induktionssteg: Stoppa in det n-te elementet bland de n 1 redan sorterade elementen så att sorteringen bibehålls Kod: public static void sort(float [] a, int n) { if ( n > 1 ) { sort( a, n-1 ); // sortera de n-1 första float x = a[n-1]; int i = n-2; // flytta undan while ( i>=0 && a[i]>x ) { a[i+1] = a[i]; i--; a[i+1] = x; // lägg in sista Detta är den vanliga enkla instickssorteringen. Algoritm 2 Induktionsantagande: Vi kan sortera n/2 element. Induktionssteg: Dela mängden i två delar med vardera n/2 element, Sortera dessa var för sig och sammanfoga sedan de två sorterade delarna. Sortera n element 1. dela i två lika stora delar 2. sortera delarna var för sig 3. sammanfoga delarna Arbetet att sammanfoga de två sorterade delarna är proportionellt mot antalet element. Låt T (n) beteckna tiden att sortera n element. Då gäller { c om n = 0, T (n) = 2T (n/2) + dn om n > 0. 9

Om n är en jämn 2-potens, n = 2 k så gäller T (n) =2T (n/2) + dn = 2(2T (n/4) + dn/2) + dn = =4T (n/4) + dn + dn =... =2kT (n/2 k ) + dnk = =nt (1) + dn log n dvs tiden är O(n log n). Exempel: Växlingsproblemet På hur många sätt kan man växla a kronor i 100, 50, 10, 5 och 1-mynt (sedlar)? (t.ex. 90 kronor i 50+4*10, 9*10, 8*10 + 10*1 etc.) Formulera en lösning av problemet i termer av sig självt. Viktigt att rekursionsfallet/fallen löser ett i någon mening mindre problem: Ordna myntsorterna i någon ordning. Dela in växlingsförsöken i två grupper: de som inte använder något mynt av första sorten de som använder första sortens mynt Problemets lösning kan nu formuleras: Antalet sätt att växla a kronor vid användande av n olika sorters mynt är 1. antalet sätt att växla a kronor vid användande av alla utom den första sortens mynt (n 1 sorter) plus 2. antalet sätt att växla a d kronor användande alla n sorters mynt (d = 1:a myntsortens valör) Delproblem 1 är mindre än ursprungsproblemet eftersom det använder färre myntslag och delproblem 2 är mindre eftersom det växlar en mindre summa. Antag att myntsorterna representeras i en array change där change[1] = 1; change[2] = 5; change[3] = 10; change[4] = 50; change[5] = 100; Ger grundprogram public static int count( int a, int n ) { return count( a, n-1 ) + count( a-change[n], n ); Vilka specialfall behövs för att undvika oändlig rekursion? 10

a kan bli = 0 vilket innebär ett lyckat försök (räkna det) a kan bli < 0 vilket innebär ett misslyckat försök (räkna ej) n kan bli = 0 vilket innebär ett misslyckat försök (räkna ej) Slutlig version int count( int a, int n) { if ( a == 0 ) return 1; else if (a < 0) (n == 0) return 0; else return count(a,n-1) + count(a-change[n],n); Detta program är användbart om inte a och n är alltför stora men precis som i Fibonacci-exemplet så är tillväxten exponentiell. Programmet kan dock förbättras i detta avseende med hjälp av s.k. dynamisk programmering som dock inte beskrives här. Övning 7: Skriv en metod som skriver ut alla möjliga permutationer av en sträng. Vad är metodens komplexitet (dvs hur beror tiden av stränglängden n)? Syntaxanalys med recursive descent Rekursiva metoder är speciellt lämpliga när de data man skall behandla i sig är rekursivt uppbyggda. Ett exempel på detta är vanliga aritmetiska uttryck. Betrakta t.ex. följande uttryck a + (b + c) d + e (f + g h) Det finns ett antal regler för hur detta skall tolkas av typ multiplikation före addition, parenteser först och från vänster till höger vid lika prioritet. Det är inte alldeles enkelt (men naturligtvis väl genomförbart) att realisera dessa regler i ett program som läser och tolkar ett uttryck. Vi kan emellertid definiera uttryck på följande sätt: Ett uttryck är en sekvens av en eller flera termer med plustecken mellan. En term är en sekvens av en eller flera faktorer med gångertecken mellan. En faktor är antingen ett tal eller ett uttryck omgivet av parenteser. (För enkelhetens skull begränsar vi oss till addition och multiplikation) Observera att ovanstående definition av uttryck är indirekt rekursiv. 11

Om vi skriver en metod som hanterar vart och ett av dessa begrepp så får vi ett enkelt program som korrekt hanterar generella former av denna begränsade variant av aritmetiska uttryck. public static double uttryck() { double sum = term(); while ( nexttoread() == + ) { readnextchar(); sum += term(); return sum; static double term() { double prod = faktor(); while ( nexttoread() == * ) { readnextchar(); prod *= faktor(); return prod; static double faktor() { if ( nexttoread()!= ( ) // skall vara tal return readnextnumber(); else { readnextchar(); // läs förbi ( double result = uttryck(); readnextchar(); // läs förbi ) return result; Programmet bygger på tre primitiver: char nexttoread() som returnerar nästa tecken utan att ta bort det från input-strömmen, char readnextchar() som läser nästa tecken samt double readnextnumber() som läser nästa tal. (I Java kan dessa primitiver t ex uttryckas med hjälp av klassen StreamTokenizer.) 12