This is an old revision of the document!


Laborator 07 - HashTable

Obiective

În urma parcurgerii acestui laborator studentul va fi capabil să:

  • definească tipul de date dicționar
  • implementeze un dicționar folosind tabele de dispersie
  • prezinte avantaje / dezavataje ale diverselor implementări de dicționare

Ce e un dicționar?

Un dicţionar este un tip de date abstract compus dintr-o colecție de chei şi o colecție de valori, în care fiecărei chei îi este asociată o valoare. Operația de găsire a unei valori asociate unei chei poartă numele de indexare, aceasta fiind și cea mai importantă operație (din acest motiv dicționarele se mai numesc și array-uri asociative - fac asocierea între o cheie și o valoare).

Operații de bază

  • put(key, value):
    • adaugă în dicționar o nouă valoare și o asociază unei anumite chei
    • dacă perechea există deja, valorea este înlocuită cu cea nouă
  • remove(key):
    • elimină din dicţionar cheia key (şi valoarea asociată acesteia)
  • get(key):
    • întoarce valoarea asociată cheii
    • dacă perechea nu există, întoarce corespunzător o eroare pentru a semnala acest lucru
  • has_key(key):
    • întoarce TRUE dacă există cheia respectivă în dicționar
    • întoarce FALSE dacă nu există cheia respectivă în dicționar

Implementare: hashtable

O implementare frecvent întâlnită a unui dicționar este cea folosind o tabelă de dispersie (hashtable). Un hashtable este o structură de date optimizată pentru funcția de lookup (în medie, timpul de căutare este constant: O(1)). Acest lucru se realizează transformând cheia într-un hash - un număr întreg fără semn pe 16 / 32 / 64 de biţi, etc. - folosind o funcție hash. În cel mai defavorabil caz, timpul de căutare al unui element poate fi O(n). Totuși, tabelele de dispersie sunt foarte utile în cazul în care se stochează cantități mari de date, a căror dimensiune (mărime a volumului de date) poate fi anticipat.

Funcția hash trebuie aleasă astfel încât să se minimizeze numărul coliziunilor (chei diferite care produc aceleași hash-uri). Coliziunile apar în mod inerent, deoarece lungimea hash-ului este fixă, iar obiectele de stocare pot avea lungimi și conținut arbitrare. În cazul apariției unei coliziuni, valorile se stochează pe aceeaşi poziție (în același bucket). În acest caz, căutarea se va reduce la compararea valorilor efective în cadrul bucketului.

Implementarea cu liste înlănțuite

O implementare a unui hashtable care trateaza coliziunile se numește înlănțuire directă (direct chaining). Cea mai simplă formă folosește câte o listă înlănțuită pentru fiecare bucket, practic un array de liste. Fiecare listă este asociată unui anumit hash.

  • inserarea în hashtable presupune găsirea indexului corect și adăugarea elementului la lista corespunzătoare.

Hash-ul poate depăşi cu mult dimensiunea array-ului de bucket-uri, ceea ce duce la necesitatea folosirii, cel mai frecvent, a operaţiei moduloindex = hash % HMAX, pentru a situa indexul bucket-ului în care va fi inserat elementul, în limitele necesare.

Dacă dimensiunea array-ului este exprimată în puteri ale lui 2, se mai poate folosi şi formula următoare → index = hash & (HMAX - 1).

HMAX reprezintă dimensiunea maximă a array-ului.

  • ștergerea presupune căutarea și scoaterea elementului din lista corespunzătoare.

Avantajul tabelelor de dispersie constă în faptul că operația de ștergere este simplă, iar redimensionarea tabelei poate fi amânată mult timp, deoarece (chiar și când toate pozițiile din hashtable sunt folosite), performanța este încă bună.

Dezavantajele acestei soluții sunt cele moștenite de la listele înlănțuite: pentru stocarea unor date mici, overhead-ul introdus poate fi semnificativ; iar parcurgerea unei liste este costisitoare.

Există și alte structuri de date cu ajutorul cărora se poate implementa un hashtable ca mai sus. Un exemplu ar fi un arbore binar echilibrat, pentru care timpul - pe cazul cel mai defavorabil - se poate reduce la O(log n) față de O(n). Totuși, această variantă se poate dovedi ineficientă dacă hashtable-ul este proiectat pentru puține coliziuni.

Alte implementări

  • arbori binari echilibrați
  • radix-tree
  • prefix-tree
  • array-uri judy

Acestea prezintă timpi de căutare mai buni pentru cel mai defavorabil caz și folosesc eficient spațiul de stocare în funcție de tipul de date folosit.

