Sei sulla pagina 1di 20

Lezione 02.11.

2021

Le liste sono collezioni di elementi dove ad ogni


elemento è associato un indice, gli indici vanno da 0 a
size -1. Nelle liste ci possono essere duplicati.
Abbiamo due tipi di liste: ArrayList e LinkedList, che
sono le due classi concrete delle liste in java. Array
list è molto simile ad array vector da noi
implementato. LA LINKED LIST NON SI BASA SU UN
ARRAY. La novità delle linked list è che usa uno
schema doppio puntatore, uno al successore e uno al
predecessore. Puntatore è sinonimo di riferimento.
Ogni elemento della linked list è un nodo e ogni nodo
contiene il riferimento, collegamento (link), al
prossimo nodo. Dopo l’ultimo nodo c’è null, ovvero
assenza di puntamento. La differenza rispetto
all’array è che nonostante gli elementi siamo uno
dopo l’altro, i nodi creati nell’heap, a livello di idirizzi
non sono uno successivo all’altro, perché all’atto
della creazione vengono creati dinamicamente nel
primo posto dove c’è spazio. Gli elementi quindi non
sono CONTIGUI. È meglio usare gli indici quando si
usa un array list.
Scenario:
io voglio accedere all’elemento che si trova in
posizione 50. Se è un array io vi accedo
immediatamente, se è una linked list, deve iterare su
TUTTI gli elementi fino a che non arriva al
cinquantesimo. Supponiamo di voler cambiare
questo elemento con una set(index i), per poterlo
fare, dopo aver visto quanto vale con la get, deve DI
NUOVO iterare tutte le prime 49 posizioni. Ci
rendiamo conto che per operare con gli indici la
LinkedList è la soluzione meno efficiente. Con la
linkedlist è molto meglio lavorare con un ListIterator.
Nella classe Collections c’è già un metodo sort che
ordina la lista per valori crescenti. Poi c’è un metodo
ricerca binaria (binary search) e ritorna l’indice o un
numero negativo se non è presente l’elemento
cercato. Questi due metodi ricevono una List (Array o
Linked). Il funzionamento logaritmico di binary search
è efficace con un ArrayList, perché con una LinkedList
devo sempre e comunque iterare su tutte le posizioni
per arrivare alla mediana, mentre con un ArrayList vi
accedo immediatamente. Questi problemi
ovviamente si verificano con strutture dati molto
estese.
● Se ho un problema in cui devo fare parecchie
ricerche ed estrazioni mi conviene usare un ArrayList.
● Se ho un problema in cui devo effettuare parecchi
inserimenti e rimozioni in posizioni intermedie è
molto più efficiente una LinkedList.

Costruttori di ArrayList
•Un array list incorpora e nasconde un array che
dinamicamente può espandersi e contrarsi a piacere;
•Occorre distinguere tra capacità e dimensione di un
array list. La capacità è la lunghezza dell’array
sottostante. La dimensione ( size() ) è il numero
effettivo di elementi presenti nella lista.

•ArrayList() –costruisce un AL con capacità di default


•ArrayList( Collection<T> c ) –costruire un AL a
partire dalla collezione c (gli elementi di c sono posti
nell’AL nell’ordine stabilito dal corrispondente
iterator)
•ArrayList( int capacitaIniziale ) –costruisce un array
con assegnata capacita iniziale
•Metodi propri di ArrayList
•protected void removeRange( int da, int a );
rimuove tutti gli elementi che vanno dalla posizione
daalla posizione a(esclusa). Essendo protected, il
metodo è direttamente accessibile da una classe
erede-specializzazione di ArrayList;
•void trimToSize() fissa la capacità alla dimensione
attuale dell’AL. Utile quando si ritiene che un AL
abbia raggiunto una situazione di stabilizzazione.

Metodi propri di LinkedList<T>


•Anche se ottenibili mediante metodi già disponibili,
essi consentono di operare direttamente e più
efficientemente alle due estremità (testa e coda)
della lista:
•void addFirst( T elemento)
•void addLast( T elemento )
•T getFirst()
•T getLast()
•T removeFirst()
•T removeLast()
Concetti di Stack e Coda
•In uno stack le operazioni di inserimento (push) e
rimozione (pop) avvengono esclusivamente alla fine
della struttura (top o testa). La gestione è LIFO–Last
Input First Output. Esempio tipico di stack è la catasta
di piatti o di libri o di pratiche in un ufficio etc. lo
stack dietro le quinte è implementato con una linked
list.
•In una coda(queue) gli elementi entrano da un
estremo (coda della lista) ed escono dall’altro (testa
della lista). La gestione è FIFO–First Input First
Output. Tipico esempio è la gestione di una fila
disciplinata di persone davanti ad uno sportello
postale o bancario etc.
L’interfaccia Set<T>
•Estende l’interfaccia Collection<T>, ma non
introduce nuovi metodi. Nei set l’ordine non è
garantito ma non è nemmeno escluso. Nei set non si
possono assolutamente usare gli indici.
•Il metodo add(T x) aggiunge, senza creare duplicati,
x all’insieme. L’operazione si basa sul metodo equals
per stabilire se x è già presente, ma non segue
necessariamente un ordine particolare di inserimento
•Come anticipato, removeAll(Collection<T> c) e
retainAll(Collection<T> c) modificano il set this
rispettivamente con l’insieme differenza e
intersezione rispetto alla collezione parametro c
Tutti i metodi seguono la semantica dell’insiemistica
matematica. Però modificano this, l’unico modo per
non modificare l’originale è usando un costruttore
per copia.

