Manca il modo in cui le due strutture dati gestiscono le collisioni di hash. I filtri bloom non memorizzano i valori effettivi, quindi lo spazio richiesto è la dimensione costante dell'array designato. Invece, se usi un hash tradizionale, cerca di memorizzare tutti i valori che gli dai, quindi cresce con il tempo.
Considera una funzione hash semplificata (solo a scopo di esempio!) f(x) = x % 2
. Ora è in ingresso i seguenti numeri interi: 2, 3, 4, 5, 6, 7
.
Hash standard: i valori indicati verranno sottoposti a hash e finiremo con molte collisioni dovute a f(2) = f(4) = f(6) = 0
e f(3) = f(5) = f(7) = 1
. Tuttavia, l'hash memorizza tutti questi valori e sarà in grado di dirti che 8
non è memorizzato in esso. Come lo fa? Tiene traccia delle collisioni e memorizza tutti i valori con lo stesso valore hash, quindi quando lo interroghi, confronta ulteriormente la tua query. Quindi interroghiamo la mappa per 8
:, f(8) = 0
quindi esamineremo un bucket in cui abbiamo già inserito 2, 4, 6
e dobbiamo fare 3 confronti per dirti che 8
non faceva parte dell'input.
Filtro Bloom: normalmente, ciascun valore di input viene sottoposto a hash rispetto a k
diverse funzioni hash. Ancora una volta, per semplicità, supponiamo che usiamo solo la singola funzione hash f
. Abbiamo quindi bisogno di un array di 2 valori e quando incontriamo l'input 2
significa che a causa di f(2) = 0
impostare il valore dell'array in posizione 0
sul valore 1
. Lo stesso succede per 4
e 6
. Allo stesso modo, gli input 3, 5, 7
impostano ciascuno la posizione dell'array 1
su value 1
. Ora chiediamo se 8
faceva parte dell'input: f(8) = 0
e l'array in posizione lo 0
è 1
, quindi il filtro bloom affermerà erroneamente che 8
era effettivamente parte dell'input.
Per diventare un po 'più realistici, consideriamo che aggiungiamo una seconda funzione hash g(x) = x % 10
. Con ciò, il valore di ingresso 2
porta a due valori hash f(2) = 0
e g(2) = 2
e due corrispondenti posizioni di matrice vengono impostati 1
. Ovviamente, l'array ora dovrebbe essere almeno di dimensioni 10
. Ma quando eseguiamo una query 8
, verificheremo l'array nella posizione 8
dovuta g(8) = 8
e tale posizione rimarrà comunque 0
. Ecco perché ulteriori funzioni hash riducono i falsi positivi che otterrai.
Confronto: il filtro bloom utilizza k
funzioni hash, il che significa che k
si accede a posizioni casuali dell'array. Ma quella cifra è esatta. L'hash invece ti garantisce solo un tempo di accesso costante ammortizzato, ma può de-generare in base alla natura della funzione hash e ai dati di input. Quindi è in genere più veloce, ad eccezione dei casi non generati.
Tuttavia, una volta che si verifica una collisione dell'hash, l'hash standard dovrà verificare l'uguaglianza dei valori memorizzati rispetto al valore della query. Questo controllo di uguaglianza può essere arbitrariamente costoso e non si verificherà mai con un filtro di fioritura.
In termini di spazio, il filtro bloom è costante, in quanto non è mai necessario utilizzare più memoria dell'array designato. D'altra parte, l'hash cresce dinamicamente e può diventare molto più grande a causa della necessità di tenere traccia dei valori di collisione.
Trade-off: ora che sai cosa costa poco e cosa no e in quali circostanze, dovresti essere in grado di vedere il trade-off. I filtri Bloom sono fantastici se vuoi rilevare molto rapidamente che un valore è stato visto in precedenza, ma può vivere con falsi positivi. D'altra parte, puoi scegliere la mappa hash se desideri la correttezza garantita al prezzo di non poter giudicare esattamente il tuo tempo di esecuzione, ma puoi accettare casi degenerati occasionalmente che potrebbero essere molto più lenti della media.
Allo stesso modo, se ti trovi in un ambiente di memoria limitato, potresti voler preferire i filtri bloom per la loro garanzia di utilizzo della memoria.