Esercizi svolti a lezione il 9 Novembre 2000 & Esercizi proposti

(ATTENZIONE: chi trova un errore in questa pagina ricevera' un premio!!!)

Lo scopo di questa lezione (e degli esercizi proposti) e` "conquistare" una maggiore padronanza (capire quando e come devono essere usate) delle nozioni di:

Commento e Invariante

Nel seguente programma abbiamo inserito, in italiano, dei commenti che ne illustano il funzionamento.
Il commento iniziale spiega cosa fa il programma.
I commenti accanto alla dichiarazione delle variabili spiegano che cosa conterranno le variabili.
I commenti nel body spiegano il significato dei valori che le variabili assumono in determinati punti del programma. Vengono dette "invarianti" perche sono valgono ogni volta che il "controllo dell'esecuzione" passa per quel punto del programma, non importa da che parte si arriva (ad esempio al test del while si puo' arrivare dall'inizio del body del programma o dalla fine del body del ciclo).


{Questo programma legge un intero da terminale  
 e scrive su terminale il fattoriale dell'intero letto.
 Se l'intero digitato e' negativo restituisce 1.}

var 
    n:integer; {Memorizza l'input (l'intero che si legge da terminale)}
    f:integer; {Memorizza l'output (il fattoriale dell'intero 
                memorizzato in n), e' anche usato per memorizzare
                risultati intermedi della computazione}
    
    i:integer; {E' usata come contatore in un ciclo while}

begin

   writeln('Questo programma calcola il fattoriale di un intero non negativo.');
   writeln('Digita l'intero:');
   readln(n);

   i:=0;
   f:=1;

   {Ogni volta che viene effettuato il test del while
    il ciclo e' stato eseguito i volte e
    f contiene il fattoriale di i. 
    Ad ogni iterazione il valore di i aumenta di 1,
    quindi, in un numero finito (possibilmente nullo)
    di passi i assumera' un valore non inferiore ad n
    e si uscira' dal ciclo}
   while (n>i) do
      begin
        i:=i+1; 
        f:=f*i;
      end;
   {Analizziamo il valore di f all'uscita dal ciclo.
    Sappiamo che f contiene il fattoriale di i.
    Distinguamo tre casi possibili per il valore di n (l'input).
    a. n<0. 
       Non si e' mai entrati nel ciclo, quindi i=0.
       Abbiamo f=1. Il risultato voluto per n negativo.
    b. n=0. 
       Non si e' mai entrati nel ciclo, quindi i=0.
       Abbiamo i=n.
       Abbiamo f = fattoriale di n.
    c. n>0. 
       Il ciclo e' stato eseguito n volte, quindi i=n.
       Abbiamo i=n.
       Abbiamo f = fattoriale di n.}
end.

I commenti nel programma precedente sono piuttosto lunghi. Sicuramente risulteranno noiosi a programmatori dotati di una certa esperienza. Si puo' allora pensare di scrivere il programma senza commenti:

var 
    n:integer; 
    f:integer; 
    
    i:integer; 

begin

   writeln('Questo programma calcola il fattoriale di un intero non negativo.');
   writeln('Digita l'intero:');
   readln(n);

   i:=0;
   f:=1;

   while (n>i) do
      begin
        i:=i+1; 
        f:=f*i;
      end;

   writeln('Il fattoriale di',n);
   writeln('e`',f);
end.

Tuttavia questa situazione non e' completamente soddisfacente. E' vero che le writeln aiutano a capire che cosa fa il programma, ma ci sono dei punti oscuri: Che cosa succede se si fornisce un numero negativo? Quale e' il ruolo della variabile i? Perche' i e' inizializzata a 0? Perche' f e' inizializzata a 1? Che cosa fa il ciclo? A cosa serve il test? Siamo sicuri che questo programma fa quello che dicono le writeln?

Occorre quindi trovare una via di mezzo tra la prima versione del programma (con commenti troppo lunghi e dettagliati) e la seconda (senza commenti). Cosa ne dite della seguente:

{Questo programma legge un intero da terminale  
 e scrive su terminale il fattoriale dell'intero letto.
 Se l'intero digitato e' negativo restituisce 1.}

var 
    n:integer; {Memorizza l'input}
    f:integer; {Memorizza risultati intermedi e output}
    
    i:integer; {Contatore usato nel ciclo while}

begin

   writeln('Questo programma calcola il fattoriale di un intero non negativo.');
   writeln('Digita l'intero:');
   readln(n);

   i:=0;
   f:=1;

   while (n>i) do {f=i!}
      begin
        i:=i+1; 
        f:=f*i;
      end;
   {se n<0 allora i=0, quindi f=1.
    se n>=0 allora i=n, quindi f=n!.}

   writeln('Il fattoriale di',n);
   writeln('e`',f);
end.

Forse il commento (invariante) dopo il do non dice abbastanza. Provate a scrivere qualcosa d'altro.

Inoltre, il programma potrebbe essere migliorato in modo da controllare che effettivamente l'utente digiti un intero non negativo. Ci sono diverse alternative. Elencate quelle che vi vengono in mente e implementate qualla che preferite.

Se cambiate il programma Ricordatevi di modificare i commenti in modo opportuno!!! Un programma con commenti sbagliati e' peggio di un programma senza commenti!!!


Procedura

La parte di codice che effettua il calcolo del fattoriale puo' essere isolata dal resto del programma attraverso una procedura.

{Questa procedura calcola il fattoriale di m 
 e lo restituisce in r.  
 Se m<0 restituisce 1.}
procedure fatt(m:integer; var r:integer);

var 
    i:integer; {Contatore usato nel ciclo while}

begin

   i:=0;
   r:=1;

   while (m>i) do {r=i!}
      begin
        i:=i+1; 
        r:=r*i;
      end;
   {se m<0 allora i=0, quindi r=1.
    se m>=0 allora i=m, quindi r=m!.}

end;

Ora un qualunque programmatore puo' utilizzare la procedura fatt per calcolare il fattoriale di un numero iintero non negativo. L'unica cosa che il programmatore deve conoscere per poter utilizzare in modo appropriato la procedura fatt e' costituito dalle prime 4 linee di codice: le 3 linee di commento e la linea dell'intestazione della procedura.

{Questa procedura calcola il fattoriale di m 
 e lo restituisce in r.  
 Se m<0 restituisce 1.}
procedure fatt(m:integer; var r:integer);


Funzione

Invece di una procedura si puo' utilizzare una funzione...

{Questa funzione restituisce il fattoriale di m.  
 Se m<0 restituisce 1.}

function fatt(m:integer;):integer;
var 
    r:integer; {Memorizza risultati intermedi e risultato finale}
    i:integer; {Contatore usato nel ciclo while}

begin

   i:=0;
   r:=1;

   while (m>i) do {r=i!}
      begin
        i:=i+1; 
        r:=r*i;
      end;
   {se m<0 allora i=0, quindi r=1.
    se m>=0 allora i=m, quindi f=m!.}
   
   fatt:=r;

end;


Esercizi proposti

Esercizi 

1. Fate l'esercizio suggerito dopo l'esempio sull'uso di commenti e
   invarianti. 
   Rifate l'esecizio usando un ciclo for. 
   Rifate l'esecizio usando un ciclo repeat. 
   ATTENZIONE: mettete gli invarianti!!! La cosa importante
               dell'esercizio e' proprio questa!!!

2. Riscrivete il programma sviluppato all'esecizio 1 utilizzando
   la procedura fatt vista a lezione.

3. Riscrivete il programma sviluppato all'esecizio 1 utilizzando
   la funzione fatt vista a lezione.

4. Scrivete un programma che legge da terminale un intero non negativo n,
   poi legge n interi non negativi, e stampa su terminale 
   il fattoriale della media aritmetica (usate div per fare la divisione)
   degli n interi.
   ATTENZIONE: mettete i commenti.
               Usate procedure e/o funzioni: riutilizzate procedure
               che abbiamo gia' visto a lezione e, quando necessario,
               scrivetene di nuove.


Soluzioni