Le classi HashSet<T> e TreeSet<T>


•Sono due classi concrete per operare sui set.
Costruttori esistono per creare un set a partire da un
altro generato con l’altra classe.
Es.

TreeSet<Integer> ts=new TreeSet<Integer>( hs );


dove hs è un HashSet<Integer> etc.
•HashSet velocizza l’accesso agli elementi
dell’insieme. Tuttavia, l’attraversamento del
contenuto di un hash set con un iteratore restituisce
gli elementi in un ordine qualsiasi . su un hashSet la
ricera di un elemento è più veloce rispetto alla ricerca
binaria, quindi lo uso quando voglio velocizzare le
operazioni di ricerca. In un hashSet non si itera su
tutti gli elementi, si stabilisce semplicemente se un
elemento è CONTENUTO ricercandolo in base al suo
hashCode. L’hashCode viene usato anche per inserire
un elemento. In molti casi le operazioni di ricerca
hanno una complessità O(1).
•TreeSet mantiene in ordine naturale(interfaccia
Comparable) il contenuto di un set mediante una
struttura ad albero binario ordinato (si veda la figura
che segue). In generale è più efficiente un HashSet;
tuttavia, se l’ordine è importante, si utilizza un
TreeSet.
Un albero è bilanciato se la cardinalità a sinistra e a
destra è uguale o differisce al massimo di uno.
L’operazione di inserimento in ordine è automatica in
base al confronto.

Tabelle hash e collisioni


•La classe HashSet memorizza gli oggetti in una
tabella (array) in cui la posizione di un elemento è
determinata dal suo hash code
•Se h è l’hash code dell’oggetto x da inserire o
ritrovare, e dim è la dimensione dell’array, la
posizione associata ad x può essere determinata
come segue:

int h=x.hashCode();
if( h<0 ) h=-h;
int indice = dim%h;

•Poichè è inevitabile che oggetti diversi possano dar


luogo allo stesso numero di hash o corrispondere
comunque alla stessa posizione (collisione), la tabella
memorizza gli elementi (ovviamente distinti) aventi lo
stesso numero di hash in posizioni vicine a quella
associata al numero di hash o usando liste
concatenate esterne (array di bucket).
Dietro le quinte, Java riesce a gestire le collisioni.
1)Soluzione meno usata, cerca di trovare dei posti
prima e dopo l’elemento che hanno lo stesso
hashCode;
2)Soluzione più usate, in quella posizione mette la
testa di una List che contiene esclusivamente
elementi che collidono in quella posizione.

LinkedHashSet
•È una variante che fornisce i tempi di accesso di un
hashset, ma nel contento mantiene l’ordine di arrivo
degli elementi
•A questo scopo gli elementi trovano collocazione in
un array secondo la tecnica hash

•In più, gli elementi, appena inseriti nella tabella,


vengono collegati gli uni agli altri a costituire una lista
concatenata che riflette appunto l’ordine di arrivo
degli elementi

•La struttura dati è una utile (e più efficiente)


alternativa ad un TreeSetnei casi nei quali l’ordine di
inserimento è anche l’ordine con cui gli elementi
vengono visualizzati etc.
MAPPA

Le mappe sono delle funzioni matematiche, ovvero


una corrispondenza tra due insiemi, per ogni x del
dominio associa una e una sola y del codominio.
•Una map(pa) è una particolare collezione in cui gli
elementi sono coppie (o corrispondenze):
<chiave, valore>
<chiave, valore>
<chiave, valore>

•Una map(pa) realizza una funzione: data la chiave si
vuole (possibilmente in modo veloce) rintracciare il
valore associato
•In una map(pa) non sussistono duplicati. In altre
parole, le entrate (coppie) esistenti sono associate a
chiavi distinte. Tuttavia, uno stesso valore può essere
associato a chiavi diverse. L’aggiunta di una nuova
coppia con una chiave già presente determina il
rimpiazzamento del valore pre-esistente
•Sia la chiave (key) che il valore (value) devono
essere oggetti(istanze di classi eredi dirette o
indirette di Object)
NON CI SONO DUPLICATI (IN SENSO DI CHIAVE) IN
UNA MAPPA. UNA CHIAVE È UNIVOCA. Se si inserisce
una nuova coppia chiave valore e la chiave è già
presente, viene semplicemente aggiornato il valore.

L’interfaccia Map<K,V>

(K è il tipo della chiave, V quello del valore)


•void clear(); --svuota la mappa

•boolean containsKey( K chiave );


ritorna true se esiste una coppia con questa chiave,
false altrimenti

