Paola Magillo, Univestita' di Genova, Corso di Programmazione II per SMID, a.a. 2004-2005.

Lezione 10:

TIPI DI DATI, PILA E CODA

Tipi di dati concreti e astratti

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'.

Esempio

Tipo di dati astratto che prevede:

Tipi: QUALCOSA
Operazioni: 
  VAI: QUALCOSA --> QUALCOSA
  TORNA: QUALCOSA --> QUALCOSA
Proprieta':
  VAI(TORNA(x))==x
  TORNA(VAI(x))==x
Per 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...

Dall'astratto al concreto

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.

Sviluppo del Software

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:

Tipi di dati Pila e Coda

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:

La differenza sta nelle proprieta' richieste per queste operazioni.

Pila (Stack)

Si mettono e tolgono elementi dalla stessa parte della sequenza, per es. la cima.

Realizza la politica FILO (First In Last Out): il primo aggiunto viene cancellato per ultimo.

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();
}

Coda (Queue)

Si mettono e tolgono elementi da parti opposte della sequenza, per es. in cima e in fondo.

Realizza la politica FIFO (First In First Out): il primo aggiunto viene cancellato per primo.

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();
}

Implementazioni

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.

Implementazione della pila con array

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:

Implementazione della pila con "lista linkata"

La pila ha puntatore alla prima cella di lista linkata contenente i suoi elementi nell'ordine dalla cima al fondo.

Implementazione delle operazioni:

Implementazione della coda con array

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:

Nota: Tutte le somme che coinvolgono le variabili cima e fondo sono intese modulo la lunghezza dell'array.

Implementazione della coda con "lista linkata"

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:

Quando le operazioni non sono possibili

Possiamo seguire due strategie:

Bisogna essere coerenti, usare sempre la stessa strategia!

Esempio di uso delle pile

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' NO
Serve 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).