gitweb on Svarog

projekti pod git sistemom za održavanje verzija -- projects under the git version control system
Hash, pomereni materijali vezani za OHashSet
[spa2-materijali.git] / Hash / hash.txt
diff --git a/Hash/hash.txt b/Hash/hash.txt
deleted file mode 100644 (file)
index d9cdb18..0000000
+++ /dev/null
@@ -1,571 +0,0 @@
-% Hash funkcije - pripremni materijal
-% SPA2, DMI-PMF-UNS, www.dmi.rs
-% 2015
-
-Heš kod i jednakost u programskom jeziku Java
-=============================================
-
-U programskom jeziku Java u svakoj klasi mogu (i često i trebaju) da
-postoje metodi
-
-    public int hashCode()
-    public boolean equals(Object o)
-
-Oni omogućavaju vrlo efikasne operacije nad velikim skupovima
-objekata, ali je bitno da uzajamno imaju konzistentno ponašanje.
-
-Tačnije, ako su dva objekta jednaka u skladu sa `equals` tada im i heš
-vrednost koju vraća `hashCode` mora biti ista. Obrnutno ne mora da važi,
-odnosno može postojati neograničeni broj različitih objekata koji
-imaju isti heš kod. Isto tako ako dva objekta imaju različih heš, tada
-oni ne bi smeli biti jednaki kad se pita `equals`. No i ovde ne važi
-obrnuto, odnosno ako su dva objekta različiti, heš kod im ne mora biti
-različit.
-
-Zbog ovih podrazumevanih odnosa je jako bitno da se ova dva metoda
-pišu zajedno, odnosno da se ne menja samo jedan bez promena u drugom.
-
-U opštem slučaju ako se ovo ponašanje ne promeni reimplementacijom
-ovih metoda, objekti u Javi će za ova dva metoda da vraćaju vrednosti
-koje se odnose na sam pokazivač u memoriji. Odnosno dva objekta su
-isti samo ako se bukvalno isti objekat u memoriji, bez obzira na to da
-li imaju isti sadržaj. Isto tako će sam heš kod biti generisan od
-vrednosti samog pokazivača, a ne od sadržaja objekta. Jako često ovo
-nije poželjno ponašanje, te se zbog toga moraju pisati svoji metodi.
-
-Heš kod je uvek poželjno pisati tako da se dobija što više različitih
-vrednosti za različite instance objekata. U idealnom slučaju bi se za
-svaki različit objekat dobijao različit heš kod, međutim to u praksi
-često nije moguće.
-
-Klasa `String` ima redefinisana oba ova metoda. Heš kod koji se dobija
-za ovu klasu je veoma kvalitetan i uglavnom se vredi osloniti na njega
-za polja ovog tipa.
-
-
-Ocene kvaliteta funkcije
-========================
-
-Kvalitetna heš funkcija ravnomerno raspoređuje elemente po celoj tabeli,
-odnosno dobijamo podskupove sličnih veličina.
-
-Za poređenje kvaliteta heš funkcija se mogu koristi različite mere. Prva je
-procentualna popunjenost tabele -- odnosno procenat podskupova (listi)
-u kojima postoji bar po jedan element.
-
-Nešto komplikovanija mera je $\chi^2$ \emph{(hi-kvadrat)} test.
-\begin{equation}
-  \chi ^2 = \sum_{i=1}^{n} \frac{(K_i - E)^2}{E}
-\end{equation}
-
-gde je $K_i$ - broj elemenata u podskupu $i$, a $E$ - očekivan broj
-elemenata u podskupu, odnosno idealna vrednost koja je jednaka
-količniku ukupnog broja elemenata i broja skupova.
-
-Iako deluje relativno komplikovano, ovo nam zapravo daje dobru ocenu
-koliko su (ne)ravnomerno raspoređeni elementi.  U idealno slučaju bi
-sve $K_i$ vrednosti bile jednake sa $E$ i imali bi da je vrednost
-testa nula. U opštem slučaju se naravno javljaju odstupanja od
-proseka, a pošto kvadriramo udaljenosti, $\chi^2$ brzo raste ukoliko
-ima većih anomalija.
-
-Pri pisanju heš funkcije treba ciljati na što veću procentualnu
-popunjenost tabele, idealno je to 100\% za veću količinu podataka.
-
-Sa druge strane, $\chi^2$ test predstavlja ravnomernost duzina listi,
-te bi trebao ostajati relativno konzistentan. Kod dobrih heš funkcija
-koje ravnomerno rasporedjuju elemente ovaj test često daje niže
-vrednosti tek za veći broj ubačenih elemenata, pošto tek onda dolaze
-do izražaja statističko ravnomerno raspoređivanje.
-
-Dobre vrednosti za ovaj test za heš funkciju zavise od podataka sa
-kojima se radi, ali u opštem slučaju treba ciljati da budu niže od
-broja podskupova.
-
-
-Primeri
-=======
-
-U sledećim primerima će se razmatrati različiti tipovi podataka i kako
-se oni efikasno mogu skladištiti u heš tabelama. Uglavnom će se
-razmatrati ideje heš tabele koja ima tačno određen broj podskupova i
-pratiti njihovu pokrivenosti i hi kvadrat meru. Tipično će se
-razmatrati razlitiče veličine ovih tabela, na primer 101, 503 i 997
-radi bolje ilustracije uticaja veličine na lošije heš funkcije.
-
-
-Korišćenje kancelarija
-----------------------
-
-Treba da čuvamo podatke o korišćenju kancelarija u toku jednog dana i
-da omogućimo što bržu proveru da li je neka konkretna osoba koristila
-neku konkretnu kancelariju. Podaci vezuju prezimena za identifikacione
-brojeve soba.  Naravno moguće je da više ljudi koristilo istu
-kancelariju, a moguće je i da je neko više puta koristio istu -- ovo
-će se u tabeli skladištiti samo jedan put, pošto nas samo zanima ko je
-bio unutra, a ne koliko puta.
-
-Uzmimo sledeću definiciju klase
-
-```Java
-    class Kancelarija {
-       private int broj;
-       private String prezime;
-       ....
-    }
-```
-
-U datim fajlovima nema više od~100 prostorija, a i postoji samo 
-oko~60 različitih prezimena (tj zaposlenih).
-
-Broj sobe tipa `int` i deluje kao "očigledan" kandidat za heš kod.
-Validno bi bilo da se ova informacija vraća kao heš kod:
-
-```Java
-    public int hashCode() {
-        return broj;
-    }
-```
-
-Pogledajmo primere učitavanja većeg broja podataka u heš tabele sa
-različitim brojem podskupova, ako imamo tako definisanu heš funkciju:
-
-Elements    : 4885
-Percent full:  99.01 
-Chi square  :  67.3081 
-'Columns'   : 101
-
-Elements    : 4885
-Percent full:  19.88 
-Chi square  : 19778.4749 
-'Columns'   : 503
-
-Elements    : 4885
-Percent full:  10.03 
-Chi square  : 44000.6551 
-'Columns'   : 997
-
-Budući da ima samo 100 soba, primećujemo da vrednosti nisu
-zadovoljavajuće čim imamo veće tabele, odnosno imamo manje od 20\% pokrivenosti
-tabele sa 503 mesta i oko 10\% za 997 mesta.
-
-Slično važi i za prezimena, budući da ih ima samo šestdesetak, bez
-obzira na koliko je dobar heš kod u klasi `String`, možemo imati samo
-šestdesetak različitih rezultata, odnosno dobijamo još gore rezultate,
-čak i za relativno malu tabelu sa 101 podskupom. Ovde treba obratiti
-pažnju i da je moguće da imamo `null` polje.
-
-```Java
-    public int hashCode() {
-        if (prezime != null) {
-            return prezime.hashCode();
-       } else {
-           return 0;
-       }
-    }
-```
-
-
-Elements    : 4885
-Percent full:  49.50 
-Chi square  : 6320.7484 
-'Columns'   : 101
-
-Elements    : 4885
-Percent full:  11.53 
-Chi square  : 39251.8342 
-'Columns'   : 503
-
-Elements    : 4885
-Percent full:   5.72 
-Chi square  : 85190.9396 
-'Columns'   : 997
-
-Očigledno je potrebno kombinovati ove dve vrednosti. Možemo ih
-jednostavno samo sabrati:
-
-```Java
-       public int hashCode() {
-               int rez = 0;
-               if (prezime != null) {
-                       rez += prezime.hashCode();
-               }
-               rez += broj;
-               return rez;
-       }
-```
-
-Dobijamo dosta bolje rezultate u testovima:
-
-Elements    : 4885
-Percent full: 100.00 
-Chi square  :  20.7468 
-'Columns'   : 101
-
-Elements    : 4885
-Percent full: 100.00 
-Chi square  : 720.2837 
-'Columns'   : 503
-
-Elements    : 4885
-Percent full:  99.00 
-Chi square  : 1341.3007 
-'Columns'   : 997
-
-Množenje će tipično dati bolje vrednosti nego sabiranje kad imamo
-vrednosti koje su jako blizu jedne drugima (što brojevi kancelarija
-koji idu zaredom jesu), pošto pomaže u "raspipanju" vrednosti na većem
-opsegu.
-
-Na primer
-
-```Java
-       public int hashCode() {
-               int rez = 1;
-               if (prezime != null) {
-                       rez *= prezime.hashCode();
-               }
-               rez *= broj;
-               return rez;
-       }
-```
-
-Elements    : 4885
-Percent full: 100.00 
-Chi square  :  78.8864 
-'Columns'   : 101
-
-Elements    : 4885
-Percent full: 100.00 
-Chi square  : 483.8686 
-'Columns'   : 503
-
-Elements    : 4885
-Percent full:  99.10 
-Chi square  : 951.0727 
-'Columns'   : 997
-
-Iz rezultata se vidi da se konzistentno dobijaju dobre vrednosti sa
-ovakvom heš funkcijom. Naročito je bitno što nam vrednost hi kvadrat
-testa govori da imamo odstupanje u veličinama podskupova u proseku
-manje od jedan.
-
-Istovremeno je bitno da se i `equals` definiše da koristi ista polja.
-Metod zahteva da parametar bude tipa `Object`, pa uvek moramo da krenemo
-od toga. Najjednostavnija varijanta zahteva da proverimo da li je
-prosledjeno nešto što je iste klase kao i naš objekat, pa tek
-onda možemo da menjamo tip i da poredimo polja.
-
-```Java
-       public boolean equals(Object o) {
-               // proveravamo da li je objekat sa
-               // kojim se poredimo Kancelarija 
-               if (o instanceof Kancelarija) {
-                       // pretvaramo objekat u kancelariju
-                       Kancelarija k2 = (Kancelarija) o;
-                       // poredimo polja
-                       if (prezime.equals(k2.prezime) && broj == k2.broj) {
-                               return true;
-                       }
-               }
-               return false;
-       }
-```
-
-Međutim potpuna verzija `equals` metoda treba da proveri da li je u
-pitanju baš ista klasa kao naša (moguće da je neki naslednik koji
-tehnički ne mora biti isti), te da tu uzme u obzir i da je moguće da
-nam je prosleđen `null` što nikako ne može biti isto kao i naš
-objekat. Dalje se može ubrzati postupak proverom da li su u pitanju
-pokazivači na isto mesto pošto nema potrebe za proverama onda.
-
-Potpuna verzija bi izgledala ovako:
-
-```Java
-       public boolean equals(Object o) {
-               // Objekat je identican
-               if (this == o) {
-                       return true;
-               }
-               // Null je uvek razlicit
-               if (o == null) {
-                       return false;
-               }
-               // Ako su klase razlicite, objekti ne mogu bili jednaki
-               if (getClass() != o.getClass()) {
-                       return false;
-               }
-
-               // pretvaramo objekat u kancelariju
-               Kancelarija k2 = (Kancelarija) o;
-
-               // Prvo proveravamo broj
-               if (broj != k2.broj) {
-                   return false;
-               }
-
-               // A potom prezime
-               if (!Objects.equals(prezime, k2.prezime)) {
-                   return false;
-               }
-
-               // Proverili smo polja i sva su jednaka
-               return true;
-       }
-```
-
-Razmotrimo sledeću modifikaciju klase: recimo da se i dalje želi samo
-pratiti u tabeli ko je bio u kojoj kancelariji, ali da su negde već
-definisani objekti u kojima se skladišti i vreme poslednjeg
-pristupanja, ili recimo neki dodatni opis zašto je neko bio u
-kancelariji.
-
-```Java
-    class KancelarijaVreme {
-       private int broj;
-       private String prezime;
-
-       private int pristupDan, pristupSat;
-
-       private String komentar;
-    }
-```
-
-Budući da za naše potrebe nije bitno kad je pristupano niti zbog čega,
-i dalje bi trebali da koristimo iste kodove za `equals` i za
-`hashCode` pošto nam je dosta da ubacimo samo jedan objekat za svako
-pojedinačno korišćenje. Negde druge se naravno oni mogu koristiti za
-detaljije zapise, ali nas to trenutno ne interesuje.
-
-
-Gadjanje mete
--------------
-
-Potrebno je da čuvamo podatke o rezultatima gadjanja mete. U nizu se
-nalazi do 20 brojeva izmedju i uključujući 0 i 10.
-
-Recimo da je klasa definisana na sledeći način:
-
-```Java
-public class Gadjanje {
-       private int[] rezultati;
-       private static int MAX_DUZ = 20;
-       ...
-}
-```
-
-Pošto je moguće (i vrlo verovatno zapravo) da neko nije gađao svih 20
-puta, u podacima će biti nizovi različitih dužina.  U skladu sa tim je
-i format za unos takav da je prvi broj u redu zapravo broj gađanja,
-koji određuje veličinu niza.
-
-Funkcija jednakosti bi izgledala ovako:
-
-```Java
-       public boolean equals(Object o) {
-               // Objekat je identican
-               if (this == o) {
-                       return true;
-               }
-               // Null je uvek razlicit
-               if (o == null) {
-                       return false;
-               }
-               // Ako su klase razlicite, objekti ne mogu bili jednaki
-               if (getClass() != o.getClass()) {
-                       return false;
-               }
-
-               Gadjanje o2 = (Gadjanje) o;
-               // proveravamo da li je polje null pre dalje provere
-               if (rezultati != null && o2.rezultati != null) {
-                       if (o2.rezultati.length == rezultati.length) {
-                               for (int i = 0; i < rezultati.length; i++) {
-                                       if (o2.rezultati[i] != rezultati[i]){
-                                               // cim je nesto razlicito nisu isti
-                                               return false;
-                                       }
-                               }
-                               // ako se sve vrednosti slazu isti su
-                               return true;
-                       }
-                       return false;
-               } else {
-                       // vracamo da li su oba null, tj da li su jednaki
-                       return (rezultati == null && o2.rezultati == null);
-               }
-       }
-```
-
-"Očigledno" rešenje je samo sabrati sve ove vrednosti i vratiti broj.
-Međutim ovakvo rešenje daje maksimalno 200 različitih vrednosti i dosta
-loše vrednosti u tabeli:
-
-Elements    : 9049
-Percent full: 100.00 
-Chi square  : 314.5114 
-'Columns'   : 101
-
-Elements    : 9049
-Percent full:  27.63 
-Chi square  : 34841.7383 
-'Columns'   : 503
-
-Elements    : 9049
-Percent full:  13.94 
-Chi square  : 77947.1553 
-'Columns'   : 997
-
-U ovakvoj strukturi, pozicija u nizu bi trebalo da bude značajna za
-krajnje heš vrednosti. Da bi se to postiglo jedan pristup je množenje
-brojeva sa svojim pozicijama ili različitim (prostim) brojevima.
-
-Za ovaj primer u kome je dužina niza raznolika i ima više "početnih"
-elemenata je dodatno pogodno da se težine primenjuju unazad, pa recimo
-možemo uvesti da prvi element ima težinu 21, a poslednji u nizu od 20
-elemenata težinu 2. Dodatno to možemo unaprediti množenjem prostim
-brojem pri svakom sabiranju, tako da se ta množenja akumuliraju na
-prvim elementima.
-
-```Java
-       public int hashCode() {
-               int rez = 0;
-               if (rezultati != null) {
-                       for (int i = 0; i < rezultati.length; i++) {
-                               rez = (rez + rezultati[i] * (MAX_DUZ + 1 - i)) * 7;
-                       }
-               }
-               return rez;
-       }
-```
-       
-Elements    : 9084
-Percent full: 100.00 
-Chi square  : 101.7743 
-'Columns'   : 101
-
-Elements    : 9084
-Percent full: 100.00 
-Chi square  : 415.1915 
-'Columns'   : 503
-
-Elements    : 9084
-Percent full: 100.00 
-Chi square  : 981.2660 
-'Columns'   : 997
-
-
-Tabla za igru XO
-----------------
-
-Zadatak nam je da efikasno čuvamo podatke o pozicijama u igri XO,
-odnosno matrice 3x3, u kojima su vrednosti -1 za "X", 0 za prazno
-polje i 1 za "O".
-
-Npr:
-```
- 1 0 -1  O _ X
- 1 1 -1  O O X
--1 0  1  X _ O
-```
-predstavlja poziciju u kojoj je ``O'' pobedio/la.
-
-Polja klase bi se mogla definisati na sledeći način:
-
-```Java
-public class XO  {
-       public static final int DIM = 3;
-       private int[][] tabla = new int[DIM][DIM];
-....
-}
-```
-
-Poredjenje ovakvih objekata bi naravno trebalo da pojedinačno ispituje
-elemente matrice, na primer:
-
-```Java
-       public boolean equals(Object o) {
-               // Objekat je identican
-               if (this == o) {
-                       return true;
-               }
-               // Null je uvek razlicit
-               if (o == null) {
-                       return false;
-               }
-               // Ako su klase razlicite, objekti ne mogu bili jednaki
-               if (getClass() != o.getClass()) {
-                       return false;
-               }
-
-               // menjamo tip da mozemo da poredimo
-               XO o2 = (XO) o;
-               // posto je u ovoj klasi uvek inicijalizovano polje table
-               // i uvek je DIM x DIM ne moramo proveravati null
-               for (int i = 0; i < DIM; i++) {
-                       for (int j = 0; j < DIM; j++) {
-                               if (o2.tabla[i][j] != tabla[i][j]) {
-                                       return false;
-                               }
-                       }
-               }
-               return true;
-       }
-```
-
-Za ovaj primer vredi prvo razmotriti da neke relativno očigledne
-tehnike nisu efikasne: sumiranje ovakve matrice daje vrednosti do plus
-ili minus 9, odnosno ima samo 19 različitih vrednosti, što bi
-predstavljalo jako lošu pokrivenost tabele.  Dodatno, vrlo često će
-rezultat biti 0 i uopšteno će biti više tablica koje su bliže
-"sredini" rezultatom, što opet dovodi do loše ravnomernosti
-raspoređivanja elemenata.
-
-Dakle ovako definisan metod za heš će davati dosta loše rezultate:
-
-```Java
-       public int hashCode() {
-               int rez = 0;
-               for (int i = 0; i < DIM; i++) {
-                       for (int j = 0; j < DIM; j++) {
-                               rez += tabla[i][j];
-                       }
-               }
-               return rez;
-       }
-```
-
-Elements    : 4424
-Percent full:   9.90 
-Chi square  : 87664.1198 
-'Columns'   : 101
-
-Elements    : 4424
-Percent full:   1.99 
-Chi square  : 454193.0719 
-'Columns'   : 503
-
-Elements    : 4424
-Percent full:   1.00 
-Chi square  : 904604.2717 
-'Columns'   : 997
-
-Efikasnost se u ovakvoj strukturi dobija davanjem "težine"
-elementima. Odnosno ne treba gledati jednako ako je "1" u gornjem
-levom uglu i u sredini. Npr u jednostavnijim varijantama se može zbir
-iz svakog reda množiti različitim brojem. Još bolji rezultati se
-dobijaju uvodjenjem još detaljnijih "težina": pri sumiranju reda
-množiti različito, ili čak imati specijalne proste brojeve za svako
-polje ili uvoditi dodatne operacije.
-
-Takodje treba uzeti u obzir i to da sabiranje pozitivnih i negativnih
-vrednosti može da dovede do "međusobnog poništavanja", te da nula
-vrednosti ne pomažu zbiru, naročito kad se uvedu težine koje se množe
-sa vrednostima polja. Jedan način da se ovo uradi u konkretnom slučaju
-je da se pri računanju heša vrednosti u matrici sabiraju sa 2, pa time
-imamo 1,2,3 umesto -1,0,1, što dovodi do veće različitosti.
-
-Kombinovanjem ovih ideja se mogu napraviti `hashCode` metodi koji će
-rezultovati sa hi kvadrat vrednostima manjim od broja podskupova
-("kolona"), što se ostavlja za samostalnu vežbu.
-
Svarog.pmf.uns.ac.rs/gitweb maintanance Doni Pracner