Implementazione dell`ADT mappa con tabelle hash

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