Tipo di dati (concreto) =
Un tipo di dati concreto e' come una classe in Java:
Esempio: i numeri interi con le operazioni di addizione, sottrazione, moltiplicazione, divisione e controllo se un numero sia divisibile per un altro numero (quest'ultima restituisce un booleano).
Notazione:
Tipi: integer, boolean Operazioni: add: integer x integer --> integer definita come add(x,y) = x+y sub: integer x integer --> integer definita come sub(x,y) = x-y mul: integer x integer --> integer definita come mul(x,y) = x*y div: integer x integer --> integer definita come div(x,y) = x/y checkdiv: integer x integer --> boolean definita come checkdiv(x,y) = true iff y*div(x,y) = x
Tipo di dati astratto = astrazione di un tipo di dati:
A parte le proprieta', un tipo di dati astratto e' come un'interfaccia in Java:
In pratica un tipo di dati astratti e' la specifica dei requisiti che vogliamo che un tipo di dati concreto possegga.
Matematicamente si tratta di un'algebra eterogenea.
Un tipo di dati astratto puo' essere implementato
da uno o piu' tipi di dati concreti.
L'implementazione fornisce gli insiemi corrispondenti ai nomi dei
tipi e il body alle operazioni, nel rispetto delle proprieta'.
Tipo di dati astratto che prevede:
Tipi: QUALCOSA Operazioni: VAI: QUALCOSA --> QUALCOSA TORNA: QUALCOSA --> QUALCOSA Proprieta': VAI(TORNA(x))==x TORNA(VAI(x))==xPer illustrare graficamente i tipi e le operazioni si puo' fare diagramma a bolle...
Questo tipo di dati astratto puo' essere implementato dal tipo di dati concreto [1]:
QUALCOSA = i numeri interi VAI(x) = x+1 TORNA(x) = x-1
Ma anche dal tipo di dati concreto [2]:
QUALCOSA = i booleani VAI(x) = not x TORNA(x) = VAI(x)
Oppure dal tipo di dati concreto [3]:
QUALCOSA = i mesi dell'anno VAI(x) = il mese successivo a x TORNA(x) = il mese precedente a x
E da infiniti altri...
Esistono livelli intermedi fra l'astratto e il concreto.
Per es. se dico che
QUALCOSA devono essere gli interi ho ancora gradi di liberta' per
le operazioni VAI e TORNA (per es. entrambe potrebbero essere
il cambiamento di segno, oppure una somma 10 e l'altra lo sottrae...).
In Java sarebbe una classe astratta.
Cio' che e' concreto per la matematica e' ancora astratto e
per l'informatica.
Per l'informatica e' concreto davvero solo quando e' codice funzionante!
Per es. considerando l'implementazione [3]
del tipo di dati astratto QUALCOSA, prima bisogna stabilire come
rappresentiamo i mesi (es. con gli interi da 1 a 12, oppure
con 12 stringhe che sono i loro nomi in italiano) e poi scrivere
il codice in un linguaggio (es. in Java una classe).
Anche quando un'operazione dal punto di vista matematico e' univocamente
definita, puo' non essere univocamente determinato l'algoritmo
da usare per scriverne il codice.
La definizione matematica dice solo che cosa deve fare un'operazione,
non dice come la deve fare.
Esempio: l'operazione di ordinare in modo crescente una sequenza di numeri
interi e' univocamente definita, ma esistono numerosi algoritmi
di ordinamento e ne devo scegliere uno.
1.
Descrizione informale del problema da parte del committente
Es: c'e' un insieme di pazienti con delle malattie a cui sono prescritte
delle medicine...
2.
Estrapolazione della specifica dei requisiti (tipo di dati astratto)
Es: di un paziente devo poter ottenere nome, cognome, eta', malattie
che ha, medicine che prende;
di una malattia devo poter ottenere le medicine che servono a curarla;
di una medicina devo poter ottenere le malattie che puo' curare.
3. Implementazione per tappe successive:
Una lista di elementi (un tipo base qualsiasi, per es. i numeri interi) e' una sequenza finita di elementi. Ma quali operazioni vogliamo che siano possibili su queste sequenze?
Pila e Coda sono due diversi tipi di dati aventi come insieme di valori corrispondenti le sequenze finite di elementi.
Entrambe prevedono operazioni di:
Si mettono e tolgono elementi dalla stessa parte della sequenza, per es. la cima.
Tipi: stack, element, boolean Operazioni: push: stack x element --> stack pop: stack --> stack top: stack --> element empty: --> stack isempty: stack --> boolean Proprieta': top(push(st,el)) = el pop(push(st,el)) = st isempty(empty) not isempty(push(st,el))
Traducendo in java (non possiamo tradurre le proprieta'), usiamo come "element" la classe generica Object e scriviamo un'interfaccia Stack:
interface Stack { void push(Object o); void pop(); Object top(); void empty(); boolean isEmpty(); }
Si mettono e tolgono elementi da parti opposte della sequenza, per es. in cima e in fondo.
Tipi: queue, element, boolean Operazioni: enqueue: queue x element --> queue dequeue: queue --> queue front: queue --> element empty: --> queue isempty: queue --> boolean Proprieta': front(qu) = front(enqueue(qu,el)) dequeue(enqueue(qu,el)) = enqueue(dequeue(qu),el) isempty(empty) not isempty(enqueue(qu,el))
Traducendo in Java (tranne le proprieta'):
interface Queue { void enqueue(Object o); void dequeue(); Object front(); void empty(); boolean isEmpty(); }
I tipi di dati Pila e Coda concettualmente sono univocamente definiti dalle proprieta' che abbiamo posto, ma devono essere ancora implementati in una struttura dati (i valori) e algoritmi (le operazioni), e poi struttura dati ed algoritmi vanno implementati in codice di un linguaggio di programmazione (es. Java).
Per la struttura dati abbiamo varie possibilita'... possiamo usare come struttura dati di base:
Lista linkata = struttura formata da celle, ognuna delle quali puo'
contenere un elemento e ha puntatore alla cella successiva
(link = collegamento).
Il treno della lezione 3 e poi la classe
IntList della lezione 4
era una lista linkata di interi!
Per gli algoritmi, abbiamo il seguente obiettivo: fare si' che tutte le operazioni siano implementate eseguendo un numero costante di istruzioni, indipendentemente da quanti elementi sono presenti nella pila / coda.
Elementi della pila sono messi in posizioni consecutive nell'array,
in ordine inverso
(fondo della pila = prima casella dell'array,
cima della pila = ultima casella dell'array).
L'array si espande e si contrae dalla parte della cima, cioe' dalla parte
della fine dell'array (aggiungere elemento sposta la cima verso destra,
toglierlo la sposta verso sinistra).
Il fondo della pila resta stabile, coincidente con l'inizio dell'array.
Un contatore cont tiene il numero di caselle occupate (il numero
di elementi nella pila), che sono dalla posizione 0 a cont-1.
Il valore di cont e' anche la posizione della prima casella libera.
La pila ha raggiunto la sua massima capacita' sse
l'array e' pieno sse cont e' uguale alla lunghezza dell'array.
Implementazione delle operazioni:
La pila ha puntatore alla prima cella di lista linkata contenente i suoi elementi nell'ordine dalla cima al fondo.
Implementazione delle operazioni:
Elementi della coda sono messi in posizioni consecutive nell'array,
nel loro ordine.
Inizialmente cima = prima casella, fondo = ultima casella dell'array.
L'array si espande verso il fondo (ultima casella si sposta verso destra)
e si contrae dalla parte della cima (prima casella si sposta anch'essa
verso detra).
L'array e' considerato "circolare" cioe' l'ultima casella dell'array ha
come successiva la prima (questo equivale a fare tutte gli incrementi
sugli indici dell'array modulo la lunghezza dell'array).
Poiche' aggiunge in fondo e si toglie in cima, a un certo punto
puo' scavalcare la fine dell'array...
Tengo due contatori: la posizione dell'elemento in cima e in fondo.
Per comodita' tengo anche contatore cont del numero di elementi presenti.
La coda ha raggiunto la sua massima capacita' sse cont e' uguale alla
lunghezza dell'array.
Implementazione delle operazioni:
La coda ha puntatore alla prima cella e all'ultima cella di lista linkata contenente i suoi elementi nell'ordine dalla cima al fondo.
Implementazione delle operazioni:
Possiamo seguire due strategie:
Programma che, data una stringa con parentesi, controlla se le parentesi sono bilanciate. Le parentesi possono essere tonde (), quadre [], graffe {}.
Algoritmo:
Vedere il programma ControllaParentesi.java. La stringa da controllare va scritta su command-line. Per esempio:
java ControllaParentesi "[ci{ao}]" -- rispondera' SI java ControllaParentesi "[ci{ao)}]" -- rispondera' NOServe anche l'interfaccia Stack (vista in questa lezione) e la classe ArrayStack che la implementa (provate a scriverla voi in base alle indicazioni viste in questa lezione).