gitweb on Svarog
projekti pod git sistemom za održavanje verzija -- projects under the git version control systemdiff --git a/Hash/OHashSet/hash.txt b/Hash/OHashSet/hash.txt
--- /dev/null
+++ b/Hash/OHashSet/hash.txt
@@ -0,0 +1,571 @@
+% 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.
+