•boolean containsValue( V valore );


ritorna true se almeno una coppia esiste con questo
valore

•V put( K chiave, V valore );


aggiunge una nuova corrispondenza <chiave,valore>.
Aggiorna una eventuale corrispondenza già esistente
con questa chiave rimpiazzando il valore esistente
con quello fornito, e restituendo il vecchio valore. Il
metodo ritorna null se la chiave non è già presente
nella mappa
•V get( K chiave );
ritorna l’oggetto associato a questa chiave o null se la
chiave non è presente
•boolean isEmpty();

•void putAll( Map<K,V> m );

•V remove( K chiave );
elimina la corrispondenza <chiave,valore> se chiave è
presente, e ritorna il valore associato
•int size();

•Collection<V> values(); --restituisce una collezione


dei soli valori presenti. Si possono scandire tali valori
con un iterator

•Set<K> keySet(); --restituisce un set delle sole chiavi


presenti

Le classi HashMap<K,V> e TreeMap<K,V>

•HashMapsi basa su una tabella hash in cui,


similmente a quanto visto per HashSet, la
distribuzione delle coppie <chiave, valore> avviene in
base al codice hash della chiave. Risultano velocizzate
le operazioni di accesso agli elementi, ma non viene
garantito alcun ordine delle coppie

•TreeMap si appoggia ad una struttura ad albero che


sebbene sia meno efficiente di una HashMap, è in
grado di garantire l’ordinamento naturale delle
coppie in base alla chiave

•Esistono costruttori, nelle due classi, che


consentono di passare da una rappresentazione
all’altra

•Esiste l’analoga struttura dati LinkedHashMap che


risponde alle stesse motivazioni e caratteristiche viste
con riferimento alla LinkedHashSet

•Riallocazione di una tabella hash: La gestione di un


HashSet o una HashMap prevede la riallocazione
della struttura dati quando il riempimento raggiunge
una certa soglia espressa come percentuale della
capacità della tabella

•La riallocazione, controllabile mediante un


parametro di un costruttore, si rende necessaria per
garantire l’efficienza degli accessi
•Entry (entrata) è una coppia <K,V> della mappa
•Si può lavorare su una mappa anche usando le sue
entry, ciò che spesso evita di doversi fare restituire
prima il keySet() della mappa
•Il metodo di Map: entrySet() ritorna una «vista» set
sulle entry della mappa. Sull’entry set si può operare,
ovviamente, con l’iteratore dei set. Ogni modifica si
ripercuote sulla mappa
•Su un oggetto Entry, sono disponibili i metodi:
getValue()e getKey() di ovvio significato, nonché
setValue(v). Es.
•Map<String,Integer> m=new TreeMap<>(); …
•Set<Map.Entry<String,Integer>> s=m.entrySet();
•for( Map.Entry<String,Integer> e: s ){
if( e.getKey().equals(“Java”) )
e.setValue( e.getValue()+1 );
}

L’interfaccia Comparator<T>
•La classe Collections offre i metodi sorte
binarySearchcome servizi di utilità su una lista di
oggetti. Tali metodi richiedono che gli oggetti siano
Comparable
•Cosa succede se si desidera ordinare una lista
secondo un criterio diverso da quello espresso dal
metodo compareTo? O se la classe degli elementi non
implementa affatto Comparable ? Si potrebbe
ridefinire il metodo compareTo ad es. progettando
una classe erede
•In alternativa, le API di Java forniscono l’interfaccia
Comparatorcon il metodo compare. Esiste una nuova
versione dei metodi sorte binarySearchche accettano
la lista edun oggettoComparator

interfaceComparator<T>{
int compare( T o1, T o2 );
}//Comparator
il metodo compare(x1,x2) deve ritornare <0 se x1
precede x2, 0 se x1 è uguale a x2, >0 altrimenti

metodo asList di Arrays


in genere, quando creiamo una linked list non la si
può riempire direttamente, bisogna fare diverse add
per aggiungere di volta in volta gli elementi. Con
questo metodo invece viene creato un array e lo si
converte a lista, però al metodo posso passare come
parametro gli oggetti che vengono aggiunti alla lista.
Il metodo crea una lista, però io non so se sia un array
list o linked list, se voglio che sia di un tipo specifico
devo fare un casting. Inoltre, questo metdoo crea List
IMMUTABILI.
IMPLEMENTARE UNA CLASSE NELL’ARGOMENTO DI
UN METODO
Questa feature è molto utile quando si vuole
programmare una classe al volo che poi non serve più
in altri progetti. Una classe che ha un solo metodo
viene chiamata CLASSE FUNZIONALE.

È possibile evitare l’introduzione di una classe a parte


che implementa Comparator ricorrendo ad una
classe anonima, ossia facendo uso dell’estensione ed
istanziazione al volo.

Il meccanismo è utilizzabile non solo per


implementare “al volo” un’interfaccia ma anche per
estendere una classe preesistente e ridefinire
qualche metodo di interesse. In ogni caso viene
introdotta una classe anonima “consumata sul
posto”.

Potrebbero piacerti anche