Astrazione sul controllo:
gli iteratori
1
Perché vogliamo iterarare “in modo
astratto”
 problema: iterare su tipi di dato arbitrari
 esempio: procedura stand-alone per calcolare la
somma di tutti gli elementi di un IntSet
public static int setSum (IntSet s) throws
NullPointerException
// EFFECTS: se s è null solleva
// NullPointerException altrimenti
// ritorna la somma degli elementi di s
2
Problema
 Non vediamo la rappresentazione (p.e. quella realizzata dal
vettore els)
 Dobbiamo usare i metodi pubblici forniti dalla classe
IntSet (la specifica)
 Unico modo per accedere agli elementi: choose
3
L’insieme di interi 1
public class IntSet {
// costruttore
public IntSet ()
// EFFECTS: inizializza this a vuoto
// metodi
public void insert (int x)
// MODIFIES: this
// EFFECTS: aggiunge x a this
public void remove (int x)
// MODIFIES: this
// EFFECTS: toglie x da this
public boolean isIn (int x)
//EFFECTS: se x appartiene a this ritorna
// true, altrimenti false
4
...}
L’insieme di interi 2
public class IntSet {
...
// metodi
...
public int size ()
// EFFECTS: ritorna la cardinalità di this
public int choose () throws EmptyException
// EFFECTS: se this è vuoto, solleva
// EmptyException, altrimenti ritorna un
// elemento qualunque contenuto in this
}
5
Una soluzione
 Prendiamo un elemento a caso con il metodo
choose
 Lo rimuoviamo
 Ripetiamo fino a svuotare l’insieme
 La procedura non deve modificare
l’insieme (non e’ riportato in MODIFIES)
 Quindi ci vuole una struttura di supporto per
memorizzare gli elementi
6
Soluzione insoddisfacente 1
public static int setSum (IntSet s) throws
NullPointerException {
// EFFECTS: se s è null solleva
// NullPointerException altrimenti
// ritorna la somma degli elementi di s
int[ ] a = new int [s.size( )];
int sum = 0;
for (int i = 0; i < a.length; i++)
{a[i] = s.choose( );
sum = sum + a[i];
s.remove(a[i]); }
// risistema s
for (int i = 0; i < a.length; i++)
s.insert(a[i]);
return sum;}
 ad ogni iterazione vengono chiamate choose e remove
 gli elementi rimossi li mettiamo in un array, poi li reinseriamo
7
Soluzione Sbagliata
 Un modo di risolvere il problema (accedere
alla rappresentazione)
 Per esempio, in IntSet accedere agli
elementi memorizzati nel vettore els
(mettendo els pubblico)
 Da evitare: rompe l’astrazione rendendo
dipendenti gli altri moduli
dall’implementazione
8
Soluzione insoddisfacente 2
 Il tipo di dato astratto non e’ progettato bene (metodi
pubblici forniti sono limitati….)
 potremmo aggiungere setSum come metodo d’istanza
della classe IntSet
– in modo più efficiente
• accedendo la rappresentazione
– non è direttamente collegata al concetto di IntSet
– quante altre operazioni simili dovremmo mettere in IntSet?
• trovare il massimo elemento.....
 Meglio sarebbe modificare la specifica mettendo delle
operazioni che siano un po’piu’ generali
9
Cosa non fare
 dotiamo IntSet di una operazione che ritorna la
rappresentazione (metodo d’istanza)
public Vector members ()
// EFFECTS: restituisce il Vector contenente gli
// elementi di this
 Soluzione assolutamente sbagliata: rende accessibile la
rappresentazione (rompe l’astrazione)
 Il codice del metodo dipende dalla rappresentazione
 Non possiamo piu’ garantire le proprieta’ di IntSet (la
rappresentazione puo’ essere modificata dall’esterno)
10
Soluzione leggermente diversa
public int [ ] members ()
// EFFECTS: restituisce un array contenente gli
// elementi di this, ciascuno esattamente una volta,
// in un ordine arbitrario
public static int setSum (IntSet s) {
int[ ] a = s.members();
int sum = 0;
for (int i = 0; i < a.length; i++) sum = sum + a[i];
return sum;}
 inefficiente
– due strutture dati
11
Di cosa abbiamo bisogno?
 un meccanismo generale di iterazione, che
permette di iterare sugli elementi di IntSet
– facile da usare
– efficiente
– che preservi l’astrazione (ovvero che non riveli
a chi lo usa il modo in cui l’insieme e’
implementato)
12
Vorremmo un generatore
 un generatore g produce in modo incrementale (uno alla
volta) tutti gli elementi i della collezione corrispondente
all’oggetto
 Utilizzando un generatore e’ possibile realizzare
