Programmazione Parametrica ( a.k.a. Generics )
Introduzione ai meccanismi e concetti della programmazione parametrica Generics e relationi di sottotipo wildcards generics e vincoli Implemendaizone di classi e metodi parametrici Supporto per i generics nella JVM
Programmazione polimorfa Polimorfo ~ multiforme, di molti tipi Programmazione polimorfa: creazione di costrutti (classi e metodi) che possono essere utilizzati in modo uniforme su dati di tipo diverso In Java, tradizionalmente ottenuta mediante i meccanismi di sottotipo ed ereditarietà Da Java 1.5. mediante i meccanismi di parametrizzazione di tipo (a.k.a. generics)
Variabili di Tipo Le variabili (o parametri) di tipo pemettono di creare astrazioni di tipo Classico caso di utilzzo nelle classi Container E = variabile di tipo astrae (e rappresenta) il tipo delle componenti public class ArrayList { public ArrayList() {... } public void add(E element) {... }... } Continua
Variabili di Tipo Possono essere istanziate con tipi classe o interfaccia Vincolo: tipi che istanziano variabili di tipo non possono essere primitivi (devono essere tipi riferimento) Classi wrapper utili allo scopo ArrayList ArrayList ArrayList // No! ArrayList
Variabili di tipo e controlli di tipo Utilizzare variabili di tipo nella programmazione permette maggiori controlli sulla correttezza dei tipi in fase di compilazione Aumenta quindi la solidità e robustezza del codice Continua
Variabili di tipo e controlli di tipo Un classico caso di utilizzo di containers Il cast è problematico, per vari motivi verboso, fonte di errori a run time Ma necessario per la compilazione e per localizzare leventuale errore a run time Continua List intList = new LinkedList(); intList.add(new Integer(0)); Integer x = (Integer) intList.iterator().next();
Variabili di tipo e controlli di tipo Container generici: più sintetici ed eleganti Compilatore può stabilire un invariante sugli elementi della lista garantire lassenza di errori a run-time in forza di quellinvariante. List intList = new LinkedList (); intList.add(new Integer(0)); Integer x = intList.iterator().next(); Continua
Variabili di tipo e controlli di tipo Ora non è possibile aggiungere una stringa ad intlist:List Le variabili di tipo rendono il codice parametrico più robusto e semplice da leggere e manutenere List intList = new LinkedList (); intList.add(new Integer(0)); Integer x = intList.iterator().next();
Classi parametriche: definizione // nulla di particolare, a parte i parametri // tra parentesi angolate public interface List { void add(E x); Iterator iterator(); } public interface Iterator { E next(); boolean hasNext(); } Un frammento delle interfacce List e Iterator nel package java.util.*
Classi parametriche: uso Quando utilizziamo un tipo parametrico, tutte le occorrenze dei parametri formali sono rimpiazzate dallargomento (parametro attuale) Meccanismo simile a quello del passaggio dei parametri in un metodo Diversi usi generano tipi diversi Ma... classi parametriche compilate una sola volta danno luogo ad un unico file.class
Sintassi: uso GenericClassName Esempio: ArrayList HashMap Scopo: Fornire tipo specifici per ciascuna delle variabili di tipo introdotte nella dichiarazione
public class Pair { public Pair(T firstElement, S secondElement) { first = firstElement; second = secondElement; } public T getFirst() { return first; } public S getSecond() { return second; } private T first; private S second; } Esempio: Pair Una semplice classe parametrica per rappresentare coppie di oggetti Continua
Esempio: Pair Una semplice classe parametrica per rappresentare coppie di oggetti: I metodi getFirst e getSecond restituiscono il primo e secondo elemento, con i tipi corrispondenti Pair result = new Pair ("Harry Hacker", harrysChecking); String name = result.getFirst(); BankAccount account = result.getSecond();
Variabili di tipo: convenzioni VariabileSignificato Inteso ETipo degli elementi in una collezione KTipo delle chiavi in una mappa VTipo dei valori in una mappa T,S,UTipi generici
Esempio: LinkedList public class LinkedList {... public E removeFirst() { if (first == null) throw new NoSuchElementException(); E element = first.data; first = first.next; return element; }... private Node first; private class Node { E data; Node next; } } Continua
Esempio: LinkedList private class Node { F data; Node next; } public class LinkedList {... public E removeFirst() { if (first == null) throw new NoSuchElementException(); E element = first.data; first = first.next; return element; }... private Node first; } Continua
Esempio: LinkedList Notiamo la struttura della classe ausiliaria che specifica la struttura dei nodi Se la classe è interna, come in questo caso, non serve alcun accorgimento allinterno di Node possiamo utilizzare il tipo E, il cui scope è tutta la classe Se invece la classe è esterna, dobbiamo renderla generica public class ListNode
File LinkedList.java 001: import java.util.NoSuchElementException; 002: 003: /** 004: A linked list is a sequence of nodes with efficient 005: element insertion and removal. This class 006: contains a subset of the methods of the standard 007: java.util.LinkedList class. 008: */ 009: public class LinkedList 010: { 011: /** 012: Constructs an empty linked list. 013: */ 014: public LinkedList() 015: { 016: first = null; 017: } 018: Continua
File LinkedList.java 019: /** 020: Returns the first element in the linked list. the first element in the linked list 022: */ 023: public E getFirst() 024: { 025: if (first == null) 026: throw new NoSuchElementException(); 027: return first.data; 028: } 029: 030: /** 031: Removes the first element in the linked list. the removed element 033: */ 034: public E removeFirst() 035: { Continua
File LinkedList.java 036: if (first == null) 037: throw new NoSuchElementException(); 038: E element = first.data; 039: first = first.next; 040: return element; 041: } 042: 043: /** 044: Adds an element to the front of the linked list. element the element to add 046: */ 047: public void addFirst(E element) 048: { 049: Node newNode = new Node(); 050: newNode.data = element; 051: newNode.next = first; 052: first = newNode; 053: } Continua
File LinkedList.java 054: 055: /** 056: Returns an iterator for iterating through this list. 057: 058: */ 059: public ListIterator listIterator() 060: { 061: return new LinkedListIterator(); 062: } 063: 064: private Node first; 065: 066: private class Node 067: { 068: E data; 069: Node next; 070: } 071: Continua
LinkedListIterator La definiamo come classe interna di LinkedList Implements linterfaccia ListIterator Ha accesso al campo first e alla classe interna Node
Classe LinkedListIterator 072: private class LinkedListIterator implements ListIterator 073: { 074: /** 075: Costruisce un iteratore posizionato sul primo 076: elemento della lista 077: */ 078: public LinkedListIterator() 079: { 080: last = null; // ultimo nodo visitato 081: previous = null; // precedente a position 082: } 083: Continua
LinkedListIterator – next() 084: /** 085: Posizione literatore sul prossimo. il prossimo elemento 087: */ 088: public E next() 089: { 090: if (!hasNext()) 091: throw new NoSuchElementException(); 092: previous = last; // Remember for remove 093: 094: if (last == null) 095: last = first; 096: else 097: last = last.next; 098: 099: return last.data; 100: } 101: 102: Continua
LinkedListIterator – hasNext() 102: /** 103: Testa se cè un elemento dopo lultimo 104: visitato. true se esiste un elemento dopo 106: lultimo visitato 107: */ 108: public boolean hasNext() 109: { 110: if (last == null) 111: return first != null; 112: else 113: return last.next != null; 114: } 115: 116: Continua
LinkedListIterator – add() 117: /** Aggiunge un elemento dopo last e sposta 118: literatore sul prossimo elemento. 119: */ 121: public void add(E element) 122: { 123: if (last == null) 124: { 125: addFirst(element); last = first; 127: } 128: else 129: { 130: Node newNode = new Node(); 131: newNode.data = element; 132: newNode.next = last.next;// (1) 133: last.next = newNode; // (2) 134: last = newNode; // (3) 135: } 136: previous = last; // (4) 137: } Continua
LinkedListIterator – add() last
LinkedListIterator – remove() 140: /** Rimuove lelemento puntato da last. Può essere 141: invocato solo dopo una chiamata a next() 142: */ 143: public void remove() 144: { 145: if (previous == last) 146: throw new IllegalStateException(); 147: 148: if (last == first) 149: { 150: removeFirst(); 151: } 152: else 153: { 154: previous.next = last.next; // (1) 155: } 156: last = previous; // (2) 157: } Continua
LinkedListIterator –remove() last
Classe LinkedListIterator 159: /** 160: Sets the last traversed element to a different 161: value. element the element to set 163: */ 164: public void set(E element) 165: { 166: if (position == null) 167: throw new NoSuchElementException(); 168: position.data = element; 169: } 170: 171: private Node position; 172: private Node previous; 173: } // end LinkedListIterator 174: } // end LinkedList
File ListIterator.java 01: /** 02: A list iterator allows access of a position in a linked 03: list. This interface contains a subset of the methods 04: of the standard java.util.ListIterator interface. The 05: methods for backward traversal are not included. 06: */ 07: public interface ListIterator 08: { 09: /** 10: Moves the iterator past the next element. the traversed element 12: */ 13: E next(); 14: 15: /** 16: Tests if there is an element after the iterator 17: position. Continua
File ListIterator.java true if there is an element after the iterator 19: position 20: */ 21: boolean hasNext(); 22: 23: /** 24: Adds an element before the iterator position 25: and moves the iterator past the inserted element. element the element to add 27: */ 28: void add(E element); 29: 30: /** 31: Removes the last traversed element. This method may 32: only be called after a call to the next() method. 33: */ Continua
File ListIterator.java 34: void remove(); 35: 36: /** 37: Sets the last traversed element to a different 38: value. element the element to set 40: */ 41: void set(E element); 42: }
Generics e sottotipi Consideriamo La prima istruzione è sicuramente legale, la seconda è più delicata … Number è una classe astratta che ha Integer, Double e altre classi wrapper come sottotipi Per capire se la seconda istruzione sia da accettare come legale continuiamo con lesempio … Continua List li = new ArrayList (); List ln = li;
Generics e sottotipi Come si vede abbiamo un problema nella terza istruzione inseriamo un Double (via auto- boxing, ricordate?) nella quarta estraiamo un Integer ! Il vero problema è nella seconda istruzione soluzione: errore di compilazione per lassegnamento al secondo Continua List li = new ArrayList (); List ln = li; ln.add(3.14); Integer i = li.get(0); // uh oh...
Generics e sottotipi In generale, dati due tipi A e B, ed tipo generico C abbiamo che: Quindi, per le stesse ragioni di prima Continua A B non implica C C Set non è sottotipo di Set
Generics e sottotipi Le limitazione sulle relazioni di sottotipo sono contro-intuitive uno degli aspetti più complessi dei generics Non solo … sono anche decisamente troppo restrittive illustriamo con un esempio Continua
Generics e sottotipi Un metodo che stampa gli elementi di una collezione Versione tradizionale Continua void printCollection(Collection c) { Iterator i = c.iterator(); for (k = 0; k < c.size(); k++) { System.out.println(i.next()); }
Generics e sottotipi Stampa degli elementi di una collezione Versione generics: primo tentativo utile solo per Collection non per qualsiasi collezione Collection non è il supertipo di tutte le collezioni Continua void printCollection(Collection c) { for (Object e:c) System.out.println(e); }
Wildcards Stampa degli elementi di una collezione Versione generics: secondo tentativo Collection è il supertipo di tutte le Collections la wildcard ? indica un qualche tipo, non specificato Continua void printCollection(Collection c) { for (Object e:c) System.out.println(e); }
Wildcards Possiamo estrarre gli elementi di c al tipo Object Corretto perché, qualunque sia il loro vero tipo, sicuramente è sottotipo di Object Continua void printCollection(Collection c) { for (Object e:c) System.out.println(e); }
Wildcards Continua Collection c = new ArrayList (); c.add(new String()); // errore di compilazione! Daltra parte … visto che non sappiamo esattamente quale tipo indica ?, non possiamo assegnare valori
Date un esempio di codice che causerebbe errore in esecuzione se permettessimo di aggiungere elementi a Collection Domanda
Lultima istruzione invocherebbe intValue() sul primo elemento di ci ma quellelemento ha tipo String … In realtà il compilatore anticipa lerrore, segnalando il problema sulla add() Risposta Collection ci = new ArrayList ; Colletion c = ci; c.add(a string); ci.get(0).intValue();
Wilcards con vincoli ( bounded ) Shapes : un esempio classico public abstract class Shape { public abstract void draw(Graphics g); } public class Circle extends Shape { private int x, y, radius; public void draw(Graphics g) {... } } public class Rectangle extends Shape { private int x, y, width, height; public void draw(Graphics g) {... } } Continua
Wilcards con vincoli ( bounded ) Graphics e il metodo draw() Solito problema: drawAll() non può essere invocato su una List public class Graphics { // disegna una shape public void draw(Shape s) { s.draw(this); } // disegna tutte le shapes di una lista public void drawAll(List shapes) { for (Shape s:shapes) s.draw(this) }... } Continua
Bounded Wilcards Quello che ci serve è un metodo che accetti liste di qualunque (sotto) tipo di Shape List bounded wildcard indica un tipo sconosciuto, sottotipo di Shape il bound può essere qualunque tipo riferimento (classe o interfaccia) Ora il metodo ha la flessibilità necessaria e desiderata void drawAll(List shapes) {... } Continua
Bounded Wilcards Graphics e il metodo draw() public class Graphics { // disegna una shape public void draw(Shape s) { s.draw(this); } // disegna tutte le shapes di una lista public void drawAll(List shapes) { for (Shape s:shapes) s.draw(this) }... } Continua
Bounded Wilcards Daltra parte, cè sempre un prezzo da pagare Il solito vincolo … Non possiamo modificare strutture con questi tipi [ perché? ] void addRectangle(List shapes) { // errore di compilazione shapes.add(new Rectangle()); }
Metodi che dipendono da una variabile di tipo Possono essere definiti allinterno di qualunque classe, generica o meno N.B. Evitiamo List perché List renderebbe il metodo poco utile (non utilizzabie su liste arbitrarie) /* trasforma un array in una lista, copiando * tutti gli elementi di a in l */ static void array2List(Object[] a, List l){... } Metodi Generici Continua
Metodi Generici Al solito però non possiamo aggiungere elementi ad una struttura (o modificare) con elementi di tipo wildcard /* trasforma un array in una lista, copiando * tutti gli elementi di a in l */ static void array2List(Object[] a, List l) { for (Object o : a) l.add(o) // compiler error } Continua
Metodi Generici Soluzione: rendiamo il metodo parametrico possiamo invocare questo metodo con una qualunque lista il cui tipo sia supertipo del tipo base dellarray purché sia un tipo riferimento /* trasforma un array in una lista, copiando * tutti gli elementi di a in l */ static void array2List(T[] a, List l) { for (T o : a) l.add(o) }
Invocazione di metodi generici Nellinvocazione di un metodo generico non è necessario passare largomento di tipo il compilatore inferisce il tipo, se esiste, dai tipi degli argomenti del metodo
Invocazione di metodi generici Continua Object[] oa = new Object[100]; Collection co = new ArrayList (); fromArrayToCollection(oa, co); // T = Object (inferito) String[] sa = new String[100]; Collection cs = new ArrayList (); fromArrayToCollection(sa, cs); // T = String (inferito) fromArrayToCollection(sa, co); // T = Object (inferito) Integer[] ia = new Integer[100]; Float[] fa = new Float[100]; Number[] na = new Number[100]; Collection cn = new ArrayList (); fromArrayToCollection(ia, cn); // T = Number (inferito) fromArrayToCollection(fa, cn); // T = Number (inferito) fromArrayToCollection(na, cn); // T = Number (inferito) fromArrayToCollection(na, co); // T = Object (inferito) fromArrayToCollection(na, cs); // compiler error
Wildarcds vs variabili di tipo Ci sono situazioni in cui è possibili usare equivalentemente wildcards e variabili di tipo. Nella libreria Collection troviamo Continua interface Collection { public boolean containsAll(Collection c); public boolean addAll(Collection c);... }
Wildarcds vs variabili di tipo Queste specifiche possono essere espresse equivalentemente con metodi parametrici Il secondo metodo è parametrico in qualunque sottotipo di E i bounds si possono utilizzare anche con variabili, non solo con wildcards Continua interface Collection { public boolean containsAll(Collection c); public boolean addAll(Collection c);... }
Wildcards e variabili di tipo possono coesistere Notiamo la dipendenza tra i tipi dei due parametri: il tipo della sorgente deve essere un sottotipo del tipo della destinazione Wildarcds vs variabili di tipo Continua interface Collection { public static void copy(List dest, List src)... }
Potremmo analogamente riformulare in modo da evitare le wildcards Come scegliere tra le due soluzioni? Wildarcds vs variabili di tipo Continua interface Collection { public static void copy( dest, List src)... }
In generale, preferiamo le wildcards quando entrambe le soluzioni sono possibili Possiamo darci la seguente rule of thumb se una variabile di tipo ha una unica occorrenza nella specifica di un metodo e il tipo non è il target di un operazione di modifica utilizziamo una wildcard al posto della variabile Wildarcds vs variabili di tipo
Variabili di Tipo e Bounds Abbiamo visto che possiamo definire bounds anche per variabili di tipo (non solo wildcards) Un caso paradigmatico public static > T max(Collection coll) { T candidate = coll.iterator().next(); for (T e : coll) if candidate.compareTo(e) < 0) candidate = e; return candidate; }
Variabili di Tipo e Bounds Il bound su una variabile impone vincoli sulla variabile, determinando quali metodi possono essere utilizzati su valori del tipo variabile Qui il bound è ricorsivo: informa che i valori con cui operiamo forniscono un metodo compareTo() che gli argomenti del metodo devono essere dello stesso tipo dei valori public static > T max(T max(List coll)
Generics e erasure I tipi generici sono significativi a compile-time La JVM opera invece con tipi raw Il tipo raw è ottenuto da un tipo generico mediante un processo detto erasure che rimuove le variabili di tipo il bycode generato da un tipo generico è lo stesso che viene generato dal corrispondente tipo raw.
Generics e erasure Generano lo stesso bytecode List words = new ArrayList (); words.add(hi); words.add(there); String welcome = words.get(0) + words.get(1); List words = new ArrayList(); words.add(hi); words.add(there); String welcome = (String)words.get(0) + (String)words.get(1);
Generics e erasure Cast-iron guarantee i cast impliciti che vengono aggiunti dalla compilazione di codice generico non falliscono mai.
Constraining Type Variables Very occasionally, you need to supply two or more type bounds extends, when applied to type variables, actually means "extends or implements" The bounds can be either classes or interfaces Type variable can be replaced with a class or interface type
Raw Types For example, generic class Pair turns into the following raw class: public class Pair { public Pair(Object firstElement, Object secondElement) { first = firstElement; second = secondElement; } public Object getFirst() { return first; } public Object getSecond() { return second; } private Object first; private Object second; }
Raw Types Knowing about raw types helps you understand limitations of Java generics For example, you cannot replace type variables with primitive types
Self Check 9.What is the erasure of the print method in Section 22.3? 10.What is the raw type of the LinkedList class in Section 22.2?
Answers 10.The LinkedList class of Chapter 20. public static void print(Object[] a) { for (Object e : a) System.out.print(e + " "); System.out.println(); }
Chapter 21 Advanced Data Structures
Chapter Goals To learn about the set and map data types To understand the implementation of hash tables To be able to program hash functions To learn about binary trees To be able to use tree sets and tree maps Continued
Chapter Goals To become familiar with the heap data structure To learn how to implement the priority queue data type To understand how to use heaps for sorting
Sets Set: unordered collection of distinct elements Elements can be added, located, and removed Sets don't have duplicates
A Set of Printers Figure 1: A Set of Printers
Fundamental Operations on a Set Adding an element Adding an element has no effect if the element is already in the set Removing an element Attempting to remove an element that isn't in the set is silently ignored Containment testing (does the set contain a given object?) Listing all elements (in arbitrary order)
Sets We could use a linked list to implement a set Adding, removing, and containment testing would be relatively slow There are data structures that can handle these operations much more quickly Hash tables Trees Continued
Sets Standard Java library provides set implementations based on both data structures HashSet TreeSet Both of these data structures implement the Set interface
Set Classes and Interface in the Standard Library Figure 2: Set Classes and Interfaces in the Standard Library
Iterator Use an iterator to visit all elements in a set A set iterator does not visit the elements in the order in which they were inserted An element can not be added to a set at an iterator position A set element can be removed at an iterator position
Code for Creating and Using a Hash Set //Creating a hash set Set names = new HashSet (); //Adding an element names.add("Romeo"); //Removing an element names.remove("Juliet"); //Is element in set if (names.contains("Juliet") {...}
Listing All Elements with an Iterator Iterator iter = names.iterator(); while (iter.hasNext()) { String name = iter.next(); Do something with name } // Or, using the "for each" loop for (String name : names) { Do something with name }
File SetTester.java 01: import java.util.HashSet; 02: import java.util.Iterator; 03: import java.util.Scanner; 04: import java.util.Set; 05: 06: 07: /** 08: This program demonstrates a set of strings. The user 09: can add and remove strings. 10: */ 11: public class SetTester 12: { 13: public static void main(String[] args) 14: { 15: Set names = new HashSet (); 16: Scanner in = new Scanner(System.in); 17: Continued
File SetTester.java 18: boolean done = false; 19: while (!done) 20: { 21: System.out.print("Add name, Q when done: "); 22: String input = in.next(); 23: if (input.equalsIgnoreCase("Q")) 24: done = true; 25: else 26: { 27: names.add(input); 28: print(names); 29: } 30: } 31: 32: done = false; 33: while (!done) 34: { Continued
File SetTester.java 35: System.out.println("Remove name, Q when done"); 36: String input = in.next(); 37: if (input.equalsIgnoreCase("Q")) 38: done = true; 39: else 40: { 41: names.remove(input); 42: print(names); 43: } 44: } 45: } 46: 47: /** 48: Prints the contents of a set of strings. s a set of strings 50: */ 51: private static void print(Set s) 52: { Continued
File SetTester.java 53: System.out.print("{ "); 54: for (String element : s) 55: { 56: System.out.print(element); 57: System.out.print(" "); 58: } 59: System.out.println("}"); 60: } 61: } 62: 63: Continued
File SetTester.java Output Add name, Q when done: Dick { Dick } Add name, Q when done: Tom { Tom Dick } Add name, Q when done: Harry { Harry Tom Dick } Add name, Q when done: Tom { Harry Tom Dick } Add name, Q when done: Q Remove name, Q when done: Tom { Harry Dick } Remove name, Q when done: Jerry { Harry Dick } Remove name, Q when done: Q
Self Test 1.Arrays and lists remember the order in which you added elements; sets do not. Why would you want to use a set instead of an array or list? 2.Why are set iterators different from list iterators?
Answers 1.Efficient set implementations can quickly test whether a given element is a member of the set. 2.Sets do not have an ordering, so it doesn't make sense to add an element at a particular iterator position, or to traverse a set backwards.
Maps A map keeps associations between key and value objects Mathematically speaking, a map is a function from one set, the key set, to another set, the value set Every key in a map has a unique value A value may be associated with several keys Classes that implement the Map interface HashMap TreeMap
An Example of a Map Figure 3: An Example of a Map
Map Classes and Interfaces Figure 4: Map Classes and Interfaces in the Standard Library
Code for Creating and Using a HashMap //Changing an existing association favoriteColor.put("Juliet",Color.RED); //Removing a key and its associated value favoriteColors.remove("Juliet");
Code for Creating and Using a HashMap //Creating a HashMap Map favoriteColors = new HashMap (); //Adding an association favoriteColors.put("Juliet", Color.PINK); //Changing an existing association favoriteColor.put("Juliet",Color.RED); Continued
Code for Creating and Using a HashMap //Getting the value associated with a key Color julietsFavoriteColor = favoriteColors.get("Juliet"); //Removing a key and its associated value favoriteColors.remove("Juliet");
Printing Key/Value Pairs Set keySet = m.keySet(); for (String key : keySet) { Color value = m.get(key); System.out.println(key + "->" + value); }
File MapTester.java 01: import java.awt.Color; 02: import java.util.HashMap; 03: import java.util.Iterator; 04: import java.util.Map; 05: import java.util.Set; 06: 07: /** 08: This program tests a map that maps names to colors. 09: */ 10: public class MapTester 11: { 12: public static void main(String[] args) 13: { 14: Map favoriteColors 15: = new HashMap (); 16: favoriteColors.put("Juliet", Color.pink); 17: favoriteColors.put("Romeo", Color.green); Continued
File MapTester.java 18: favoriteColors.put("Adam", Color.blue); 19: favoriteColors.put("Eve", Color.pink); 20: 21: Set keySet = favoriteColors.keySet(); 22: for (String key : keySet) 23: { 24: Color value = favoriteColors.get(key); 25: System.out.println(key + "->" + value); 26: } 27: } 28: } Continued
File MapTester.java Output Romeo->java.awt.Color[r=0,g=255,b=0] Eve->java.awt.Color[r=255,g=175,b=175] Adam->java.awt.Color[r=0,g=0,b=255] Juliet->java.awt.Color[r=255,g=175,b=175]
Self Check 3.What is the difference between a set and a map? 4.Why is the collection of the keys of a map a set?
Answers 3.A set stores elements. A map stores associations between keys and values. 4.The ordering does not matter, and you cannot have duplicates.
Hash Tables Hashing can be used to find elements in a data structure quickly without making a linear search A hash table can be used to implement sets and maps A hash function computes an integer value (called the hash code) from an object Continued
Hash Tables A good hash function minimizes collisions– identical hash codes for different objects To compute the hash code of object x : int h = x.hashCode();
Sample Strings and Their Hash Codes StringHash Code "Adam" "Eve"70068 "Harry" "Jim"74478 "Joe"74676 "Juliet" "Katherine" "Sue"83491
Simplistic Implementation of a Hash Table To implement Generate hash codes for objects Make an array Insert each object at the location of its hash code To test if an object is contained in the set Compute its hash code Check if the array position with that hash code is already occupied
Simplistic Implementation of a Hash Table Figure 5: A Simplistic Implementation of a Hash Table
Problems with Simplistic Implementation It is not possible to allocate an array that is large enough to hold all possible integer index positions It is possible for two different objects to have the same hash code
Solutions Pick a reasonable array size and reduce the hash codes to fall inside the array When elements have the same hash code: Use a node sequence to store multiple objects in the same array position These node sequences are called buckets int h = x.hashCode(); if (h < 0) h = -h; h = h % size;
Hash Table with Buckets to Store Elements with Same Hash Code Figure 6: A Hash Table with Buckets to Store Elements with Same Hash Code
Algorithm for Finding an Object x in a Hash Table Get the index h into the hash table Compute the hash code Reduce it modulo the table size Iterate through the elements of the bucket at position h For each element of the bucket, check whether it is equal to x If a match is found among the elements of that bucket, then x is in the set Otherwise, x is not in the set
Hash Tables A hash table can be implemented as an array of buckets Buckets are sequences of nodes that hold elements with the same hash code If there are few collisions, then adding, locating, and removing hash table elements takes constant time Big-Oh notation: O(1) Continued
Hash Tables For this algorithm to be effective, the bucket sizes must be small The table size should be a prime number larger than the expected number of elements An excess capacity of 30% is typically recommended
Hash Tables Adding an element: simple extension of the algorithm for finding an object Compute the hash code to locate the bucket in which the element should be inserted Try finding the object in that bucket If it is already present, do nothing; otherwise, insert it Continued
Hash Tables Removing an element is equally simple Compute the hash code to locate the bucket in which the element should be inserted Try finding the object in that bucket If it is present, remove it; otherwise, do nothing If there are few collisions, adding or removing takes O(1) time
File HashSet.java 001: import java.util.AbstractSet; 002: import java.util.Iterator; 003: import java.util.NoSuchElementException; 004: 005: /** 006: A hash set stores an unordered collection of objects, using 007: a hash table. 008: */ 009: public class HashSet extends AbstractSet 010: { 011: /** 012: Constructs a hash table. bucketsLength the length of the buckets array 014: */ 015: public HashSet(int bucketsLength) 016: { Continued
File HashSet.java 017: buckets = new Node[bucketsLength]; 018: size = 0; 019: } 020: 021: /** 022: Tests for set membership. x an object true if x is an element of this set 025: */ 026: public boolean contains(Object x) 027: { 028: int h = x.hashCode(); 029: if (h < 0) h = -h; 030: h = h % buckets.length; 031: 032: Node current = buckets[h]; 033: while (current != null) 034: { Continued
File HashSet.java 035: if (current.data.equals(x)) return true; 036: current = current.next; 037: } 038: return false; 039: } 040: 041: /** 042: Adds an element to this set. x an object true if x is a new object, false if x was 045: already in the set 046: */ 047: public boolean add(Object x) 048: { 049: int h = x.hashCode(); 050: if (h < 0) h = -h; 051: h = h % buckets.length; 052: Continued
File HashSet.java 053: Node current = buckets[h]; 054: while (current != null) 055: { 056: if (current.data.equals(x)) 057: return false; // Already in the set 058: current = current.next; 059: } 060: Node newNode = new Node(); 061: newNode.data = x; 062: newNode.next = buckets[h]; 063: buckets[h] = newNode; 064: size++; 065: return true; 066: } 067: Continued
File HashSet.java 068: /** 069: Removes an object from this set. x an object true if x was removed from this set, false 072: if x was not an element of this set 073: */ 074: public boolean remove(Object x) 075: { 076: int h = x.hashCode(); 077: if (h < 0) h = -h; 078: h = h % buckets.length; 079: 080: Node current = buckets[h]; 081: Node previous = null; 082: while (current != null) 083: { 084: if (current.data.equals(x)) 085: { Continued
File HashSet.java 086: if (previous == null) buckets[h] = current.next; 087: else previous.next = current.next; 088: size--; 089: return true; 090: } 091: previous = current; 092: current = current.next; 093: } 094: return false; 095: } 096: 097: /** 098: Returns an iterator that traverses the elements of this set. a hash set iterator 100: */ 101: public Iterator iterator() 102: { 103: return new HashSetIterator(); 104: } Continued
File HashSet.java 105: 106: /** 107: Gets the number of elements in this set. the number of elements 109: */ 110: public int size() 111: { 112: return size; 113: } 114: 115: private Node[] buckets; 116: private int size; 117: 118: private class Node 119: { 120: public Object data; 121: public Node next; 122: } 123: Continued
File HashSet.java 124: private class HashSetIterator implements Iterator 125: { 126: /** 127: Constructs a hash set iterator that points to the 128: first element of the hash set. 129: */ 130: public HashSetIterator() 131: { 132: current = null; 133: bucket = -1; 134: previous = null; 135: previousBucket = -1; 136: } 137: 138: public boolean hasNext() 139: { 140: if (current != null && current.next != null) 141: return true; Continued
File HashSet.java 142: for (int b = bucket + 1; b < buckets.length; b++) 143: if (buckets[b] != null) return true; 144: return false; 145: } 146: 147: public Object next() 148: { 149: previous = current; 150: previousBucket = bucket; 151: if (current == null || current.next == null) 152: { 153: // Move to next bucket 154: bucket++; 155: 156: while (bucket < buckets.length 157: && buckets[bucket] == null) 158: bucket++; Continued
File HashSet.java 159: if (bucket < buckets.length) 160: current = buckets[bucket]; 161: else 162: throw new NoSuchElementException(); 163: } 164: else // Move to next element in bucket 165: current = current.next; 166: return current.data; 167: } 168: 169: public void remove() 170: { 171: if (previous != null && previous.next == current) 172: previous.next = current.next; 173: else if (previousBucket < bucket) 174: buckets[bucket] = current.next; 175: else 176: throw new IllegalStateException(); Continued
File HashSet.java 177: current = previous; 178: bucket = previousBucket; 179: } 180: 181: private int bucket; 182: private Node current; 183: private int previousBucket; 184: private Node previous; 185: } 186: }
File SetTester.java 01: import java.util.Iterator; 02: import java.util.Set; 03: 04: /** 05: This program tests the hash set class. 06: */ 07: public class SetTester 08: { 09: public static void main(String[] args) 10: { 11: HashSet names = new HashSet(101); // 101 is a prime 12: 13: names.add("Sue"); 14: names.add("Harry"); 15: names.add("Nina"); 16: names.add("Susannah"); 17: names.add("Larry"); 18: names.add("Eve"); Continued
File SetTester.java 19: names.add("Sarah"); 20: names.add("Adam"); 21: names.add("Tony"); 22: names.add("Katherine"); 23: names.add("Juliet"); 24: names.add("Romeo"); 25: names.remove("Romeo"); 26: names.remove("George"); 27: 28: Iterator iter = names.iterator(); 29: while (iter.hasNext()) 30: System.out.println(iter.next()); 31: } 32: } Continued
File SetTester.java Output Harry Sue Nina Susannah Larry Eve Sarah Adam Juliet Katherine Tony
Self Check 5.If a hash function returns 0 for all values, will the HashSet work correctly? 6.What does the hasNext method of the HashSetIterator do when it has reached the end of a bucket?
Answers 5.Yes, the hash set will work correctly. All elements will be inserted into a single bucket. 6.It locates the next bucket in the bucket array and points to its first element.