Metodi ausiliari ricorsivi

annuncio pubblicitario
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
Scarica