l’iterazione astratta:
per ogni i prodotto da g esegui a su i
 l’azione a da compiere sugli elementi è separata dalla
generazione degli elementi stessi
13
Generatori in Java
 i generatori sono oggetti di particolari classi
 sono sottotipi di una Interfaccia Iterator
– il tipo Iterator è definito dalla seguente
interfaccia Java (java.utilpackage)
14
Cos’e’ una Interfaccia
 una Interfaccia e’ un particolare tipo di
classe
 contiene solo la specifica
 non ha implementazione
 contiene solo metodi d’istanza (astratti)
 non ha costruttori
15
Cos’e’ una Interfaccia
 Non si possono creare oggetti
 L’implementazione e’ definita dai sottotipi,
che implementano i metodi ereditati
 serve per definire implementazioni multiple
di un tipo o per definire operazioni comuni
a vari tipi
16
Iterator (versione semplificata)
public interface Iterator {
//OVERVIEW: i sottotipi definiscono generatori
//metodi astratti
public boolean hasNext ( );
// EFFECTS: restituisce true se ci sono altri
// elementi da generare altrimenti false
public Object next throws NoSuchElementException;
// MODIFIES: this
// EFFECTS: se ci sono altri elementi da generare
//dà il successivo e modifica lo stato di this,
//altrimenti
//solleva NoSuchElementException (unchecked)}
}
17
Sottotipi di una interfaccia
 Si definiscono usando la parola chiave
implements (al posto di extends)
 Ereditano i metodi dall’interfaccia e li
implementano
 In questo modo si puo’ definire una famiglia
di sottotipi che hanno un’insieme di
operazioni comuni
18
Esempio
–un generatore su una data collezione si definisce
implementando l’interfaccia Iterator
–un generatore si crea, creando un oggetto
di quel tipo
–tutti i generatori hanno i metodi next ed
hasnext che si comportano nello stesso
modo (come dichiarato nella superclasse)
19
Cosa vedremo?
 Come si usa un generatore per realizzare
l’iterazione astratta su un tipo di dato
 Come si implementa l’interfaccia Iterator
20
Iterazione astratta
 Per utilizzare generatori per iterare in modo
astratto su una collezione, bisogna aggiungere un
metodo che ritorna un generatore nella specifica
 Metodi che ritornano un generatore, spesso
chiamati iteratori
– da non confondere con il tipo Iterator che
restituiscono
 possono essere anche procedure stand-alone