Exemplu

Funcție de hash pentru șiruri de caractere:

hash.h
#ifndef __HASH__H
#define __HASH__H
 
// Hash function based on djb2 from Dan Bernstein
// http://www.cse.yorku.ca/~oz/hash.html
//
// @return computed hash value
 
unsigned int hash_fct(char *str)
{
        unsigned int hash = 5381;
        int c;
 
        while ( (c = *str ++) != 0 ) {
                hash = ((hash << 5) + hash) + c;
        }
 
        return hash;
}
 
#endif // __HASH__H

Header-ul pentru Hashtable:

hashtable.h
#ifndef __HASHTABLE__H
#define __HASHTABLE__H
#include <list>
 
template<typename Tkey, typename Tvalue>
struct elem_info {
    Tkey key;
    Tvalue value;
};
 
template<typename Tkey, typename Tvalue>
class Hashtable {
private:
        std::list<struct elem_info<Tkey, Tvalue> > *H;
        int HMAX;
        unsigned int (*hash) (Tkey);
public:
        Hashtable(int hmax, unsigned int (*h)(Tkey));
        ~Hashtable();
 
        void put(Tkey key, Tvalue value);
        void remove(Tkey key);
        Tvalue get(Tkey key);
        bool has_key(Tkey key);
};
 
#endif // __HASHTABLE__H

Exerciţii

1) [3p] Pornind de la header-ul de mai sus, implementați structura de date hashtable

  • [1p] constructor si destructor
  • [1p] metoda put şi remove
  • [1p] metoda has_key şi get

2) [1p] Testați implementarea voastră folosind un cod simplist.

3) [3p] Considerăm o aplicație simplistă de tip instant messenger. Utilizatorii se pot autentifica în sistem (devin activi), pot părăsi sistemul (devin inactivi) sau pot trimite mesaje între ei. Înainte de trimiterea unui mesaj, aplicația client lansează o cerere către server pentru a afla dacă utilizatorul către care se trimite mesajul este online sau nu. Serverul menține o listă a tuturor clienților activi și va trebui să răspundă tuturor cererilor. Voi va trebui să implementați această funcționalitate.

Serverul va avea de tratat următoarele scenarii:

  • 0 username - Utilizatorul username s-a autentificat (devine activ)
  • 1 username - Utilizatorul username s-a deconectat (devine inactiv)
  • 2 username - Cerere de la un client: 'Este utilizatorul username activ?'

Pentru fiecare cerere, se va afişa pe ecran TRUE sau FALSE dacă utilizatorul este sau nu online.

Date de intrare
0 mariap
0 georgeta
2 mariap
2 florint
1 mariap
2 mariap
Date de ieşire
TRUE
FALSE
FALSE

Schelet de cod: lab06_schelet_cod.zip

Paşi de rezolvare (urmăriţi TODO-urile din schelet):

  • [1p] TODO 1: implementare clasa String
  • [0.5p] TODO 2: definiţi funcţia hash pentru clasa String
  • [1.5p] TODO 3: rezolvaţi problema folosind un Hashtable cu cheie de tip String.

4) [Bonus 1p] Implementați și testați redimensionarea unui hashtable: funcția resize() dublează dimensiunea structurii interne a tabelei de dispersie. Dublarea se va face în momentul în care raportul dintre numărul de elemente introduse în hashtable size şi numărul de bucket-uri HMAX este mai mare decât o valoare aleasă (size / HMAX > 0.75). Comportamentul dorit pentru această funcţionalitate este următorul: se redimensionează array-ul de bucket-uri, iar apoi fiecare bucket este parcus în ordine și elementele sunt redistribuite după valoarea noului hash.

Interviu

Această secțiune nu este punctată și încearcă să vă facă o oarecare idee a tipurilor de întrebări pe care le puteți întâlni la un job interview (internship, part-time, full-time, etc.) din materia prezentată în cadrul laboratorului.

  • Pentru o colecție de date cu nume, prenume și multe alte câmpuri, cum ai defini funcția hash?
  • Care este complexitatea unei operatiuni de căutare într-un hashtable?
  • Care este diferența dintre un hashtable și un vector?
  • Descrie cum ai implementa DEX cu ajutorul unui hashtable.
  • În ce condiții căutarea într-un hashtable ar putea să nu fie constantă?

Bibliografie

sd-ca/laboratoare/laborator-07.1429262518.txt.gz · Last modified: 2015/04/17 12:21 by razvan_madalin.matei
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0