Esercizio Java

di il
11 risposte

Esercizio Java

Come posso modificare questa mia classe in modo tale che quando inserirò in un HashSet new MyList(1, 2, 42) e new MyList(1, 2, 42) causerà solo l'inserimento della prima lista??

public class MyList
{
	private Elemento first;
	private class Elemento
	{
		private int val;
		private Elemento next;
		public Elemento(int val, Elemento next) { this.val = val; this.next = next; }
	}
	public MyList(int... interi) { for (int k : interi) first = new Elemento(k, first); }
}
Ciao e buona giornata

11 Risposte

  • Re: Esercizio Java

    Paolovox ha scritto:


    Come posso modificare questa mia classe in modo tale che quando inserirò in un HashSet new MyList(1, 2, 42) e new MyList(1, 2, 42) causerà solo l'inserimento della prima lista??
    Se oggetti MyList devono essere contenuti in un HashSet, siccome questa collezione è basata su una hash-table, allora la tua classe MyList deve ridefinire correttamente equals(Object) e hashCode() in modo da rispettare il "contratto" che esiste tra questi due metodi.
  • Re: Esercizio Java

    Per poter fare questo devo prima rendere la classe Iterable (come dice la traccia), però ho un piccolo problema. Penso che così me la concateni nel verso contrario, cioè il primo elemento verrà puntato dal successivo e così via. Credo di doverlo fare nell'ordine giusto.
    Poi dopo implemento l'@Override di equals(MyList l).
    public class MyList implements Iterable<Integer>{
    
    	private Elemento first;
    		
    	private class Elemento
    		{
    			private int val;
    			private Elemento next;
    			public Elemento(int val, Elemento next) { this.val = val; this.next = next; }
    		}
    	
    	public MyList(int... interi) { 
    		for (int k : interi) first = new Elemento(k, first); 
    	}
    	
    	
    	public Iterator<Integer> iterator(){
    		return new Iterator<Integer>(){
    			
    			private Elemento current = first;
    
    			@Override
    			public boolean hasNext() {
    				return current != null;
    			}
    
    			@Override
    			public Integer next() {
    				if(!hasNext()) return null;
    				Integer v = current.val;
    				current = current.next;
    				return v;
    			}	
    		};
    	}
    		
    	public static void main(String[] args) {
    		MyList l1 = new MyList(1, 2, 42);
    		MyList l2 = new MyList(1, 2, 42);
    
    	}
    
    }
  • Re: Esercizio Java

    Ecco l'override del metodo equals, e nonostante sia funzionante i due oggetti MyList nell'HashSet compaiono entrambi. Ora devo riscrivere hashCode. L'hashCode sarebbe l'identificatore univoco assegnato ad un qualsiasi oggetto?
    @Override
    	public boolean equals(Object l){
    		if(  l == null || !(l.getClass() == MyList.class) ) return false;
    		
    		Iterator i1 = this.iterator();
    		Iterator i2 = ((MyList) l).iterator();
    		
    		while(i1.hasNext() || i2.hasNext()){
    			if ( i1.next() == null || i2.next() == null ) return false;
    			if( !(i1.next() == i2.next())) return false;
    		}
    		return true;
    	}
    Penso di aver capito. Bisogna trovare un modo per generare, in base alla costruzione dell'oggeto, un hashCode dipendente dalla creazione ed eventuali modifiche, così quando costruisco oggetti simili, mi genera lo stesso hash code.
    Per fare una prova banale ho fatto generare dall'hash code 0 ed il risultato è che nell'HashSet me ne compare solo uno e quindi è ok.
    Però la comparazione con "==" mi ritorna false, mentre l'equals true. Perchè?
  • Re: Esercizio Java

    Paolovox ha scritto:


    Per poter fare questo devo prima rendere la classe Iterable (come dice la traccia)
    Ok, comunque equals/hashCode di per sé non hanno direttamente a che fare con Iterable (ma è chiaro che se implementi un iteratore, poi il confronto tra liste viene chiaramente più facile ).

    Paolovox ha scritto:


    Penso che così me la concateni nel verso contrario, cioè il primo elemento verrà puntato dal successivo e così via. Credo di doverlo fare nell'ordine giusto.
    Il punto è che per come "incateni" gli Elemento nel costruttore, l'ultimo int del vararg (che è un array) è il primo, puntato da first.
    Quindi devi accodare, per ciascun int da inserire:
    - scansioni la lista fino al fondo.
    oppure
    - tieni anche il riferimento al last, oltre che al first.


    Piccola nota sul next(): se non ci sono più elementi e si invoca un next(), deve lanciare NoSuchElementException.

    Paolovox ha scritto:


    Poi dopo implemento l'@Override di equals(MyList l).
    equals(Object) ... non equals(MyList)
  • Re: Esercizio Java

    Paolovox ha scritto:


    Ecco l'override del metodo equals, e nonostante sia funzionante
    No, non è corretto. Osserva bene come i next "avanzano" nelle due liste.

    Paolovox ha scritto:


    i due oggetti MyList nell'HashSet compaiono entrambi.
    Serve infatti anche hashCode ridefinito.

    Paolovox ha scritto:


    L'hashCode sarebbe l'identificatore univoco assegnato ad un qualsiasi oggetto?
    "univoco" NO. L'obiettivo di hashCode è quello di fornire un valore che sia il più possibile distinto per diversi oggetti. Le collisioni di hashCode ci possono essere ma meno ce ne sono e meglio è.

    Paolovox ha scritto:


    Penso di aver capito. Bisogna trovare un modo per generare, in base alla costruzione dell'oggeto, un hashCode dipendente dalla creazione ed eventuali modifiche, così quando costruisco oggetti simili, mi genera lo stesso hash code.
    Pensa: la somma dei valori sarebbe un buon hashCode? Nì. Cioè sì, sicuramente meglio che es. restituire un valore fisso (leggi punto sotto) o la somma di solo alcuni valori. Ma inferiore ad altre tecniche.

    Con solo la somma basilare, due liste con gli stessi elementi ma in posizioni diverse, danno lo stesso hashCode = collisione.
    Generalmente è meglio dare un "peso" ai valori.

    Paolovox ha scritto:


    Per fare una prova banale ho fatto generare dall'hash code 0 ed il risultato è che nell'HashSet me ne compare solo uno e quindi è ok.
    Avere un hashCode che restituisce un valore fisso, es. 0 o 10, o quello che vuoi, è assolutamente legale per il contratto tra equals e hashCode. Ma è terribilmente inefficiente, prova a pensare perché. E quindi non va bene in generale.
  • Re: Esercizio Java

    Si ti ringrazio per la puntualizzazione ed ecco l'equals e come strategia per l'hash code, per evitare collisioni, durante la costruzioni dell'oggetto MyList mi genera l'hash code moltiplicando l'indice+1 dell'array * il valore dell'elemento. Prima l'hashCode=0 era solo per una prova a bruciapelo



    Hash Code generato al momento dell'inizializzazione
    
    for(int i=0; i<interi.length ; i++){
    			first = new Elemento(interi[i],first);
    			int x = i;
    			hash+=(x+1)*interi[i];
    		}
    

    Override equals e hashCode
    
    @Override
    	public boolean equals(Object l){
    		
    		if(l == null || !(l.getClass() == MyList.class)) return false;
    		
    		Iterator i1 = this.iterator();
    		Iterator i2 = ((MyList) l).iterator();
    		
    		
    		while(i1.hasNext() && i2.hasNext()){
    			Integer e1 = (Integer) i1.next();
    			Integer e2 = (Integer) i2.next();
    			if( !(e1 == e2) ) return false;
    			}
    		
    		if(i1.hasNext() || i2.hasNext()) return false;
    			
    		return true;
    	}
    	
    	@Override
    	public int hashCode(){
    		return hash;
    	}
    
    Costruttore che concatena la lista nell'ordine dato, scansionandola ogni volta.
    
    public MyList(int... interi) {
    		
    		for ( int i =0 ; i<interi.length ; i++){
    			
    			if(first == null) {
    				first = new Elemento(interi[i],null); continue;
    			}
    			Elemento l = first;
    			
    			while(l.next!=null){
    				l = l.next;
    			}
    			l.next = new Elemento(interi[i], null);				
    		}
    	}
    
    Sarebbe più efficiente mantenere un Elemento last anzichè spendere un O(n) ogni volta che inseriamo un elemento.
  • Re: Esercizio Java

    Paolovox ha scritto:


    come strategia per l'hash code, per evitare collisioni, durante la costruzioni dell'oggetto MyList mi genera l'hash code moltiplicando l'indice+1 dell'array * il valore dell'elemento.
    Precisazione: le "collisioni" ci possono generalmente sempre essere. L'obiettivo qui non è impedirle del tutto ma renderle meno probabili.
    Comunque la tua implementazione, come algoritmo, va bene. Dopotutto è un esercizio ... non stiamo creando razzi per la NASA ...

    Tieni però presente una cosa, detta in generale: se un oggetto è immutabile allora il hashCode lo si può computare alla costruzione dell'oggetto oppure in modo "lazy", ovvero solo alla prima invocazione di hashCode() (come fa la classe String).
    Se invece è mutabile, si possono trovare varie ottimizzazioni ma se la computazione non è "costosa", meglio farla ad ogni richiesta.

    Paolovox ha scritto:


    
    @Override
    	public boolean equals(Object l){
    		
    		if(!(l.getClass() == MyList.class) || l == null) return false;
    		
    		Iterator i1 = this.iterator();
    		Iterator i2 = ((MyList) l).iterator();
    		
    		
    		while(i1.hasNext() && i2.hasNext()){
    			Integer e1 = (Integer) i1.next();
    			Integer e2 = (Integer) i2.next();
    			if( !(e1 == e2) ) return false;
    			}
    		
    		if(i1.hasNext() || i2.hasNext()) return false;
    			
    		return true;
    	}
    
    Mi pare meglio di prima. Comunque visto che il Iterable (e Iterator restituito) sono parametrizzati, allora sfrutta bene i generics.

    Paolovox ha scritto:


    Sarebbe più efficiente mantenere un Elemento last anzichè spendere un O(n) ogni volta che inseriamo un elemento.
    Ovviamente!
  • Re: Esercizio Java

    Paolovox ha scritto:


    Integer e1 = (Integer) i1.next();
    Integer e2 = (Integer) i2.next();
    if( !(e1 == e2) ) return false;
    Mi era sfuggito prima: e1 == e2 è un confronto tra reference!! Non sul contenuto dei due Integer.
  • Re: Esercizio Java

    Si (grave) compara gli indirizzi delle variabili che puntano agli oggetti nello Heap, e non il valore effettivo dell'oggetto. Però la JVM è intelligente e spesso fa puntare due variabili allo stesso oggetto nel caso si riferiscano a due oggetti uguali. Questo è un altro arcano per me. Se poi da un riferimento lo modifichiamo?

    Ho capito l'inefficienza delle collisioni.
    In caso di collisioni risulterebbe inefficiente dato che non si avrebbe più un tempo O(1) costante per accedere ad un oggetto, ma quando più oggetti collidono vengono messi in una lista puntata dalla posizione dell'hash code nella tabella hash, quindi va scandita la lista per trovare l'oggetto utilizzando ogni volta il metodo equals.
    L'hash code viene utilizzato solo per memorizzare oggetti nelle Hash Table?

    Giusto con i generics potrei dichiarare la mia classe:
    public class MyList<Elemento> implements Iterable<Elemento>
    e utilizzare nell'Iterator solo Elemento.

    Mi sto scervellando. Ok se un oggetto è immutabile è comprensibile generare una sola volta l'hash code, dato che non cambierà stato e sarà thread safe. Ma se un oggetto è mutabile perchè ad ogni mutazione generarne uno diverso? Il precedente non potrebbe andare bene?


    Per completare:

    Costruttore che utilizza l'Elemento first e last per linkare gli elementi.
    
    public MyList(int... interi) {
    		
    		for ( int i =0 ; i<interi.length ; i++){
    			
    			int x = i;
    			hash+= ++x * interi[i];
    			
    			if(first == null) {
    				first = new Elemento(interi[i],null); continue;
    			}
    			
    			Elemento l;
    			if(first.next == null){
    				l = first;
    			}
    			else l=last;
    			
    			last = new Elemento(interi[i],null);
    			
    			l.next = last;					
    		}
    	}
    
    Ti ringrazio per tutta l'attenzione che mi hai dato. Ho imparato più stasera in un paio di orette che in un paio di settimane.
    E ho visto dal tuo sito personale che sei un javista d'onore.
  • Re: Esercizio Java

    Paolovox ha scritto:


    Però la JVM è intelligente e spesso fa puntare due variabili allo stesso oggetto nel caso si riferiscano a due oggetti uguali.
    Se ti riferisci al "auto-boxing" (feature da Java 5 in poi), ci sarebbe da fare un discorso un po' più lungo. Se ti interessa e vuoi, apri pure un'altra discussione su questo.

    Paolovox ha scritto:


    Ho capito l'inefficienza delle collisioni.
    In caso di collisioni risulterebbe inefficiente dato che non si avrebbe più un tempo O(1) costante per accedere ad un oggetto, ma quando più oggetti collidono vengono messi in una lista puntata dalla posizione dell'hash code nella tabella hash, quindi va scandita la lista per trovare l'oggetto utilizzando ogni volta il metodo equals.
    Esatto, il hashCode alla fine viene usato solo per indirizzare il giusto bucket ("secchio") in cui sono contenuti più oggetti. E il caso estremo-peggiore è proprio se si fa ritornare da hashCode() un valore fisso, es. 10. In questo caso tutti gli oggetti di quella classe andranno a finire nello stesso bucket e quindi ci sarà una grossa lista linkata per cui i benefici di una hash-table vanno a farsi friggere.

    L'obiettivo di hashCode() è appunto quello di fornire valori il più possibile distribuiti e distinti. Ma le collisioni possono comunque sempre esserci.

    Paolovox ha scritto:


    L'hash code viene utilizzato solo per memorizzare oggetti nelle Hash Table?
    Sì, hashCode() è stato fatto ed è utilizzato quasi esclusivamente per le collezioni basate su hash-table.

    Paolovox ha scritto:


    Giusto con i generics potrei dichiarare la mia classe:
    public class MyList<Elemento> implements Iterable<Elemento>
    e utilizzare nell'Iterator solo Elemento.
    No, la tua classe MyList non è "generica". In MyList<Elemento> nella definizione della classe, il Elemento non è un tipo concreto ma una type variable (come il <E> delle collezioni). E il fatto poi che tu abbia una classe con nome Elemento, complicherebbe solo le cose a livello di nomi.

    Quindi è corretto:

    public class MyList implements Iterable<Integer>

    Paolovox ha scritto:


    Ma se un oggetto è mutabile perchè ad ogni mutazione generarne uno diverso? Il precedente non potrebbe andare bene?
    Innanzitutto per hashCode non è obbligatorio usare per forza tutto lo "stato" di un oggetto. Si potrebbe usarne solo una parte, purché resti corretto il contratto tra equals e hashCode. Se cambia quella parte di stato, chiaramente hashCode viene influenzato ma è giusto che sia così.

    È chiaro che se metti in un HashSet un oggetto "mutabile" e tenendo il reference all'oggetto poi ne modifichi il suo stato dopo che l'oggetto è stato inserito nel HashSet .... succede una brutta cosa: potresti non trovare più l'oggetto.

    Paolovox ha scritto:


    Costruttore che utilizza l'Elemento first e last per linkare gli elementi.
    
    public MyList(int... interi) {
    		
    		for ( int i =0 ; i<interi.length ; i++){
    			
    			int x = i;
    			hash+= ++x * interi[i];
    			
    			if(first == null) {
    				first = new Elemento(interi[i],null); continue;
    			}
    			
    			Elemento l;
    			if(first.next == null){
    				l = first;
    			}
    			else l=last;
    			
    			last = new Elemento(interi[i],null);
    			
    			l.next = last;					
    		}
    	}
    
    Se c'è il last è molto più semplice e il tuo codice si può ridurre/semplificare. E innanzitutto sarebbe anche pulito avere un metodo apposito per il add di un elemento:
    public void add(int valore) {
        Elemento e = new Elemento(valore, null);
    
        if (last != null) {
            last.next = e;
        }
    
        last = e;
    
        if (first == null) {
            first = last;
        }
    }
    E poi nel costruttore banalmente:
    public MyList(int... interi) {
        for (int v : interi) {
            add(v);
        }
    }

    Paolovox ha scritto:


    E ho visto dal tuo sito personale che sei un javista d'onore.
    Grazie.
  • Re: Esercizio Java

    Ti ringrazio nuovamente per tutte le dritte.
    Ciao alla prossima
Devi accedere o registrarti per scrivere nel forum
11 risposte