Esercizio Scrivere una classe ListMap<K, V> che implementa la mappa con una lista Strutture Dati Esercizio Si scriva un metodo public static <E> PositionList<Position<E>> preorder(LinkedBinaryTree<E> T) che preso in input un albero binario T, usi uno stack per dare un' implementazione iterativa della visita preorder su T Strutture Dati Funzione hash Load factor La probabilità di avere una collisione fra chiavi dipende dalla capacità N del bucket array dal numero x delle chiavi memorizzate dalla funzione hash h(k) Indipendentemente dal metodo di risoluzione delle collisioni, è sempre auspicabile mantenere bassa la probabilità di collisione senza usare troppa memoria λ = x/N load factor Si può dimostrare che conviene mantenere λ < 0.5 nel linear probing λ < 0.9 nel separate chaning Strutture Dati ADT Mappa Implementazione con tabelle hash e linear probing Classe HashTableMap che implementa Map variabili istanza: • bucket array: A • capacità dell'array: N Strutture Dati ADT Mappa Implementazione con tabelle hash e linear probing Classe HashTableMap che implementa Map la funzione hash di una chiave key viene calcolata dal metodo hashValue che opera in due fasi: 1)Codice hash: l'oggetto key viene trasformato in un intero mediante il metodo hashCode() (pacchetto java.lang) key key.hashCode() 2)Funzione di compressione: l'intero viene “compresso” in modo da essere compreso in [0, N – 1] (affinché possa essere inserito nel bucket array) Strutture Dati ADT Mappa Implementazione con tabelle hash e linear probing Classe HashTableMap che implementa Map la funzione di compressione è studiata in modo da minimizzare la probabilità di collisioni Esempio: Il metodo MAD (multiply add and divide): f (i) = [|ai + b| mod p] mod N N capacità del bucket array p numero primo > N a,b interi scelti a caso nell'intervallo [0 , p - 1] Strutture Dati ADT Mappa Implementazione con tabelle hash e linear probing Classe HashTableMap che implementa Map Metodo get(k) – si parte dalla cella h(k) – si provano (probe) tutte le celle successive fino a che si verifichi una delle seguenti condizioni: Strutture Dati ● viene trovato un oggetto con chiave k, o ● viene trovata una cella vuota, o ● è stato già fatto il probe di N celle (senza successo) ADT Mappa Implementazione con tabelle hash e linear probing Classe HashTableMap che implementa Map Come implementare remove(k)? (primo tentativo: rimpiazziamo l'oggetto avente chiave k con ∅) 0 1 2 3 4 5 6 ∅ 025-6612001 481-1715002 ∅ 151-2250004 588-5550005 567-1160004 … Strutture Dati ADT Mappa Implementazione con tabelle hash e linear probing Classe HashTableMap che implementa Map Come implementare remove(k)? (primo tentativo: rimpiazziamo l'oggetto avente chiave k con ∅) Supponiamo di voler rimuovere 0 1 2 3 4 5 6 025-6612001 481-1715002 ∅ … Strutture Dati l'oggetto con chiave k1=151-2250004 ∅ 151-2250004 588-5550005 k1 567-1160004 k2 h(k1) = h(k2) ADT Mappa Implementazione con tabelle hash e linear probing Classe HashTableMap che implementa Map Come implementare remove(k)? (primo tentativo: rimpiazziamo l'oggetto avente chiave k con ∅) 0 1 2 3 4 5 6 ∅ 025-6612001 481-1715002 ∅ ∅ 588-5550005 567-1160004 … Strutture Dati k2 h(k1) = h(k2) ADT Mappa Implementazione con tabelle hash e linear probing Classe HashTableMap che implementa Map Come implementare remove(k)? (primo tentativo: rimpiazziamo l'oggetto avente chiave k con ∅) Supponiamo ora di fare get(k2): 0 1 2 3 4 5 6 ∅ 025-6612001 481-1715002 ⇒ get non funziona! ∅ ∅ 588-5550005 567-1160004 … Strutture Dati la ricerca parte da A[h(k2)] e si arresta subito perché incontra ∅ k2 ADT Mappa Implementazione con tabelle hash e linear probing Classe HashTableMap che implementa Map Come implementare remove(k)? rimpiazziamo l'oggetto avente chiave k con un oggetto speciale AVAILABLE 0 1 2 3 4 5 6 025-6612001 481-1715002 ∅ AVAILABLE 588-5550005 567-1160004 … Strutture Dati In questo modo get(k2) continua la ricerca fino a trovare k2 ∅ k2 ADT Mappa Implementazione con tabelle hash e linear probing Classe HashTableMap che implementa Map Metodo remove(k) – cerchiamo una entrata con chiave k – se tale entrata (k, v) viene trovata, la rimpiazziamo con AVAILABLE e restituiamo l'elemento v altrimenti, restituiamo null – Strutture Dati ADT Mappa Implementazione con tabelle hash e linear probing Classe HashTableMap che implementa Map Metodo put(k,v) – – – Strutture Dati se la tabella è “quasi piena” raddoppiamo la dimensione del bucket array (per mantenere il load factor < 0.5) si parte dalla cella h(k) si fa il probe di tutte le celle consecutive fino a che si verifichi una delle seguenti condizioni: ● viene trovata una cella i che contiene già una chiave k (rimpiazziamo il valore precedente con v e restituiamo il vecchio valore) ● viene trovata una cella i che è vuota oppure contiene AVAILABLE (memorizziamo l'entrata (k, v) nella cella i e restituiamo null) ADT Mappa Implementazione con tabelle hash e linear probing public class HashTableMap<K, V> implements Map<K,V>{ public static class HashEntry<K,V> implements Entry<K,V> { protected K key; protected V value; public HashEntry(K k, V v) { key = k; value = v; } public V getValue() { return value; } public K getKey() { return key; } public V setValue(V val) { V oldValue = value; value = val; return oldValue; } public boolean equals(Object o) { HashEntry<K,V> ent; try { ent = (HashEntry<K,V>) o; } catch (ClassCastException ex) { return false; } return (ent.getKey() == key) && (ent.getValue() == value); } } ... } Strutture Dati ADT Mappa Implementazione con tabelle hash e linear probing public class HashTableMap<K, V> implements Map<K,V>{ ... protected Entry<K,V> AVAILABLE = new HashEntry<K,V>(null, null); protected int n = 0; // numero di entrate nella mappa protected int prime, capacity; // prime factor e capacita` del bucket array protected Entry<K,V>[] bucket;// il bucket array protected int scale, shift; // i fattori shift e scale /** Crea una tabella hash con prime factor 109345121 e capacita` 1024. */ public HashTableMap(){ this(109345121,1024); } /** Crea una tabella hash con prime factor 109345121 e capacita` data. */ public HashTableMap(int cap) { this(109345121, cap); } /** Crea una tabella hash con prime factor e capacita` dati. */ public HashTableMap(int p, int cap) { prime = p; capacity = cap; bucket = (Entry<K,V>[]) new Entry[capacity]; // safe cast java.util.Random rand = new java.util.Random(); scale = rand.nextInt(prime-1) + 1; //num. casuale compreso tra 1 e prime-1 shift = rand.nextInt(prime); //num. casuale compreso tra 0 e prime-1 } ... } Strutture Dati ADT Mappa Implementazione con tabelle hash e linear probing public class HashTableMap<K, V> implements Map<K,V>{ ... protected void checkKey(K k) { if (k == null) throw new InvalidKeyException("Chiave invalida: null."); } public int hashValue(K key) {//calcola il valore hash con il metodo mad return (int)((Math.abs(key.hashCode()*scale + shift) % prime) % capacity); } /** Restituisce una collezione iterable contenente tutte le chiavi. */ public Iterable<K> keys() { PositionList<K> keys = new NodePositionList<K>(); for (int i=0; i < capacity; i++) if ((bucket[i] != null) && (bucket[i] != AVAILABLE)) keys.addLast(bucket[i].getKey()); return keys; } /** * * * ... } Metodo ausiliario di ricerca – restituisce l'indice della chiave in input, se la chiave viene trovata, oppure il valore negativo -(a + 1) (se la chiave non esiste nella tabella), dove a e` l'indice della prima cella vuota o disponibile trovata. */ Strutture Dati ADT Mappa Implementazione con tabelle hash e linear probing public class HashTableMap<K, V> implements Map<K,V>{ ... protected int findEntry(K key) throws InvalidKeyException { int avail = -1; checkKey(key); int i = hashValue(key); int j = i; do { Entry<K,V> e = bucket[i]; if ( e == null) { if (avail < 0) avail = i; // la cella i e` la prima cella disponibile break; // la chiave non e` nella tabella } if (key.equals(e.getKey())) // se troviamo la chiave... return i; // restituiamo l'indice della chiave trovata if (e == AVAILABLE) { if (avail < 0) avail = i; // la cella i e` la prima cella disponibile } i = (i + 1) % capacity; // continua la ricerca } while (i != j); return -(avail + 1); // avail e` la prima cella vuota o AVAILABLE } ...} Strutture Dati ADT Mappa Implementazione con tabelle hash e linear probing public class HashTableMap<K, V> implements Map<K,V>{ ... public V get (K key) throws InvalidKeyException { int i = findEntry(key); if (i < 0) return null; // se i<0 la chiave non esiste return bucket[i].getValue(); } public V put (K key, V value) throws InvalidKeyException { int i = findEntry(key); //trova l'appropriata cella per questa entrata if (i >= 0) // questa chiave ha gia` un valore nella tabella return ((HashEntry<K,V>) bucket[i]).setValue(value); // setta il nuovo valore if (n >= capacity/2) { rehash(); // rehash per mantenere il load factor <= 0.5 i = findEntry(key); //trova l'appropriata cella per questa entrata } bucket[-i-1] = new HashEntry<K,V>(key, value); n++; return null; // non e` stato trovato alcun valore precedente } ... } Strutture Dati ADT Mappa Implementazione con tabelle hash e linear probing public class HashTableMap<K, V> implements Map<K,V>{ ... protected void rehash() { capacity = 2*capacity; Entry<K,V>[] old = bucket; bucket = (Entry<K,V>[]) new Entry[capacity]; // il nuovo bucket java.util.Random rand = new java.util.Random(); scale = rand.nextInt(prime-1) + 1; // new hash scaling factor shift = rand.nextInt(prime); // new hash shifting factor for (int i = 0; i < old.length; i++) { Entry<K,V> e = old[i]; if ((e != null) && (e != AVAILABLE)) { // una entrata valida int j = - 1 - findEntry(e.getKey()); bucket[j] = e; } } } ... } Strutture Dati ADT Mappa Implementazione con tabelle hash e linear probing public class HashTableMap<K, V> implements Map<K,V>{ ... public V remove (K key) throws InvalidKeyException { int i = findEntry(key); // cerca la chiave key if (i < 0) return null; // niente da rimuovere in questo caso V toReturn = bucket[i].getValue(); bucket[i] = AVAILABLE; // disattiva la cella i n--; return toReturn; } ... } Strutture Dati