Capitolo 12 Ricorsione Cay S. Horstmann Concetti di informatica e fondamenti di Java quarta edizione Obiettivi del capitolo Comprendere il meccanismo della ricorsione Capire la relazione esistente tra ricorsione e iterazione Analizzare problemi che sono molto più semplici da risolvere con la ricorsione che con l’iterazione Imparare a “pensare ricorsivamente” Essere in grado di usare metodi ausiliari ricorsivi Capire come l’uso della ricorsione si ripercuote sull’efficienza di un algoritmo Numeri triangolari Calcolare l’area di un triangolo di ampiezza n Si ipotizza che ciascun quadrato [ ] abbia area unitaria A volte viene chiamato numero triangolare n-esimo Il terzo numero triangolare è 6 [] [][] [][][] Traccia della classe triangolare public class Triangle { public Triangle(int aWidth) { width = aWidth; } public int getArea() { . . . } private int width; } Triangolo di ampiezza 1 Il triangolo consiste di un unico quadrato La sua area vale 1 Invoca getArea su tale triangolo public int getArea() { if (width = = 1) return 1; . . . } Trattare il caso generale Supponiamo di conoscere l’area del triangolo più piccolo [] [][] [][][] [][][][] Così si può calcolare più facilmente l’area del triangolo più grande smallerArea + width Continua… Trattare il caso generale Per ottenere l’area del triangolo più piccolo: costruiamo un triangolo più piccolo e chiediamogliela Triangle smallerTriangle = new Triangle(width - 1); int smallerArea = smallerTriangle.getArea(); Completare il metodo getArea public int getArea() { if (width == 1) return 1; Triangle smallerTriangle = new Triangle(width - 1); int smallerArea = smallerTriangle.getArea(); return smallerArea + width; } Calcolare l’area del triangolo di ampiezza 4 Il metodo getArea crea un triangolo più piccolo di ampiezza 3. Invoca getArea su tale triangolo. Quel metodo crea un triangolo più piccolo di ampiezza 2. Invoca getArea su tale triangolo. Quel metodo crea un triangolo più piccolo di ampiezza 1. Invoca getArea su tale triangolo. Tale metodo restituisce 1. Il metodo restituisce smallerArea + width = 1 + 2 = 3. Il metodo restituisce smallerArea + width = 3 + 3 = 6. Il metodo restituisce smallerArea + width = 6 + 4 = 10. La ricorsione Un’elaborazione ricorsiva risolve un problema usando la soluzione del problema stesso con dati di ingresso più semplici. Perché una ricorsione termini, devono esistere casi speciali per i dati in ingresso più semplici. Due requisiti basilari: Ogni invocazione ricorsiva deve semplificare in qualche modo l’elaborazione. Devono esistere casi speciali che gestiscano in modo diretto le elaborazioni più semplici. Altri modi per calcolare numeri triangolari L’area di un triangolo è uguale alla somma 1 + 2 + 3 + . . . + width Calcolarla con un semplice ciclo double area = 0; for (int i = 1; i <= width; i++) area = area + i; Calcolare con questa formula 1 + 2 + . . . + n = n × (n + 1)/2 => width * (width + 1) / 2 File Triangle.java 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: 17: /** Una forma triangolare composta di quadrati impilati, come questa: [] [][] [][][] . . . */ public class Triangle { /** Costruisce una forma triangolare. @param aWidth l’ampiezza (e l’altezza) del triangolo */ public Triangle(int aWidth) { width = aWidth; } Continua… File Triangle.java 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: } /** Calcola l’area del triangolo. @return l’area */ public int getArea() { if (width <= 0) return 0; if (width == 1) return 1; Triangle smallerTriangle = new Triangle(width-1); int smallerArea = smallerTriangle.getArea(); return smallerArea + width; } private int width; File TriangleTester.java 01: public class TriangleTester 02: { 03: public static void main(String[] args) 04: { 05: Triangle t = new Triangle(10); 06: int area = t.getArea(); 07: System.out.println("Area = " + area); 08: System.out.println(“Expected: 55”); 09: } 10: } Continua… File triangleTester.java Visualizza Enter width: 10 Area: 55 Expected: 55 Permutazioni Progettiamo una classe che elenchi tutte le permutazioni di una stringa Una permutazione è una qualsiasi disposizione delle lettere La stringa “eat” ha sei permutazioni (compresa la stringa stessa). “eat” “eta” “aet” “ate” “tea” “tae” Stringhe permutate public class PermutationGenerator { public PermutationGenerator(String aWord) { . . . } public ArrayList<String> getPermutations() { . . . } } File PermutationGeneratorDemo.java 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: import java.util.ArrayList; /** Questo programma utilizza il generatore di permutazioni. */ public class PermutationGeneratorDemo { public static void main(String[] args) { PermutationGenerator generator = new PermutationGenerator("eat"); ArrayList<String> permutations = generator.getPermutations(); for (String s : permutations) { System.out.println(s); } } } File PermutationGeneratorDemo.java Visualizza eat eta aet ate tea tae Generare le permutazioni ricorsive Generare tutte le permutazioni che iniziano con la lettera ‘e’, poi quelle che iniziano con ‘a’, infine quelle che iniziano con ‘t’ Per generare le permutazioni che iniziano con ‘e’ abbiamo bisogno di conoscere le permutazioni della sottostringa “at” Questo non è altro che il problema precedente con un dato di ingresso più semplice Usiamo quindi la ricorsione Generare le permutazioni getPermutations:scriviamo un ciclo che prenda in esame tutte le posizioni all’interno della parola che deve essere permutata Per ciascuna posizione i, calcoliamo la parola più breve che si ottiene eliminando il carattere i-esimo String shorterWord = word.substring(0, i) + word.substring(i + 1); Generare le permutazioni Costruiamo poi un generatore di permutazioni che fornisca le permutazioni di tale parola più breve: PermutationGenerator shorterPermutationGenerator = new PermutationGenerator(shorterWord); ArrayList<String> shorterWordPermutations = shorterPermutationGenerator.getPermutations(); Generare le permutazioni Infine, aggiungiamo il carattere precedentemente escluso a tutte le permutazioni della parola più breve: for (String s : shorterWordPermutations) { result.add(word.charAt(i) + s); } Caso speciale: la più semplice stringa possibile è la stringa vuota, che ha un’unica permutazione: se stessa. File PermutationGenerator.java 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: import java.util.ArrayList; /** Questa classe genera le permutazioni di una parola. */ public class PermutationGenerator { /** Costruisce un generatore di permutazioni. @param aWord la parola da permutare */ public PermutationGenerator(String aWord) { word = aWord; } Continua… File PermutationGenerator.java 17: 18: /** 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: */ public ArrayList<String> getPermutations() { ArrayList<String> result = new ArrayList<String>(); Fornisce tutte le permutazioni della parola. @return un vettore contenente tutte le permutazioni // La stringa vuota ha un’unica permutazione:se stessa if (word.length() == 0) { result.add(word); return result; } // effettua un ciclo su tutti i caratteri della stringa for (int i = 0; i < word.length(); i++) { Continua… File PermutationGenerator.java 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: // Componi una parola più breve eliminando il carattere i-esimo String shorterWord = word.substring(0, i) + word.substring(i + 1); // Genera tutte le permutazioni della parola più breve PermutationGenerator shorterPermutationGenerator = new PermutationGenerator(shorterWord); ArrayList<String> shorterWordPermutations = shorterPermutationGenerator.getPermutations(); } // Aggiungi il carattere escluso all’inizio di ciascuna // permutazione della parola più breve for (String s : shorterWordPermutations) { result.add(word.charAt(i) + s); } Continua… File PermutationGenerator.java 51: 52: 53: 54: 55: 56: } // Restituisce tutte le permutazioni return result; } private String word; Tenere traccia delle invocazioni di metodi ricorsivi Figura 1: Debugging di un metodo ricorsivo Visualizzazione della pila delle invocazioni (call stack) Figura 2 Pensare ricorsivamente Problema: verificare se una frase è una palindrome Palindrome: una stringa uguale a se stessa quando ne invertite l’ordine dei caratteri • A man, a plan, a canal – Panama! • Go hang a salami, I’m a lasagna hog • Madam, I’m Adam Realizzare il metodo isPalindrome public class Sentence { /** Costruisce una frase. @param aText una stringa contenente tutti i caratteri di una frase */ public Sentence(String aText) { text = aText; } Continua… Realizzare il metodo isPalindrome /** Verifica se questa frase è una palindrome. @return true se questa frase è una palindrome, false in caso contrario */ public boolean isPalindrome() { . . . } private String text; } Pensare ricorsivamente Fase 1. Considerate diversi modi per semplificare i dati Ecco alcune possibilità: • Eliminare il primo carattere. • Eliminare l’ultimo carattere. • Eliminare il primo e l’ultimo carattere. • Eliminare il carattere centrale. • Dividere la stringa a metà. Pensare ricorsivamente Fase 2. Combinate le soluzioni dei casi più semplici per fornire una soluzione al problema originario La semplificazione più promettente è l’eliminazione del primo e dell’ultimo carattere. “adam, I’m Ada”: anche questo è una palindrome Una parola è una palindrome se • la prima e l’ultima lettera sono uguali (trascurando le differenze tra maiuscole e minuscole) e • la parola che si ottiene eliminando la prima e l’ultima lettera è una palindrome Continua…. Pensare ricorsivamente Cosa succede se la prima o l’ultima lettera della parola non sono una lettera? • Se il primo e l’ultimo carattere sono lettere, verificate se sono uguali. In tal caso, eliminateli entrambi e verificate la stringa rimanente. • Altrimenti, se l’ultimo carattere non è una lettera, eliminatelo e verificate la stringa rimanente. • Altrimenti, il primo carattere non è una lettera: eliminatelo e verificate la stringa rimanente. Pensare ricorsivamente Fase 3. Trovate le soluzioni per i casi più semplici • stringa di due caratteri Non è necessario identificare una soluzione speciale : la Fase 2 si applica anche ad esse • stringa di un solo carattere E’ una palindrome • stringa vuota E’ una palindrome Pensare ricorsivamente Fase 4. Scrivete il codice per la soluzione combinando i casi semplici e il passo di semplificazione public boolean isPalindrome() { int length = text.length(); // considera separatamente i casi delle stringhe più brevi if (length <= 1) return true; // Prendi il primo e l’ultimo carattere,convertiti in minuscolo char first = Character.toLowerCase(text.charAt(0)); char last = Character.toLowerCase(text.charAt(length - 1)); Pensare ricorsivamente if (Character.isLetter(first) && Character.isLetter(last)) { // entrambi sono lettere if (first == last) { // elimina il primo e l’ultimo carattere Sentence shorter = new Sentence(text.substring(1, length 1)); return shorter.isPalindrome(); } else return false; } Pensare ricorsivamente else if (!Character.isLetter(last)) { // elimina l’ultimo carattere. Sentence shorter = new Sentence(text.substring(0, length - 1)); return shorter.isPalindrome(); } else { // elimina il primo carattere. Sentence shorter = new Sentence(text.substring(1)); return shorter.isPalindrome(); } } Metodi ausiliari ricorsivi A volte è più semplice trovare una soluzione ricorsiva dopo aver apportato una piccola modifica al problema originario Considerate la verifica della palindrome vista prima. Costruire nuovi oggetti di tipo Sentence a ogni passo è poco efficiente. Continua… Metodi ausiliari ricorsivi Invece di verificare se l’intera frase è una palindrome, verifichiamo se una sottostringa è una palindrome /** Verifica se una sottostringa della frase è una palindrome. @param start l’indice del primo carattere della sottostringa @param end l’indice dell’ultimo carattere della sottostringa @return true se la sottostringa è una palindrome */ public boolean isPalindrome(int start, int end) Metodi ausiliari ricorsivi Invocate semplicemente il metodo ausiliario con valori di posizioni che verifichino l’intera stringa: public boolean isPalindrome() { return isPalindrome(0, text.length() - 1); } Metodi ausiliari ricorsivi: isPalindrome public boolean isPalindrome(int start, int end) { // considera separatamente i casi delle stringhe più brevi if (start >= end) return true; // prendi primo e ultimo carattere, convertiti in minuscolo char first = Character.toLowerCase(text.charAt(start)); char last = Character.toLowerCase(text.charAt(end)); if (Character.isLetter(first) && Character.isLetter(last)) { // entrambi sono lettere if (first == last) { // verifica la sottostringa che non contiene // le due lettere uguali return isPalindrome(start + 1, end - 1); Metodi ausiliari ricorsivi: isPalindrome } else return false; } } else if (!Character.isLetter(last)) { // verifica la sottostringa che non contiene l’ultimo // carattere return isPalindrome(start, end - 1); } else { // verifica la sottostringa che non contiene il primo // carattere return isPalindrome(start + 1, end); } La sequenza di Fibonacci La sequenza di Fibonacci è una sequenza di numeri definita da queste equazioni: I primi 10 valori della sequenza sono: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 File RecursiveFib.java 01: import java.util.Scanner; 02: 03: /** 04: Calcola i numeri di Fibonacci con un metodo ricorsivo 05:. 06: */ 07: public class FibTester 08: { 09: public static void main(String[] args) 10: { 11: Scanner in = new Scanner(System.in); 12: System.out.print("Enter n: "); 13: int n = in.nextInt(); 14: 15: for (int i = 1; i <= n; i++) 16: { Continua… File RecursiveFib.java 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: } long f = fib(i); System.out.println("fib(" + i + ") = " + f); } } /** Calcola un numero di Fibonacci. @param n un numero intero @return l’n-esimo numero di Fibonacci */ public static long fib(int n) { if (n <= 2) return 1; else return fib(n - 1) + fib(n - 2); } File RecursiveFib.java Visualizza Enter n: 50 fib(1) = 1 fib(2) = 1 fib(3) = 2 fib(4) = 3 fib(5) = 5 fib(6) = 8 fib(7) = 13 . . . fib(50) = 12586269025 L’efficienza della ricorsione La ricorsione è un metodo semplice e corretto Osservate attentamente i dati che vengono visualizzati mentre eseguite il programma di prova Le prime invocazioni del metodo fib sono abbastanza veloci Per valori maggiori, il programma lascia trascorrere una quantità sorprendente di tempo tra due visualizzazioni Per identificare il problema inseriamo nel metodo alcuni messaggi di tracciatura File RecursiveFibTracer.java 01: import java.util.Scanner; 02: 03: /** 04: Questo programma stampa messaggi di tracciatura 05: che mostrano quanto spesso il metodo ricorsivo per calcolare i numeri di Fibonacci chiama se stesso. 06: */ 07: public class RecursiveFibTracer 08: { 09: public static void main(String[] args) 10: { 11: Scanner in = new Scanner(System.in); 12: System.out.print("Enter n: "); 13: int n = in.nextInt(); 14: 15: long f = fib(n); 16: 17: System.out.println("fib(" + n + ") = " + f); 18: } Continua… File RecursiveFibTracer.java 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: } /** Calcola un numero di Fibonacci. @param n un numero intero @return l’n-esimo numero di Fibonacci */ public static long fib(int n) { System.out.println("Entering fib: n = " + n); long f; if (n <= 2) f = 1; else f = fib(n - 1) + fib(n - 2); System.out.println("Exiting fib: n = " + n + " return value = " + f); return f; } File RecursiveFibTracer.java Visualizza Enter n: 6 Entering fib: n = 6 Entering fib: n = 5 Entering fib: n = 4 Entering fib: n = 3 Entering fib: n = 2 Exiting fib: n = 2 return Entering fib: n = 1 Exiting fib: n = 1 return Exiting fib: n = 3 return Entering fib: n = 2 Exiting fib: n = 2 return Exiting fib: n = 4 return value = 1 value = 1 value = 2 value = 1 value = 3 Continua… File RecursiveFibTracer.java Entering fib: n = 3 Entering fib: n = 2 Exiting fib: n = 2 return Entering fib: n = 1 Exiting fib: n = 1 return Exiting fib: n = 3 return Exiting fib: n = 5 return Entering fib: n = 4 Entering fib: n = 3 Entering fib: n = 2 Exiting fib: n = 2 return Entering fib: n = 1 Exiting fib: n = 1 return Exiting fib: n = 3 return Entering fib: n = 2 value = 1 value = 1 value = 2 value = 5 value = 1 value = 1 value = 2 Continua… File RecursiveFibTracer.java Exiting fib: n = 2 return value = 1 Exiting fib: n = 4 return value = 3 Exiting fib: n = 6 return value = 8 fib(6) = 8 Schema delle invocazioni del metodo ricorsivo fib Figura 3 L’efficienza della ricorsione Questo metodo è così lento perché gli stessi valori vengono calcolati più e più volte. Il calcolo di fib(6) richiede di calcolare due volte fib(4) e tre volte fib(3). Imitando il procedimento “carta e penna” otteniamo che nessun valore della sequenza venga calcolato due volte. File LoopFib.java 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: import java.util.Scanner; /** Calcola i numeri di Fibonacci con un metodo iterativo. */ public class FibLoop { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("Enter n: "); int n = in.nextInt(); for (int i = 1; i <= n; i++) { Continua… File LoopFib.java 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: } } long f = fib(i); System.out.println("fib(" + i + ") = " + f); /** Calcola un numero di Fibonacci. @param n un numero intero @return l’n-esimo numero di Fibonacci */ public static long fib(int n) { if (n <= 2) return 1; long fold = 1; long fold2 = 1; long fnew = 1; for (int i = 3; i <= n; i++) { Continua… File LoopFib.java 34: 35: 36: 37: 38: 39: 40: } fnew = fold + fold2; fold2 = fold; fold = fnew; } return fnew; } File LoopFib.java Visualizza Enter n: 50 fib(1) = 1 fib(2) = 1 fib(3) = 2 fib(4) = 3 fib(5) = 5 fib(6) = 8 fib(7) = 13 . . . fib(50) = 12586269025 L’efficienza della ricorsione A volte la soluzione ricorsiva è più lenta di quella iterativa Nella maggior parte dei casi la soluzione ricorsiva è soltanto poco più lenta. La soluzione iterativa tende a essere un po’ più veloce, perché ciascuna invocazione di un metodo ricorsivo richiede una certa quantità di tempo di elaborazione del processore. Per un compilatore efficiente è possibile evitare l’esecuzione di invocazioni ricorsive se queste seguono uno schema semplice, ma la maggior parte dei compilatori non fa questo. Solitamente le soluzioni ricorsive sono più facili da capire e da realizzare correttamente, rispetto a soluzioni iterative. L. Peter Deutsch: “Iterare è umano, usare la ricorsione è divino”. Soluzione iterativa per la verifica di una palindrome public boolean isPalindrome() { int start = 0; int end = text.length() - 1; while (start < end) { char first = Character.toLowerCase(text.charAt(start)); char last = Character.toLowerCase(text.charAt(end); if (Character.isLetter(first) && Character.isLetter(last)) { // Entrambi sono lettere. if (first == last) { start++; end--; } Continua… Soluzione iterativa per la verifica di una palindrome else return false; } if (!Character.isLetter(last)) end--; if (!Character.isLetter(first)) start++; } return true; } I limiti del calcolo automatico Figura 4: Una macchina di Turing Ricorsione mutua Calcolare i valori di espressioni aritmetiche, come: 3 + 4 * 5 (3 + 4) * 5 1 - (2 - (3 - (4 - 5))) Calcolare un’espressione di questo tipo è complicato: - le operazioni * e / hanno una precedenza più elevata delle operazioni + e – - si possono usare le parentesi per raggruppare sottoespressioni. Diagrammi sintattici per la valutazione di un’espressione Figura 5 Usare la ricorsione mutua Un’espressione viene scomposta in una sequenza di termini, separati dai segni + o –, Ciascun termine viene a sua volta scomposto in una sequenza di fattori, separati da segni * o /, Ciascun fattore è un numero o un’espressione racchiusa fra parentesi tonde. Questa scomposizione può essere rappresentata mediante un albero. Gli alberi sintattici rappresentano l’ordine di esecuzione delle operazioni. Alberi sintattici per due espressioni Figura 6 Metodi della ricorsione mutua Nella ricorsione mutua, un insieme di metodi cooperanti si invocano l’un l’altro ripetutamente Per calcolare il valore di un’espressione, realizziamo tre metodi: - getExpressionValue - getTermValue - getFactorValue Il metodo getExpressionValue public int getExpressionValue() { int value = getTermValue(); boolean done = false; while (!done) { String next = tokenizer.peekToken(); if ("+".equals(next) || "-".equals(next)) { tokenizer.nextToken(); // ignora "+" or "-" int value2 = getTermValue(); if ("+".equals(next)) value = value + value2; else value = value - value2; } else done = true; } return value; } Il metodo getFactorValue public int getFactorValue() { int value; String next = tokenizer.peekToken(); if ("(".equals(next)) { tokenizer.nextToken(); // ignora "(" value = getExpressionValue(); tokenizer.nextToken(); // ignora ")" } else value = Integer.parseInt(tokenizer.nextToken()); return value; } Usare la ricorsione mutua Per evidenziare la ricorsione mutua, seguiamo passo passo l’analisi sintattica e la valutazione dell’espressione (3+4)*5: • getExpressionValue invoca getTermValue • getTermValue invoca getFactorValue • getFactorValue legge ( dalla stringa d’ingresso • getFactorValue invoca getExpressionValue • getExpressionValue restituisce il valore 7, dopo aver letto 3+4; ecco l’invocazione ricorsiva • getFactorValue legge ) dalla stringa d’ingresso • getFactorValue restituisce 7 • getTermValue legge * e 5 dalla stringa d’ingresso e restituisce 35 • getExpressionValue restituisce 35 File Evaluator.java 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: /** Calcolare il valore di una espressione aritmetica. */ public class Evaluator { /** Costruisce un valutatore. @param anExpression una stringa che contiene una espressione da valutare */ public Evaluator(String anExpression) { tokenizer = new ExpressionTokenizer(anExpression); } Continua… File Evaluator.java 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: /** Valuta l’espressione. @return il valore dell’espressione */ public int getExpressionValue() { int value = getTermValue(); boolean done = false; while (!done) { String next = tokenizer.peekToken(); if ("+".equals(next) || "-".equals(next)) { tokenizer.nextToken(); // Elimina "+" or "-" int value2 = getTermValue(); if ("+".equals(next)) value = value + value2; else value = value - value2; } Continua… File Evaluator.java 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: else done = true; } } return value; /** Valuta il successivo termine nell’espressione. @return il valore del termine */ public int getTermValue() { int value = getFactorValue(); boolean done = false; while (!done) { String next = tokenizer.peekToken(); if ("*".equals(next) || "/".equals(next)) { Continua… File Evaluator.java 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: tokenizer.nextToken(); int value2 = getFactorValue(); if ("*".equals(next)) value = value * value2; else value = value / value2; } else done = true; } } return value; /** */ Valuta il successivo fattore nell’espressione. @return il valore del fattore Continua… File Evaluator.java 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: } public int getFactorValue() { int value; String next = tokenizer.peekToken(); if ("(".equals(next)) { tokenizer.nextToken(); // Discard "(" value = getExpressionValue(); tokenizer.nextToken(); // Discard ")" } else value = Integer.parseInt(tokenizer.nextToken()); return value; } private ExpressionTokenizer tokenizer; File ExpressionTokenizer.java 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: 17: /** Questa classe scompone una stringa che descrive un’espressione in: numeri, parentesi e operatori. */ public class ExpressionTokenizer { /** Costruisce uno scompositore. @param anInput la stringa da scomporre */ public ExpressionTokenizer(String anInput) { input = anInput; start = 0; end = 0; nextToken(); } Continua… File ExpressionTokenizer.java 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: /** Restituisce l’elemento successivo senza estrarlo. @return l’elemento successivo, oppure null se non ci sono più elementi */ public String peekToken() { if (start >= input.length()) return null; else return input.substring(start, end); } /** 31: 32: */ Restituisce l’elemento successivo e fa avanzare lo scompositore all’elemento successivo. @return l’elemento successivo, oppure null se non ci sono più elementi Continua… File ExpressionTokenizer.java 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: public String nextToken() { String r = peekToken(); start = end; if (start >= input.length()) return r; if (Character.isDigit(input.charAt(start))) { end = start + 1; while (end < input.length() && Character.isDigit(input.charAt(end))) end++; } else end = start + 1; return r; } Continua… File ExpressionTokenizer.java 50: 51: 52: 53: } private String input; private int start; private int end; File ExpressionCalculator.java 01: import java.util.Scanner; 02: 03: /** 04: Questo programma calcola il valore di un’espressione costituita da numeri,operatori aritmetici e parentesi tonde. 05: */ 06: public class EvaluatorTester 07: { 08: public static void main(String[] args) 09: { 10: Scanner in = new Scanner(System.in); 11: System.out.print("Enter an expression: "); 12: String input = in.nextLine(); 13: Evaluator e = new Evaluator(input); 14: int value = e.getExpressionValue(); 15: System.out.println(input + "=" + value); 16: } 17: } File ExpressionCalculator.java Visualizza Enter an expression: 3+4*5 3+4*5=23