21
Metodo d’istanza per IntSet
public class IntSet {
// come prima più
public Iterator elements (){
// EFFECTS: ritorna un generatore che produrrà tutti
// gli elementi di this (come Integers) ciascuno una
// sola volta, in ordine arbitrario
// REQUIRES: this non deve essere modificato
// finché il generatore è in uso
}
 la clausola REQUIRES impone condizioni sul
codice che utilizza il generatore
– tipica degli iteratori su tipi di dato modificabili
22
Un iteratore stand-alone 1
public class Num {
// come prima più
public static Iterator primesLT100 () {
// EFFECTS: restituisce un generatore,
// che genera incrementalmente tutti i
// numeri primi (Integer) minori di 100}
}
23
Specifica di un iteratore stand alone
public class Num {
// come prima più
public static Iterator allPrimes ()
// EFFECTS: ritorna un generatore che produrrà tutti
// i numeri primi (come Integers) ciascuno una
// sola volta, in ordine arbitrario
}
 il limite al numero di iterazioni deve essere
imposto dall’esterno
– il generatore può produrre infiniti elementi
24
Come si usano?
 Il metodo iteratore ed il relativo generatore si usano
guardando la specifica, che descrive il modo in cui produce
gli lementi
public static Iterator primesLT100 ()
// EFFECTS: restituisce un generatore,
// che genera incrementalmente tutti i
// numeri primi (Integer) minori di 100
25
Iterazione astratta
 Sappiamo che restituisce Integer dalla specifica
// ciclo controllato da hasnext
Iterator g = Num.primesLT100 ();
while (g.hasNext())
{int i = ((Integer) g.next( )).intValue( );
// esegui a su i }
26
Alternativa
// ciclo controllato da
exception
Iterator g = Num.primesLT100();
try {while (true) {int i =
((Integer) g.next()).intValue();
// esegue a su i
}
catch (NoSuchElementException e) { };
27
Iterazione astratta per IntSet
public Iterator elements ()
// EFFECTS: ritorna un generatore che produrrà tutti
// gli elementi di this (come Integers) ciascuno una
// sola volta, in ordine arbitrario
// REQUIRES: this non deve essere modificato
// finché il generatore è in uso
}
Lo usiamo per realizzare la procedura stand-alone
public static int setSum (IntSet s) throws
NullPointerException
// EFFECTS: se s è null solleva
// NullPointerException altrimenti
// ritorna la somma degli elementi di s
28
Procedura stand-alone
public static int setSum (IntSet s) throws
NullPointerException {
Iterator g= s.elements();
int sum = 0; try{while(true)
{sum=sum +(Integer) g.next().intvalue();} }
catch (NoSuchElementException e)
{}}
Scritta guardando solo la specifica di IntSet
Realizza l’iterazione astratta (indipendente dalla
rappresentazione) usando il generatore
29
Iteratori
 Abbiamo visto la specifica e l’uso dei
metodi che ritornano un generatore
(sottotipo di Iterator)
 Questi metodi li abbiamo chiamati iteratori
 Come e dove si definiscono i relativi
generatori?
30
Implementazione degli iteratori e dei
generatori
 i generatori sono oggetti che hanno come tipo un
sottotipo di Iterator
– istanze di una classe g che “implementa” l’interfaccia
Iterator
 un iteratore a è un metodo (stand alone o
associato ad un tipo astratto) che ritorna il
generatore istanza di g
 Vogliamo che g acceda alla rappresentazione (deve
iterare) senza renderla visibile
31
Esempio
 Il generatore di IntSet deve produrre tutti gli
elementi dell’insieme
 Deve necessariamente poter accedere alla
rappresentazione, ovvero al vettore els
32
Quindi
 Il generatore g deve avere una visibilità limitata al
package che contiene l’iteratore a
– oppure può essere contenuta nella classe che contiene a
• come inner class privata
 in questo modo i generatori sono visti dall’esterno
come oggetti di tipo Iterator
– perché il sottotipo g non è visibile
33
Classi nidificate
 una classe g dichiarata all’interno di una classe a
può essere
– static (di proprietà della classe a)
– di istanza (di proprietà degli oggetti istanze di a)
 se g è static come sempre non può accedere
direttamente le variabili di istanza ed i metodi di
istanza di a
– le classi che definiscono i generatori si possono quasi
sempre definire come inner classes statiche
34
Esempio
 La classe che definisce il generatore di
IntSet la definiamo come inner class privata
 Vede la rappresentazione
 Non e’ visibile ai moduli esterni al package
35
Intset: iteratore
public class IntSet {
private Vector els;
public Iterator elements () {
// EFFECTS: ritorna un generatore che produrrà tutti
// gli elementi di this (come Integers) ciascuno una
// sola volta, in ordine arbitrario
// REQUIRES: this non deve essere modificato
// finché il generatore è in uso
return new IntSetGen(this);}
Ritorna un oggetto di tipo IntSetGen, sottotipo di Iterator,
a cui passa l’oggetto corrente
Il tipo IntSetGen e’ definito come inner class privata di IntSet
36
Intset:generatore 1
private static class IntSetGen implements Iterator {
private IntSet s; // l’insieme su cui si itera
private int next; // prossimo indice del vettore da considerare
Manca l’OVERVIEW: il generatore e’ descritto nel corrispondente
Iteratore (elements)
Le variabili d’istanza mantengono le informazioni per iterare,
l’insieme e l’indice del vettore a cui siamo arrivati
Nota che la classe e’ interna ad IntSet, ha visione
delle variabili private di IntSet (in particolare del Vector)
37
Intset:generatore 1
private IntSet s; // l’insieme su cui si itera
private int next; // prossimo indice del vettore da considerare
public IntSetGen (IntSet it) {
// REQUIRES: it != null
s = it; next=0; }
Costruttore procedura parziale, usata solo all’interno
della classe
Prende come parametro l’IntSet su cui operare
(vedi elements())
38
Intset:generatore 2
public boolean hasNext () {if (next>= s.els.size())
{return false;} else {return true;} }
public Object next () throws NoSuchElementException {
if (next >= s.els.size()) {
throw new NoSuchElementException(”IntSet.elements"); }
return s.els.get(next);
next=next+1;}
La specifica dei metodi e’ quella descritta nell’interfaccia
(e’ sempre la stessa)
39
Implementazione degli iteratori 3
public class Num {
public static Iterator allPrimes (){return new PrimesGen();}
// EFFECTS: ritorna un generatore che produrrà tutti
// i numeri primi (come Integers) ciascuno una
// sola volta, in ordine arbitrario
private static class PrimeGen implements Iterator {
// inner class (classe annidata)
private Vector ps; // primi già dati
private int p; // prossimo candidato alla generazione
PrimesGen () {p = 2; ps = new Vector(); }
public boolean hasNext () {return true }
public Object next () {
if (p == 2) { p = 3; return new Integer(2);}
for (int n = p; true; n = n + 2)
for (int i = 0; i < ps.size(); i++){
int e1 = ((Integer) ps.get(i)).intValue();
if (n%e1 == 0) break; // non è primo
if (e1*e1 > n)
{ps.add(new Integer(n); p = n + 2;
return new Integer(n);}}} }}
40
Classi nidificate e generatori
 le classi i cui oggetti sono generatori definiscono
comunque dei tipi astratti
– sottotipi di Iterator
 in quanto tali devono essere dotati di
– una funzione di astrazione
– un invariante di rappresentazione
Necessarie per ragionare sulla loro correttezza
41
Funzione di astrazione per i
generatori
 dobbiamo sapere cosa sono gli stati astratti
 per tutti i generatori, lo stato astratto è
– la sequenza di elementi che devono ancora essere
generati
– la funzione di astrazione mappa la rappresentazione su
tale sequenza
42
Funzione di Astrazione:
IntSetGen
private static class IntSetGen implements Iterator {
private IntSet s; // l’insieme su cui si itera
private int next; // prossimo indice del vettore da considerare
a(c) = [] se c.next>=c.s.els.size()
oppure
a(c) =[x_i, x_i+1, ... ,x_n] tale che
c.next= i ed n=c.s.els.size()-1
e per ogni i <= j <=n
x_j = c.els.get(j)
•notare che si usano le rep sia di IntSet che di IntSetGen
43
Invariante di rappresentazione
I(c) = c.s != null e
(0 <= c.next <=
c.s.els.size())
 Il next varia tra 0 e la lunghezza del vettore
44
Correttezza dell’iteratore
 Facciamo vedere che l’invariante di
IntSetGen vale
 Costruttore
 Metodi per induzione sui dati
45
Invariante
private static class IntSetGen implements Iterator {
private IntSet s; // l’insieme su cui si itera
private int next; // prossimo indice del vettore da considerare
IntSetGen (IntSet it) {
// REQUIRES: it != null
s = it; next=0; }
public boolean hasNext () {if (next > = s.els.size())
{return false;} else {return true;}}
I(c) = c.s != null e
(0 <= c.next <= c.s.els.size())
46
Invariante
private static class IntSetGen implements Iterator {
private IntSet s; // l’insieme su cui si itera
private int next; // prossimo indice del vettore da considerare
public Object next () throws NoSuchElementException {
if (next >= s.els.size()) {
throw new NoSuchElementException(”IntSet.elements"); }
return s.els.get(next);
next=next+1;}
I(c) = c.s != null e
(0 <= c.next <= c.s.els.size())
Vale: se c.next era = c.s.els.size() allora ha
sollevato l’eccezione
47
Correttezza del generatore
 Valgono le invarianti di Intset e di
IntSetGen
 E’ facile convincersi che i metodi next e
hasnext() soddisfano la specifica
48
Correttezza dell’iteratore
 il generatore soddisfa anche la specifica (quella richiesta in elements)
// EFFECTS: ritorna un generatore che produrrà tutti
// gli elementi di this (come Integers) ciascuno una
// sola volta, in ordine arbitrario
 Il generatore ritorna gli elementi del vettore che rappresenta l’insieme
 E’ corretto perche’: (1) non ci sono occorrenze multiple nel vettore; (2)
gli elementi del vettore sono Integer
 Queste proprieta’ sono garantite dall’invariante di IntSet
49
Conclusioni sugli iteratori
 in molti tipi di dato astratti (collezioni) gli iteratori sono un
componente essenziale
–
–
–
–
–
supportano l’astrazione via specifica
portano a programmi efficienti in tempo e spazio
sono facili da usare
non distruggono la collezione
ce ne possono essere più d’uno
 se il tipo di dato astratto è modificabile ci dovrebbe sempre
essere il vincolo sulla non modificabilità del dato durante
l’uso dell’iteratore
– altrimenti è molto difficile specificarne il comportamento previsto
– in alcuni casi può essere utile combinare generazioni e modifiche
50
Scarica

Iterazione astratta