Esempio di applicazione TDA Map [GT3 8.1-8.2] 8 1 8 2] Motivazione: mappe e grafi Ci sono algoritmi su grafi che hanno bisogno di associare delle informazioni ai vertici e/o agli archi Esempio Sia u un nodo in un grafo. Posso considerare una mappa (per ogni nodo u) con chiavi (Object) COLORE, PREDECESSORE, DISTANZA Ad esempio, i campi predecessore/colore/distanza (BFS) oppure il peso degli archi e campi predecessore/chiave (MST). (PREDECESSORE, v), (COLORE, grigio), (DISTANZA, 0) [key1, value1], [key2, value2], [key3, value3] Per il nodo u, so che: il predecessore è v, il colore è grigio, la distanza è 0. Invece di aggiungere campi nell’implementazione di un vertice o di un arco, aggiungiamo solo un’istanza della classe che implementa MAP L’uso della mappa, garantisce che, dato un nodo, non posso avere due o più predecessori, due o più colori, due o più distanze… perché k deve essere unica Operazioni sul TDA Map Definizione Infatti non consentiamo a due persone (valori) di avere la stessa matricola (key) Mappa che conserva le informazioni sugli studenti (ad esempio, nome, cognome, indirizzo, esami, numero di matricola…): la chiave potrebbe essere il numero di matricola Il TDA Map è un contenitore di oggetti (Entry) che sono coppie chiave-valore (k,v) Sia la chiave k sia il valore v possono essere oggetti qualsiasi. qualsiasi Differenza con PQ: non sono consentite più entry con la stessa chiave k. Le operazioni principali da fare su un TDA Map sono di ricerca, inserimento e cancellazione di elementi. size(), isEmpty() keys(): restituisce un iteratore delle chiavi di M values(): restituisce un iteratore dei valori di M Si lavora sulle chiavi… Potete pensarle con “chiavi di ricerca” abbinate agli elementi (valori), che uso per reperirli. 1 Operazioni sul TDA Map get(k): Se la mappa M ha l’entry con chiave k, restituisce il valore associato, altrimenti null. put(k v): Inserisce ll’entry put(k, entry (k, (k v) nella mappa M M. Se la chiave k non è già presente in M, restituisce null; altrimenti restituisce il vecchio valore associato a k. remove(k): Se nella mappa M c’è un entry con chiave k, si rimuove l’entry con chiave k e si restituisce il valore associato. Altrimenti si restituisce null. Interfaccia per il TDA Map public interface Map { public int size(); //restituisce il numero di entry della Map public boolean isEmpty(); //verifica se la Map è vuota //inserisce una coppia key-value e rimpiazza il value precedente, se k //c’è già, altrimenti null. public Object p j put(Object p ( j key, y, Object j value)) throws InvalidKeyException; y p ; //Restituisce il value associato a k public Object get(Object key) throws InvalidKeyException; //Cancella la coppia (chiave-valore) specificata da key public Object remove(Object key) throws InvalidKeyException; public Iterator keys(); //Restituisce un iteratore delle chiavi della mappa public Iterator values(); //Restituisce un iteratore dei valori della mappa } Osservazione su “null” Abbiamo visto che le operazioni get(k), put(k,v) e remove(k) se eseguite su una mappa M che non contiene un’entry con chiave k, restituisce null COME IMPLEMENTIAMO??? “Cose abbastanza naturali…” 1. Si tratta di un value speciale, p , detto sentinella. public interface Entry { public Object key(); public Object element(); } Questo significa che potrebbe esserci ambiguità con l’entry (k,null), ossia con value null [poco appropriato] Se si vuole mantenere questa possibilità, si può pensare ad un’eccezione. Esempio Operation 1. 2. 3. 4. 5 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. Una entry della mappa viene rappresentata dalla classe MyEntry, come fatto per le code a priorità. isEmpty() put(5,A) put(7,B) put(2,C) put(8 D) put(8,D) put(2,E) get(7) get(4) get(2) size() remove(5) remove(2) get(2) isEmpty() L’implementazione sarà una classe interna, come per le code a priorità. COME IMPLEMENTIAMO??? Output true null null null null C B null E 4 A E null false Map Ø (5,A) (5,A),(7,B) (5,A),(7,B),(2,C) (5 A) (7 B) (2 C) (8 D) (5,A),(7,B),(2,C),(8,D) (5,A),(7,B),(2,E),(8,D) (5,A),(7,B),(2,E),(8,D) (5,A),(7,B),(2,E),(8,D) (5,A),(7,B),(2,E),(8,D) (5,A),(7,B),(2,E),(8,D) (7,B),(2,E),(8,D) (7,B),(8,D) (7,B),(8,D) (7,B),(8,D) “Cose abbastanza naturali…” 2. I metodi presenti nell’interfaccia Map devono lanciare l’eccezione InvalidKeyException se la chiave specificata non è per il contenitore in esame. Ma qual q è qui q il criterio di valida p validità???? 3. Il metodo checkKey dovrà solo controllare che k non è null, non c’è bisogno di una relazione d’ordine totale tra le chiavi… 2 Abbiamo bisogno di confrontare le chiavi…. ¾ ¾ ¾ ¾ Un’ implementazione basata su liste Dobbiamo decidere se due chiavi sono uguali. Se le chiavi hanno una relazione d’ordine totale determinata da qualche comparatore C, possiamo usare C.compare(k1,k2) e vedere quando ritorna 0… ma non conviene. Per una mappa generica, possiamo usare un oggetto EQUALITY TESTER che supporta l’operazione isEqualTo(k1,k2) sulle chiavi. Potremmo anche usare il metodo equals di Java, ma non è generale. Possiamo implementare efficientemente il TDA Map usando una lista non ordinata (implementata come lista doppiamente-linkata) Memorizziamo gli elementi della mappa in una lista senza un ordinamento nodes/positions header 9 a 6 c 5 f trailer 8 d entries Esistono implementazioni migliori (basate su tabelle hash) Abbiamo bisogno di confrontare le chiavi…. ¾ ¾ ¾ Quando creiamo una mappa M, abbiniamo un EQUALITY TESTER basato su un comparatore. Nelle slides successive, usiamo “=“ per testare l’uguaglianza. Nell’implementazione Java possiamo fare come per la coda a Priorità, ossia Interfaccia da scrivere Algoritmo get(k) [compl. O(size)] Algorithm get(k): B = M.positions() //B è un iteratore delle posizioni in M while B.hasNext() do p = B.next() //la prossima posizione in B if (p.element().key() == k) then return (p.element()).value() return null //non ci sono entry con chiave uguale a k protected static class DefaultEqualityTester implements EqualityTester{ public DefaultEqualityTester() { /* default constructor */} } public boolean isEqualTo(Object a, Object b) throws ClassCastException { return (a.equals(b)); } Confronto con il package java.util.Map Metodi del TDA Map size() isEmpty() get(k) put(k,v) remove(k) keys() values() Metodi java.util.Map size() isEmpty() get(k) put(k,v) remove(k) keySet().iterator() values().iterator() Inoltre, non si usa un Equality Tester esterno, ma solo il metodo equals (se non è appropriato per il nostri oggetti – vedi esempio dei punti nel piano – occorre sovrascriverlo…) Algoritmo remove(k) [compl. O(size)] Algorithm remove(k): B =M.positions() while B.hasNext() do p = B.next() if (p.element().key() == k) then t = p.element().value() M.remove(p) size = size – 1 return t return null //non c’erano entry con chiave k 3 Algoritmo put(k,v) [compl. O(size)] Algorithm put(k,v): B = M.positions() while M.hasNext() do p = B.next() if (p.element().key() == k) then t = p.element().value() M.replace(p,(k,v)) //come implemento??? Esercizio. return t M.insertLast((k,v)) size = size + 1 return null //non c’erano già value con chiave k Esercizi proposti Implementare il TDA Map con liste non ordinate Scrivere un programma di test per la classe Scrivere il metodo toStringKey() che restituisca una stringa contenente le chiavi della mappa; scrivere un metodo toStringValue() che restituisca una stringa contenente i values della mappa. Usare i metodi del TDA Map. 4