Sei sulla pagina 1di 52

Informatica A a.a.

05/06 Prima Prova in Itinere 24/11/2005


Esercizio 1 - Algebra di Boole e Aritmetica Binaria - ( 2 punti )
(a) Si dica se le seguenti espressioni booleane sono equivalenti, giustificando la risposta: A or C and not B not ( ( B or not C ) and not A ) Si osserva, applicando due volte DeMorgan alla prima espressione, che A or C and not B = not (not A and not (C and not B)) = not (not A and (not C or B)) che equivalente alla seconda espressione a meno di permutazioni (prop. simmetrica) Alternativamente, si pu verificare che le espressioni hanno le stesse tabelle di verit: ABC
0 0 0 0 1 1 1 1 0 0 1 1 0 0 1 1 0 1 0 1 0 1 0 1

A or C and not B
0 0 0 0 1 1 1 1 0 1 0 0 1 1 1 1 0 1 0 1 0 1 0 1 0 1 0 0 0 1 0 0 1 1 0 0 1 1 0 0 0 0 1 1 0 0 1 1

not ( ( B or not C ) and not A )


0 1 0 0 1 1 1 1 0 0 1 1 0 0 1 1 1 0 1 1 1 0 1 1 1 0 1 0 1 0 1 0 0 1 0 1 0 1 0 1 1 0 1 1 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1

(b)

Si convertano in complemento a due i numeri A = 93dec e B = 48dec sul minimo numero di bit sufficiente a rappresentarli entrambi (stabilendo quindi anche quanti bit occorrono), e se ne calcoli la somma (A + B) e la differenza (A - B) in complemento a due. Si indichi anche se si genera riporto sulla colonna dei bit pi significativi e se si verifica overflow. Con 8 bit si rappresentano in C2 i numeri da 128dec a +127dec Col metodo dei resti in binario assoluto, e poi in C2: 93dec = 1011101bin = 01011101C2 48dec = 110000bin = 00110000C2 -48dec = 11001111C2 + 00000001C2 = 11010000C2 Applicando lalgoritmo di addizione, ricordando che 93(48) si calcola come 93+48, e indicando (riporto) e [overflow] con le rispettive parentesi: 0 1 0 1 1 1 0 1 1 1 0 1 0 0 0 0 (1) [0] 0 0 1 0 1 1 0 1 0 1 0 1 1 1 0 1 0 0 1 1 0 0 0 0 (0) [1] 1 0 0 0 1 1 0 1 93dec -48dec 45dec 93dec 48dec 141dec ?? -115dec !!

+ =

+ =

Nel primo caso si genera riporto dalla colonna dei bit pi significativi ma non overflow, nel secondo si verifica overflow anche se non vi eccedenza di riporto sulla colonna dei bit pi significativi

Esercizio 2 - Struttura e allocazione della memoria - ( 1,5 punti ) Si consideri un personal computer dotato di 512 MByte di memoria, costituita da celle di 32 bit. (a) Qual la dimensione (in bit) del registro dati e del registro indirizzi?

Il DR di 32 bit, come le celle della memoria. LAR di 27 bit, perch 512 MByte = 128 MWord = 227 (deve indirizzare 227 parole di 4 byte).

(b) Sapendo che sizeof(int) = sizeof(float) = 4 byte, si consideri la seguente dichiarazione:


typedef struct { int giorno; int mese; int anno; } Data; typedef enum { Analisi, Fisica, Informatica } CorsoPrimoSemestre; typedef int CarrieraPrimoSemerstre[3]; typedef struct { int matricola; Data DataNascita; CarrieraPrimoSemestre voti; float mediaPrimoSemestre; } Studente; CorsoPrimoSemestre worst = Informatica; Studente Classe0506[200];

Si calcoli la dimensione (in byte) del vettore Classe0506.

sizeof(Data) = 3 * sizeof(int) = 12 byte = 3 word sizeof(CarrieraPrimoSemestre) = 3 * sizeof(int) = 12 byte = 3 word sizeof(Studente) = sizeof(int)+sizeof(Data)+sizeof(CarrieraPrimoSemestre)+sizeof(float) = 32 byte sizeof(Classe0506) = 200 * sizeof(Studente) = 6400 byte = 1600 word

(c) Ipotizzando che il vettore sia stato allocato sullelaboratore di cui al punto (a) a partire dalla cella di memoria con indirizzo 972, si calcolino gli indirizzi delle celle che contengono le variabili identificate dalle seguenti espressioni:
Classe0506[1].DataNascita.Anno Classe0506[32].voti[worst]

Calcoliamo gli indirizzi sommando allindirizzo base la dimensione (misurata in celle) delle variabili allocate prima della variabile di cui si chiede di calcolare lindirizzo: ind1 = ind2 = 972 + sizeof(Studente)/4 + sizeof(int)/4 + 2*sizeof(int)/4 = 972 + 8 + 1 + 2 = 983 972 + 32*sizeof(Studente)/4 + sizeof(int)/4 + sizeof(Data)/4 + 2*sizeof(int)/4 = 972 + 256 + 1 + 3 + 2 = 1234

Esercizio 3 - Codifica vincolata - ( 2,5 punti ) Si codifichi un programma C che legge dallo standard input una sequenza (di lunghezza arbitraria) di interi positivi terminata dal valore 0 e, al termine della sequenza, visualizza su standard output un messaggio che indica quante coppie di numeri consecutivi che siano diversi, pari e il cui prodotto sia un quadrato perfetto sono contenute nella sequenza. Ecco un esempio: > > 2 50 13 16 8 7 8 2 18 6 6 Le coppie di numeri cercati sono 4 16 4 1 25 0

Si noti che un numero pu anche appartenere a due coppie. Nella soluzione si utilizzi la funzione: int dppqp(int, int); /* Restituisce 1 se i parametri sono diversi, pari, e il
loro prodotto un quadrato perfetto, 0 altrimenti */

La funzione dichiarata qui sopra pu essere ad esempio definita come segue:


int dppqp (int a, int b) { int x = 2, p = a*b; if ( a%2 || b%2 || a==b ) // Se uno dei parametri dispari o sono uguali: -> 0 return 0; while ( x*x < p ) // Prova i quadrati di tutti i numeri pari iniziando x += 2; // da 2 e fino a raggiungere o superare a*b return x*x == p; // Se lultimo quadrato supera p: -> 0, altrimenti: -> 1 }

#define SENTINELLA 0 int main() { int cont = 0, prev, cur = 0;

// L'inizializzazione di cur a 0 innocua // rappresentano il valore corrente e il precedente

printf("\nSequenza di int separati da spazi (termina con %d)\n\n", SENTINELLA); do { prev = cur; scanf("%d", &cur); cont += dppqp(prev,cur); } while ( cur != SENTINELLA ); printf("Le coppie di numeri cercati sono %d\n", cont); return 0; }

Esercizio 4 - Codifica libera - ( 3,5 punti ) Si codifichi una funzione C che riceve come parametri due stringhe che rappresentano due parole e restituisce un valore intero, da interpretarsi come valore di verit, che indica se le parole sono anagrammi, cio se possibile ottenere luna dallaltra tramite permutazione delle loro lettere. Ad esempio le parole POLENTA e PENTOLA sono anagrammi. Si presti attenzione al fatto che parole come TAPPO e PATTO non sono anagrammi, anche se ogni lettera delluna contenuta nellaltra.

// Restituisce 1 se le due parole sono una l'anagramma dell'altra, 0 altrimenti int anagrammi(char a[], char b[]) { int len = strlen(a), // lunghezza di a contA, contB, // contatori di occerrenze di una lettera i, k; // indici di sevizio if ( len != strlen(b) ) return 0; for ( i = 0 ; i < len ; i++ ) { contA = contB = 0; for ( k = 0 ; k < len ; k++ ) { if ( a[k] == a[i] ) ++contA; if ( b[k] == a[i] ) ++contB; } if ( contA != contB ) return 0; } return 1; } // se non sono lunghe uguali banale

// per ogni char a[i] in a (escluso il \0) // scandisco entrambe le stringhe // conto le occorrenze di a[i] in a // e anche le occorrenze di a[i] in b // se un conteggio non corrisponde -> 0

// se corrispondono tutti -> 1

// Oppure, usando un vettore ausiliaro con tanti interi quanti sono i caratteri // ammessi (ipotizzando che la stringa ammetta solo caratteri del set standard) int anagrammi(char a[], char b[]) { int len = strlen(a), // lunghezza di a i, k, // indici di sevizio int ch[128] = { 0 }; // inizializazione automatica a 0 if ( len != strlen(b) ) return 0; for ( i = 0 ; i < len ; i++ ) { ch[a[i]]++; ch[b[i]]--; } for ( i = 0 ; i < 128 ; i++ ) if ( ch[i] ) return 0; return 1; } // se non sono lunghe uguali banale

// per ogni char a[i] in a (escluso il \0) // incremento il contatore per i char di a // e lo decremento per i char di b // scandisco il vettore dei contatori // se un valore non zero -> return 0

// se sono tutti 0 -> return 1

Esercizio 5 - Parametrizzazione del codice - funzioni - ( 2 punti ) Si completi opportunamente il programma sottostante, dove puzzle una matrice di caratteri dichiarata come variabile globale, e quindi visibile a tutte le funzioni. Il programma visualizza un messaggio con tre numeri, che indicano quante volte la parola inserita si legge nel puzzle in verticale, in orizzontale e in diagonale. Esempi e avvertimenti:
A M A C A S A L T A B P O A R A L B E R C O R N A L B E R O D L A O L T A R O Q E L E T B A T T E U A O R T E L R A T B G C D O R E E O R A H X A L O O R A N T S A L M O N R U N T M A L S A O T X E E

> DICIOTTO > La parola non si trova > AMACA > H: 0 > ORA > H: 2

V: 1

D: 1

V: 3

D: 2

Si presti attenzione - ai bordi del puzzle (le parole non continuano ciclicamente) - allassenza del terminatore di stringa nel puzzle

Per scrivere una soluzione sintetica e coerente con i principi della buona programmazione, si suggerisce (e si apprezza) luso della funzione controlla, che verifica la presenza di una parola a partire da una casella del puzzle, in modo parametrico rispetto alla direzione in cui cercarla (tra le 8 direzioni possibili) i parametri cio indicano, rispetto alle direzioni verticale e orizzontale, se lo spostamento non avviene, avviene in senso positivo, o avviene in senso negativo. Ad esempio, con i valori 0 e 1 si pu indicare la direzione , con -1 e 0 la direzione , con 1 e -1 la direzione .
#define N 10 char puzzle[N][N] = { 'A', 'B', 'C',... 'T', 'E' }; /* Controlla se da puzzle[i][j] inizia la parola p, muovendosi in orizzontale, verticale o diagonale in base ai valori di sv e so (spost. vert. e orizz. */ int controlla (char p[], int i, int j, int sv, int so); int main() { char parola[N+1]; // +1 per il terminatore int i, j, hor = 0, ver = 0, dia = 0; printf("\nInserisci la parola da cercare (max %d caratteri) : ", N); scanf("%s", parola); /* Per ogni carattere di puzzle controllo le 8 direzioni, sommando lesito (1, 0) al contatore opportuno (2 controlli per hor, 2 per ver, 4 per dia) */ for ( i=0 ; i<N ; i++ ) for ( j=0 ; j<N ; j++ ) { hor += controlla(parola, i, j, 0, 1) + controlla(parola, i, j, 0,-1); ver += controlla(parola, i, j, 1, 0) + controlla(parola, i, j,-1, 0); dia += controlla(parola, i, j, 1, 1) + controlla(parola, i, j,-1,-1) + controlla(parola, i, j, 1,-1) + controlla(parola, i, j,-1, 1); } if ( hor + ver + dia > 0 ) printf("H: %d V: %d D: %d\n", hor, ver, dia); else printf("La parola non si trova\n"); return 0; } int controlla (char p[], int i, int j, int sv, int so){ int len = strlen(p), k = 0; while ( i>=0 && j>=0 && i<N && j<N && k<len ) { // Esce se usciamo dai bordi o if ( p[k++] != puzzle[i][j] ) // se abbiamo conrollato tutta p return 0; // se il k-esimo diverso, return 0 ! i += sv; j += so; // modifichiamo pure i e j cono copie! } return k == len; // Se arriva qui, i primi k caratteri di p } // sono stati trovati in sequenza nella martice, // a partire dalla cella [i,j] e nella direzione // indicata da sv e so, quindi la parola c' tutta // se e solo se k pari alla lunghezza di p

Esercizio 6 (Facoltativo) - Analisi di codice offuscato - ( bonus libero )


Si dica quale propriet di due numeri interi verificata dalla funzione verificaProp, argomentando brevemente la risposta. Si discutano anche le garanzie di terminazione della funzione.
int verificaProp( int a, int b ) { int c; if ( a < 0 ) a = -a; if ( b < 0 ) b = -b; if ( a < b ) { c = a; a = b; b = c; } while ( a > b ) a -= b; /* a riassegnato al suo valore assoluto */

/* b riassegnato al suo valore assoluto */

/* se b maggiore di a, scambia i valori di a e b */

/* a questo punto, quindi, certamente a >= b */ /* sottrae b ad a finch non diventa a <= b */

return ( a == b ); /* restituisce 1 se sono diventati uguali, 0 se diversi */ }

Soluzione attesa:

Se i parametri sono uguali oppure entrambi diversi da 0, la funzione verifica se uno dei parametri
multiplo dellaltro, provando a ridurre tramite sottrazioni successive il modulo del parametro maggiore (in valore assoluto) al modulo dellaltro parametro. La funzione NON termina se uno dei parametri pari a zero e laltro no (in tal caso, infatti, entra nel ciclo while e non ne esce pi). Dato che 0 multiplo di ogni numero relativo, per implementare in modo ineccepibile la verifica della propriet il programmatore avrebbe potuto/dovuto iniziare la funzione con
if ( a==0 || b==0 return 1; )

Giustificazione analitica: Dopo le prime tre istruzioni if, a e b rappresentano i valori assoluti dei parametri, in ordine descrescente ( a >= b ). Continuando a decrementare a del valore di b, solo due situazioni possono verificarsi al momento delluscita del ciclo (che avviene a patto che non sia a 0 e b = 0): 1. a diventa UGUALE a b, e quindi i parametri iniziali erano uno multiplo dellaltro. Si noti che se a e b sono uguali da subito tutte le condizioni precedenti alla return sono false, e il valore restituito correttamente 1. 2. a diventa MINORE di b, e i parametri iniziali non erano uno mltiplo dellaltro. In particolare, in questo caso a rappresenta il resto della divisione intera tra il maggiore (in modulo) tra i parametri e il minore (in modulo) tra i parametri.

Informatica A a.a. 05/06 Seconda Prova in Itinere 30/1/2006 Esercizio 7 - Sintesi di codice : Algoritmica di base e gestione dei file - ( 4,5 punti )
Si codifichi in linguaggio C la funzione di prototipo: int conta( char * filename, int * words, int * lines ); che: apre in lettura il file a caratteri di nome filename, se loperazione riesce lo scandisce, restituisce tramite i parametri il numero di linee e il numero di parole contenute nel file e infine lo chiude al termine dellesecuzione. La funzione restituisce 0 se lapertura e la lettura del file vanno a buon fine, 1 in caso di errore. Si conti una nuova linea ogni volta che si incontra il carattere \n e si conti come parola ogni successione di uno o pi caratteri diversi da \n e spazio (una parola, quindi, pu essere terminata sia da uno spazio sia dal carattere \n in finale di linea). Inoltre, la fine del file costituisce un ulteriore (ovvio) terminatore per lultima parola e lultima linea, se non diversamente terminate. Prima di scrivere il codice C (3 punti), si descriva brevemente ma in modo preciso la logica alla base dellalgoritmo scelto per implementare la funzione (1,5 punti).

ATTENZIONE a non perdersi nei dettagli: si badi soprattutto al cuore dellalgoritmo (c altro, dopo!)

Scandisco il file un carattere alla volta, incrementando il contatore di linee ad ogni \n e quello delle parole ad ogni spazio o \n non preceduto da un altro spazio o da un altro \n. Nella scansione, che termina quando leggo EOF, memorizzo anche il carattere precedentemente letto, in modo da poter ragionare anche sullultimo carattere, quando questo non sia n \n n uno spazio. In questo caso aggiungo una ulteriore linea se lultimo carattere non un \n e una parola se non uno spazio. Leggere il file una linea per volta non in generale un buon approccio se non si sa dimensionare a priori il buffer di lettura della linea (a meno di compensare controllando a ogni lettura, ad esempio effettuata con fgets(), se lultimo carattere valido letto sia un \n, per capire se finita la linea). [La soluzione proposta, testata al calcolatore, pi raffinata di quella attesa da 20 minuti di lavoro]
int conta (char * filename, int * words, int * lines) { char c, cprec; /* Caratteri corrente e precedente FILE * fp = fopen (filename, "r+"); /* Apertura del file if ( fp == NULL ) return -1; /* Errore *words = *lines = 0; /* Se c' il file, azzero i contatori cprec = c = fgetc( fp ); /* Legge il primo carattere while ( c != EOF ) { /* Fino alla fine del file if ( c == '\n' ) (*lines)++; /* Ogni '\n' una nuova linea if ( ( c==' ' || c=='\n' ) && /* Se c uno spazio oppure '\n' e il cprec != ' ' && cprec!='\n' ) /* cararrere precedente invece no, (*words)++; /* allora abbiamo una nuova parola cprec = c; /* Memorizza il carattere precedente c = fgetc( fp ); /* E passa al successivo } if ( cprec != EOF ) { if ( cprec != '\n' ) { (*lines)++; if ( cprec != ' ' ) (*words)++; } } fclose(fp); return 0; } */ */ */ */ */ */ */ */ */ */ */ */

/* Se il file non vuoto */ /* se non termina con \n c' un'altra */ /* linea da contare (senza '\n') */ /* e l'ultima parola pu non essere */ /* seguita da uno spazio */

Esercizio 8 - Analisi di codice : puntatori, funzioni, parametri, ricorsione - ( 3,5 punti )


Si consideri il seguente programma C, completando la definizione di MATR con la propria matricola. Il programma (che ha una funzione ricorsiva) confronta le cifre della matricola in posizioni simmetriche rispetto al centro, e in base allesito dei confronti stampa dei caratteri e a volte incrementa un intero.
#define MATR

"......"

/* Ad esempio, "623372"

*/

void f( char * c1, char * c2, int * n ) { *n = *n + 1; /* Innanzitutto incrementa n (dereferenziato) if ( c1 >= c2 ) return; else f( c1+1, c2-1, n ); if ( *c1 >= *c2 ) { c1 = c2; *n = *n + 10; } printf("%c", *c1); return; } int main() { char mat[7] = MATR; int cont = 0; printf("%s - ", mat); f( mat, mat+5, &cont ); printf(" - %d", cont); return 0; }
/* 6 caratteri per la matricola e 1 per il '\0' /* Se ... (attenzione a cosa si confronta) /* termina e restituisce il controllo al chiamante /* Altrimenti effettua una chiamata ricorsiva in /* cui ... i puntatori ... (attenzione a + e -)

*/ */ */ */ */

/* Se ... (attenzione a cosa si confronta), allora */ /* esegue due assegnamenti (ancora attenzione a *) */

/* Quindi stampa a video il carattere ...

*/

/* Termina e restituisce il controllo al chiamante */

*/

/* stampa per prima la matricola e effettua una */ /* chiamata ricorsiva passando primo e ultimo char */

a) Indicare la riga di output del programma su stdout, simulandone lesecuzione (si presti attenzione a impilare bene le chiamate e a considerare quale attivazione ogni volta risvegliata si legga anche domanda b) prima di iniziare, per non fare il lavoro 2 volte. (2 punti):

Nel caso dellesempio: 623372 322 24


b) Completare la rappresentazione dello stack e dei valori delle variabili nei record di attivazione nel momento in cui eseguita listruzione return; individuata, nel codice, dalla freccia Si noti che lo stack in figura, che cresce verso lalto, gi contiene i record del main e della prima attivazione di f, senza valori per le variabili, ad eccezione dei puntatori c1 e n, per esemplificare la notazione da usare per gli altri puntatori) (1,5 punti)

c1 c2 n c1 c2 n c1 c2 n c1 c2 n mat ... cont ...

Ambiente del main


mat 6 2 3 3 7 2 \0

cont 4

Esercizio 9 - Memoria Dinamica - ( 6 punti )


Si consideri una lista concatenata semplice di parole. La lista rappresenta un indice di parole, elencate in ordine alfabetico e accompagnate da un numero, che rappresenta la pagina in cui la parola compare per la prima volta in un libro. La struttura della lista :
typedef struct Guscio { char * parola; int pagina; struct Guscio * next; typedef Nodo * Indice;

} Nodo;

(a) Si implementi la funzione di prototipo void scambiaPrimeParole( Indice * idx ); che danneggia lindice ordinato scambiando tra loro i primi due nodi (se presenti). (2 punti)

La codifica davvero semplice e immediata, ricordando di dereferenziare idx


viod scambiaPrimeParole( Indice * idx ) { Indice sec; if ( *idx == NULL || (*idx)->next == NULL ) /* Se ha meno di due nodi return; /* non c niente da fare sec = (*idx)->next; /* Metto al sicuro un ptr al secondo nodo (*idx)->next = sec->next; /* Il primo ora ha il terzo come sucessivo /* Il secondo ora ha il primo come successivo sec->next = *idx; *idx = sec; /* Aggiorno il valore del parametro return; }

*/ */ */ */ */ */

(b) Si dica brevemente (ma in modo preciso) qual leffetto della funzione seguente (2 punti) viod funzione( Indice idx ) { Indice idy = idx; if ( idy == NULL || idy->next == NULL ) return; while ( idx->next->next != NULL ) { idx = idx->next; } idx->next = idy; return; }

La funzione lascia inalterato ogni indice che contiene meno di due parole. Su ogni altro indice raggiunge il penultimo nodo e rende ciclica la lista collegando tale penultimo nodo con la testa, e tagliando fuori quindi lultimo nodo, che diventa garbage.

(c) Bivio: esercizio a scelta (2,5 punti): si risolva uno solo, a scelta dei seguenti esercizi [il primo certamente pi breve, ma potrebbe non piacere a chi non si trova a suo agio con la ricorsione]. 1) Si codifichi la funzione ricorsiva di prototipo int trovaPagina( Indice idx, char * lemma ); che restituisce la pagina relativa alla parola lemma se questa nellindice, oppure 1 se non nellindice, badando ad attraversare in entrambi i casi solo la porzione necessaria della lista.
int trovaPagina( Indice idx, char * lemma ) { if ( idx == NULL || strcmp(idx->parola, lemma) > 0 ) return 1; if ( strcmp(idx->parola, lemma) == 0 ) return idx->pagina; else return trovaPagina(idx->next, lemma); }

2)

Si codifichi la funzione cleanup di prototipo void cleanup ( Indice * idx ); Indice cleanup ( Indice idx );

oppure, a scelta, di prototipo

che elimina dallindice idx (deallocandoli) tutti i nodi che contengono informazioni non valide (puntatori a NULL o stringhe di lunghezza zero come parole, valori negativi o nulli come numeri di pagina), lasciando gli altri nodi correttamente concatenati. Nel caso del primo prototipo la testa della lista passata per indirizzo, nel caso del secondo la funzione restituisce un puntatore alla testa della lista modificata. void cleanup (Indice *idx) { Indice aux,prec,punt; prec = NULL; punt = (*idx); while (punt != NULL) { if (punt->parola==NULL||strlen(punt->parola)==0||punt->pagina<=0){ if (prec == NULL){ aux = punt; punt = punt->next; free(aux); (*idx) = punt; }else { aux = punt; prec->next = punt->next; punt = punt->next; free(aux); } } else{ prec = punt; punt = punt->next; } } // end while } Indice cleanup( Indice idx ) { /* Per laltro prototipo si propone */ Indice aux; /* una soluzione... ricorsiva! */ if (idx == NULL) return idx; if (punt->parola==NULL||strlen(punt->parola)==0||punt->pagina<=0){ aux = idx; idx = cleanup(idx->next); free(aux); } else p->next = cleanup(p->next); return idx; }

Facoltativo Alberi ternari - ( incremento libero )


Un albero ternario una struttura dati ricorsiva in cui ogni nodo ha esattamente tre sotto-alberi. (a) Si proponga la definizione in C di un tipo di dato ricorsivo che definisca un albero ternario in cui ogni nodo, oltre ai sottoalberi, contenga anche due puntatori a carattere e due interi, che rappresentino due parole p1 e p2 e due numeri di pagina n1 e n2 (le pagine in cui p1 e p2 rispettivamente compaiono per la prima volta nello stesso libro dellesercizio 3).

Questa parte assolutamente standard


typedef struct Gemma { char * parola1, * parola2; int pagina1, pagina2; struct Gemma * left, * center, * right; } Nodo; typedef Nodo * Albero;

(b) Una simile struttura efficace nel rappresentare un indice ordinato se, per ogni nodo, si garaniscono le seguenti propriet: (1) il sottoalbero di sinistra contiene solo parole alfabeticamente precedenti rispetto a p1; (2) p1 precede alfabeticamente p2; (3) il sottoalbero centrale contiene solo parole comprese tra p1 e p2; (4) il sottoalbero di destra contiene solo parole maggiori di p2. Si codifichi una versione della funzione trovaPagina che operi, in queste ipotesi, sullalbero definito al punto (a), e svolga lo stesso compito dellomonima funzione dellesercizio 3c. Si discuta eventualmente la sua efficienza in confronto a quella dellesercizio 3, immaginando il caso di indici molto grandi e ragionevolmente bilanciati nella distribuzione delle parole sui vari rami.
int trovaPagina( Albero a, char * lemma ) { if ( a == NULL ) return 1; if ( strcmp(a->parola1, lemma) > 0 ) return trovaPagina(a->left, lemma); if ( strcmp(a->parola1, lemma) == 0 ) return a->pagina1; if ( strcmp(a->parola2, lemma) > 0 ) return trovaPagina(a->center, lemma); if ( strcmp(a->parola2, lemma) == 0 ) return a->pagina2; else return trovaPagina(a->right, lemma); }

Consideriamo un indice di N nodi. Il costo della ricerca con questa soluzione pari (al pi) alla massima profondit dellalbero. Se lalbero pieno ad ogni livello (pieno e bilanciato, cfr. es c), tale lunghezza esattamente log3 N (la lunghezza di tutti i cammini dalla radice alle foglie). In molti casi di bilanciamento medio comunque ragionevole ritenere che la profondit massima cresca logaritmicamente con N (in realt caratterizzare il caso medio non banale, ma [intuitivamente] vero se la larghezza dei livelli cresce al crescere della profondit). Nel caso della lista, invece, il caso medio richiede N/2 accessi (non c variabilit strutturale, e i valori sono mediamente collocati al centro della lista). Confrontando queste due stime, si nota che il rapporto (N/2)/(log N) diverge al crescere di N, e il guadagno della soluzione ad albero cresce esponenzialmente con N. Questo vantaggio [per dirla tutta] si paga per con una maggiore complessit delle operazioni di inserimento e cancellazione dei nodi, se si vuole garantire che esse lascino lalbero su cui agiscono ordinato e bilanciato [il che pu richiedere pesanti ristrutturazioni].

(c) Un albero ternario si dice pieno e bilanciato se (1) vuoto oppure (2) tutti i suoi rami hanno la stessa profondit e ogni livello pieno, cio ogni nodo non-foglia ha tutti i sottoalberi non degeneri. semplice calcolare il numero di nodi degli alberi pieni e bilanciati: i diversi alberi, identificati univocamente dalla loro profondit n, hanno f(n) = ni=0 3i nodi. Si dia una definizione ricorsiva precisa (e/o la si codifichi in C) della funzione pb(...) che restituisce 1 se lalbero passato come parametro pieno e bilanciato, zero altrimenti. Seguono le raffigurazioni dei primi alberi pieni e bilanciati in ordine di dimensione: NULL

(0)

(1)1 nodo

(2)4 nodi

(3)13 nodi

(4)40 nodi...

Istintivamente si dice un albero pb se tutti i suoi sottoalberi sono pb, quindi riduciamo ricorsivamente al caso base. Queta condizione necessaria, ma non sufficiente. Infatti lalbero disegnato sotto NON pb, anche se tutti e tre i suoi sottoalberi lo sono.

Occorre quindi imporre anche che i tre sottoalberi abbiano la stessa profondit, ad esempio con un parametro che, quando la risposta positiva (return 1;) restituisca al chiamante la profondit dellalbero corrente:
int pb ( Albero a, int * livello ) { int lr, lc, ll; if ( a == NULL ) { *livello = 0; return 1; } else if ( pb(a->left, &ll) && pb(a->center, &lc) && pb(a->right, &lr) && ll == lc && lc == lr ) { *livello = lr + 1; return 1; } return 0; }

/* Il livello dei 3 sottoalberi /* Un albero vuoto... /* ...ha profondit 0... /* ...ma pb! /* Se i tre sottoalberi sono... /* ...tutti pb e le loro... /* ... profondit restituite /* ...sono tutte uguali /* [o lc o ll...tanto sono uguali] /* ...allora pb! /* ...altrimenti no.

*/ */ */ */ */ */ */ */ */ */ */

Quando la funzione restituisce 0 il valore del livello non significativo, mentre quando la funzione restituisce 1 indica la profondit dellalbero pieno e bilanciato.

Informatica A a.a. 05/06 Primo Appello 20/2/2006 Esercizio 10 - Algebra di Boole, Aritmetica Binaria e Codifica delle Informazioni ( 4 punti )
(c) Si costruisca la tabella di verit della seguente espressione booleana e se ne derivi la tabella per il caso in cui sia imposto B=1. (1,5 punti) ( not A and C ) or not ( A or not ( B and not C ) )
1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 1 0 1 0 1 0 1 0 1 0 1 0 0 1 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 0 1 0 0 1 1 0 0 1 1 0 0 1 0 0 0 1 0 1 0 1 0 1 0 1 0 0 1 0 1 0 1 0 1

con altra notazione: AC + (A + BC)

Per derivare la tabella corrispondente al caso B=1 sufficiente estrarre dalla tabella precedente le quattro linee in cui B vale 1 (segnate in corsivo). (d) Si stabilisca il minimo numero di bit sufficiente a rappresentare in complemento a due i numeri A = 63dec e B = 31dec, li si converta, se ne calcoli la differenza (A B) in complemento a due e si indichi se si genera riporto sulla colonna dei bit pi significativi e se si verifica overflow. (1,5 punti) Con 7 bit si rappresentano in C2 i numeri da 64dec a +63dec Col metodo dei resti in binario assoluto, e poi in C2: 63dec = 111111bin = 0111111C2 31dec = 11111bin = 0011111C2 -31dec = 1100000C2 + 0000001C2 = 1100001C2 Applicando lalgoritmo di addizione, ricordando che 6331 si calcola come 6331, e indicando (riporto) e [overflow] con le rispettive parentesi: 0 1 1 1 1 1 1 63dec + 0 0 1 1 1 1 1 31dec = (0) [1] 1 0 1 1 1 1 0 0 1 1 1 1 1 1 1 1 0 0 0 0 1 94dec ?? -34dec !! 63dec -31dec

= (1) [0] 0 1 0 0 0 0 0 32dec Nel primo caso non si genera riporto dalla colonna dei bit pi significativi ma si verifica overflow, perch da due addendi positivi otteniamo un risultato negativo; nel secondo caso si genera riporto sulla colonna dei bit pi significativi, ma non vi overflow (operandi discordi). (e) Chiamiamo S0-4a-c linsieme delle stringhe di lunghezza compresa tra 0 e 4 e composte dai soli caratteri a, b e c. Quante sono le stringhe in S0-4a-c? Qual il numero minimo di bit sufficiente a codificare univocamente gli elementi di S0-4a-c? Si giustifichino le risposte. (1 punto)

Esempi di elementi di S0-4a-c sono le stringhe a, cccc, , ba, aab, ca, ccba, c, ...

Ragionando per enumerazione, c 1 sola stringa di lunghezza 0 (la stringa vuota), 3 stringhe di lungh. 1 (a, b, c), 3x3=9 stringhe di lungh. 2 (aa, ab, ac, ba, bb, bc, ca, cb, cc), 3x3x3=27 stringhe di lungh. 3 (aaa, aab, aac, aba, ccc), e 3x3x3x3=81 di lungh. 4 Linsieme S0-4a-c si compone quindi di 1+3+9+27+81=121 elementi diversi, e per codificarli in modo univoco occorrono 7 bit (che mettono a disposizione 128 codici, di cui sette non utilizzati).

Esercizio 11 - Sintesi di codice: Algoritmica di base e ricorsione semplice - ( 7 punti ) Il seguente programma C legge un intero n da terminale e stampa due volte a video i numeri della serie 11 22 ... ii ... nn, tramite le funzioni f1() e f2(), che hanno lo stesso effetto.
int main () { int n; printf (Inserisci un intero : ) scanf (%d, &n); f1(n); f2(n); }

(a)

Supponendo di disporre della funzione int potenza(int base, int esponente); che realizza lelevamento a potenza intera di un numero intero, si codifichi in C la funzione iterativa f1() (3 punti) Estremamente banale:
void f1 (int n) { int i; for (i=1; i<=n; i++) printf (%d , potenza(i, i)); return; }

(b)

Si codifichi in modo ricorsivo la funzione potenza(), considerando solo il caso in cui sia la base sia lesponente sono strettamente positivi (2 punti) Il caso base chiaramente quello per cui lesponente vale 1: il risultato allora pari alla base. Il passo induttivo si formalizza notando che be = b x be-1 (la ricorsione, cio, si fa solo sullesponente).
int potenza (int b, int e) { if ( e == 1 ) return b; else return ( b * potenza(b, e-1) ); }

(c)

Sempre sfruttando la funzione potenza(), si codifichi in C la funzione ricorsiva f2(), basandola sulla definizione seguente (da completare). Attenzione a stampare i valori nellordine giusto. (2 punti) caso base: se n vale 1, stampa a video ... passo induttivo: se n maggiore di 1 ...

Evidentemente si eseguono le chiamate ricorsive diminuendo il parametro n fino a portarlo a 1. Lunica difficolt consiste nel generare loutput nellordine corretto: tipico, infatti, non badare a mettere listruzione di stampa a video DOPO la chiamata ricorsiva (leffetto di visualizzare la sequenza invertita).
void f2 (int n) { if ( n == 1 ) printf (1 ); else { f2( n-1 ); printf (%d , potenza(n, n)); } return; }

Eccone anche una versione alternativa, equivalente, un po pi compatta:


void f2 (int n) { if ( n > 1 ) f2( n-1 ); printf (%d , potenza(n, n)); return; }
Se si continua sul retro di qualche foglio, indicare dove

Esercizio 12 - Vettori, Liste e Memoria Dinamica - ( 10 punti )


Sia data una lista concatenata semplice non ordinata di interi senza valori duplicati. La struttura :
typedef struct Elem { int dato; struct Elem * next; typedef Nodo * Lista; } Nodo;

La lista rappresenta un insieme di numeri. Una funzione incrocia() riceve come parametri: la lista, un vettore dinamico di interi (anchesso senza duplicati, allocato dal programma chiamante) e la lunghezza di tale vettore dinamico. La funzione rimuove dalla lista originaria (deallocandoli) tutti i valori contenuti nel vettore (se presenti) e aggiunge in coda tutti i valori contenuti nel vettore e non nella lista originaria. (d) Si definisca opportunamente il prototipo della funzione incrocia() e si descriva sinteticamente (ma in modo preciso) come opera un algoritmo che la implementa. In particolare, si badi a evitare che un valore presente nella lista e non nel vettore sia prima aggiunto e poi rimosso (o viceversa) (2 punti)

La lista deve poter essere modificata, quindi passata per indirizzo. Il vettore dinamico, e non deve essere modificato, dunque sufficiente un puntatore a intero. Il prototipo pertanto void incrocia ( Lista * lis, int * v, int lun ) Per effettuare correttamente lincrocio, la funzione pu operare come segue: 1) per ogni valore v[i] del vettore, a) effettua una scansione completa della lista, in cui, per ogni nodo corrente i) se il nodo corrente della lista contiene v[i], lo dealloca e termina la scansione

ii) se si raggiunta la fine della lista, alloca un nuovo nodo col valore v[i] imporante ri-scandire tutta la lista per ogni elemento del vettore, poich i valori non sono ordinati. Lassenza di valori duplicati nel vettore garantisce che uno stesso valore non sia prima aggiunto e poi tolto alla lista o viceversa; lassenza di duplicati nella lista permette di interrompere la scansione. Occorre anche trattare a parte linserimento in testa e la cancellazione nel caso in cui la lista sia vuota:

1) per ogni valore v[i] del vettore, a) se la lista vuota, alloca un nuovo nodo col valore v[i] b) altrimenti, se il primo nodo contiene il valore v[i], dealloca il nodo e termina la scansione c) altrimenti, effettua una scansione completa della lista, in cui, per ogni nodo corrente i) se il nodo corrente della lista contiene v[i], lo dealloca e termina la scansione

ii) se si raggiunta la fine della lista, alloca un nuovo nodo col valore v[i] d) Considera il prossimo elemento del vettore

(e) Si codifichi poi in C la funzione secondo lalgoritmo precedentemente dettagliato (6 punti) Si possono utilizzare le funzioni tipiche di inserimento e cancellazione in testa e in coda, a patto di - riportarne il il prototipo e codificarle a parte, oppure - riportarne il prototipo e fare esplicitamente riferimento a una codifica nota

Io propongo qui una codifica diretta delle due scansioni innestate, con la gestione dei casi particolari.

Se si continua sul retro di qualche foglio, indicare dove

void incrocia ( Lista * lis, int * v, int lun ) { Lista cur, prec; int i, found; for ( i=0; i<lun; i++) { if ( *lis == NULL ) { /* Se la lista vuota */ *lis = (Lista) malloc( sizeof(Nodo) ); (*lis)->dato = v[i]; /* alloco un nodo per v[i] */ (*lis)->next = NULL; } else if ( (*lis)->dato == v[i] ) { /* Se il primo valore v[i] */ cur = *lis; *lis = (*lis)->next; /* Dealloca il primo nodo */ free(cur); } else { cur = (*lis)->next; /* Altrimenti, saltando il primo nodo */ prec = *lis; found = 0; /* v[i] non ancora trovato */ while ( !found && cur != NULL ) { /* Scandisco la lista */ if ( cur->dato == v[i] ) { prec->next = cur->next; /* Dealloco il nodo se trovo */ free(cur); /* v[i] e assegno found = 1 */ found = 1; /* e interrompo la scansione */ } else { prec = cur; cur = cur->next; /* Passa al nodo successivo */ } } if ( !found ) { prec->next = (Lista) malloc( sizeof(Nodo) ); prec->next->dato = v[i]; prec->next->next = NULL; /* Ins. in coda, se !trovato */ } } } return; } (f) Si dica infine brevemente (ma giustificando in modo preciso la risposta) qual leffetto sulla lista della funzione seguente. (2 punti) void funzione( Lista x ) { Lista y = x; if ( x == NULL ) return; while ( x->next != NULL ) { if ( x->dato < y->dato ) { int tmp = x->dato; x->dato = y->dato; y->dato = tmp; } x = x->next; } funzione( y->next ); return; }

In sintesi (minima risposta sufficientemente precisa): La funzione opera (ricorsivamente) una scansione iterativa di tutti i suffissi della lista inizialmente passata come parametro, portando, ad ogni scansione, in testa al suffisso il dato minore; leffetto globale quello di ordinare la lista in modo crescente.

Analiticamente: una funzione ricorsiva che non fa nulla se la lista vuota (caso base). Se la lista non vuota, la funzione scandisce (iterativamente) tutta la lista scambiando il dato del primo elemento e il dato dellelemento corrente ogni volta che lelemento corrente contiene un dato minore (y continua a puntare al primo elemento della lista passata come parametro). Alla fine delliterazione, dunque, il primo elemento della lista passata come parametro contiene il dato minimo. Portato lestremo in prima posizione, la funzione procede ricorsivamente sulla parte restante della lista, sicch ad ogni chiamata ricorsiva literazione collocher al suo posto il valore minimo residuale della lista, fino a ordinarla in modo crescente.
Se si continua sul retro di qualche foglio, indicare dove

Esercizio 13 - Analisi di codice: puntatori, funzioni, parametri, ricorsione - (4 punti )


Si consideri il seguente programma C, completando la definizione di MATR con la propria matricola.
#define MATR

"......"

/* Ad esempio, "623372"

*/ */ */ */ */

void f( char ** p ) { int lun; *p = *p + 2; lun = strlen( *p ); if ( lun == 0 ) return; else { printf("%d - %s - ", lun, *p); f( p ); } return; } int main() { char mat[7] = MATR; char * v = mat; f( &v ); printf("%d", strlen( v )); return 0; }

/* ATTENZIONE: p un doppio puntatore /* ATTENZIONE alleffetto di questo incremento /* Assegna a lun la lunghezza di ... /* Se ... restituisce il controllo al chiamante

/* Altrimenti stampa un intero... e una stringa... */ /* e si richiama ricorsivamente */


/* Termina e restituisce il controllo al chiamante */

/* Sei caratteri per la matricola, uno per il '\0' */ /* I nomi degli array sono costanti di tipo... */ /* Chiamata alla funzione ricorsiva passando... */

c)

Indicare la riga di output stampata dal programma, simulandone lesecuzione (si presti attenzione a impilare e disimpilare bene le chiamate ricorsive e a considerare tutte le chiamate alla funzione printf, che compare in due punti del codice). (2 punti)

[con la matricola 623372] ->


d)

4 3372 2 72 0

Completare la rappresentazione grafica dello stack e dei valori delle variabili nei record di attivazione nel momento in cui eseguita listruzione return; individuata, nel codice, dalla freccia . Suggerimenti: il momento in cui lo stack raggiunge la massima altezza e in cui si raggiunge il caso base della ricorsione; utilizzare le frecce per rappresentare i valori delle variabili di tipo puntatore; inserire i valori interi e i caratteri direttamente nei rettangolini che rappresentano le variabili. (2 punti)

p lun p lun p lun 4 2 0 mat 6 2 3 3

Ambiente del main


7 2 \0

Record di attivazione della prima invocazione di f

v
V passato alla funzione f per indirizzo, quindi tutte le le istanze di p sono doppi puntatori, che puntano a v

Record di attivazione del main

mat ... v ...

Se si preferisce una notazione diversa, utilizzare il retro del foglio

Esercizio 14 Facoltativo Alberi ( incremento libero )


Si consideri un albero binario rappresentato dalla seguente definizione ricorsiva:
typedef struct Elemento { struct Elemento * left, * right; } Nodo; typedef Nodo * Tree;

(d) Si dica, giustificando molto sinteticamente la risposta, quali propriet di un albero sono verificate dalle funzioni booleane d() e p(). N.B. d() invoca solo se stessa, mentre p() invoca anche d(). typedef enum {false, true} bool; bool d( Tree t ) { if ( t == NULL ) return false; else return ( d(t->left) == d(t->right) ); } bool p( Tree t ) { if ( t == NULL ) return true; else return ( p(t->left) == d(t->right) ); }

La funzione d() restituisce true se il numero di nodi di t dispari, false altrimenti (cio se il numero di nodi pari). La funzione p() restituisce true se il numero pari, false altrimenti (cio se dispari).

Ragioniamo induttivamente. Se lalbero vuoto (0 nodi, pari), la funzione d() restituisce false. Se lalbero ha 1 nodo (dispari), la funzione restituisce true (perch entrambi i sottoalberi restituiscono false). Se lalbero genericamente non vuoto, la funzione restituisce true se i suoi sottoalberi restituiscono la stessa risposta, cio se sono entrambi pari o entrambi dispari. La somma di due numeri pari pari, come anche la somma di due numeri dispari, ma c anche, daq contare, il nodo radice, quindi la funzione d() dice se il totale dei nodi dellalbero dispari. La funzione p() restituisce true se lalbero banalmente pari, oppure se i due sottoalberi dx e sx sono uno pari e uno dispari (non importa quale!): la somma di un pari e un dispari sempre dispari, e contando anche la radice abbiamo pari. Se sono entrambi pari o entrambi dispari, p() restituisce false.
(e) Si spieghi (brevemente e qualitativamente) quali condizioni, in generale, garantiscono che le funzioni ricorsive non entrino in una sequenza infinita di invocazioni mutue. Si dica poi se possibile affermare con certezza che d() e p() terminano sempre, eventualmente specificando le condizioni necessarie a garantirlo. In estrema sintesi, un insieme di funzioni ricorsive (cio che si invocano reciprocamente) termina certamente se garantito che si raggiunga il caso base (o i casi base) per ogni possibile combinazione dei parametri. In pratica occorre garantire che ogni passo induttivo costituisca un effettivo avvicinamento a un caso base, e quindi una semplificazione del problema. Nel caso di p() e d() il caso base rappresentato da un albero vuoto, e tutte le chiamate ricorsive sono effettuate su un sottoalbero dellalbero passato come parametro, dunque esse terminano per ogni albero (di dimensione finita!) passato come parametro, per evidente riduzione strutturale.

A rigore, andrebbe specificato che lalbero sia anche ben formato, e in particolare che nessun puntatore sia assegnato allindirizzo di un nodo antenato del nodo da cui tale puntatore esce (o al nodo stesso, o comunque in modo da formare dei cicli di puntatori). I semplici alberi malformati rappresentati a fianco, infatti, sono tali per cui non si raggiunge mai il caso base, ma si continuano a generare chiamate ricorsive a causa dei cicli costituiti dagli archi in neretto.
(f) La funzione p2(), qui sotto specificata, equivalente a p()? Perch? bool p2( Tree t ) { return ! d(t); } La funzione p2() equivalente a p() perch: (a) nel caso base restituisce lo stesso risultato, e (b) nel passo induttivo vediamo che p() restituisce p(t->left)==d(t->right), che, nellipotesi che p2() e p() siano equivalenti, si pu scrivere come p2(t->left)==p(t->right), ossia come !d(t->left)==p(t-> right), che nel dominio booleano equivale a !( d(t->left)==p(t->right) ) e quindi a !d(t).

Informatica A a.a. 05/06 Secondo Appello 21/7/2006 Esercizio 15 - Algebra di Boole, Aritmetica Binaria ( 4,5 punti )
(f) Si costruisca la tabella di verit della seguente espressione booleana, prestando attenzione alla precedenza tra gli operatori logici. Eventualmente si aggiungano le parentesi. [1,5 punti] A and B and not C or A and not C or not A or C
con altra notazione:

ABC+AC+A+C

Per indicare chiaramente la soluzione, scegliamo una strategia associativa (ad es. a sinistra). (((A and B) and not C) or (A and not C)) or (not A or C)
1 1 1 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 1 0 0 1 1 0 0 0 1 0 0 0 0 1 0 1 0 1 0 1 0 0 1 0 1 0 1 0 1 0 0 0 1 1 0 1 0 0 0 0 0 1 1 1 1 0 0 0 0 1 0 1 0 1 0 1 0 1 0 1 0 0 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 0 1 0 1

Lespressione proposta quindi una tautologia. Si poteva anche notare da subito [DeMorgan] che: (A and not C) or (not A or C) = (A and not C) or not (A and not C) che unistanza del ben noto schema di tautologie X or not X (g) Si stabilisca il minimo numero di bit sufficiente a rappresentare in complemento a due i numeri A = 66dec e B = 22dec, li si converta, se ne calcolino la somma (A + B) e la differenza (A B) in complemento a due e si indichi se si genera riporto sulla colonna dei bit pi significativi e se si verifica overflow. [2 punti] Con 8 bit si rappresentano in C2 i numeri da 128dec a +127dec; 7 bit non sono sufficienti per il 66dec Col metodo dei resti in binario assoluto, e poi in C2: 66dec = 1000010bin = 01000010C2 22dec = 10110bin = 00010110C2 -22dec = 11101001C2 + 0000001C2 = 11101010C2 Applicando lalgoritmo di addizione, ricordando che 66-(-22) si calcola come 66+22, e indicando (riporto) e [overflow] con le rispettive parentesi: 0 1 0 0 0 0 1 0 66dec + 1 1 1 0 1 0 1 0 -22dec = (1) [0] 0 0 1 0 1 1 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 1 1 0 44dec 66dec 22dec

= (0) [0] 0 1 0 1 1 0 0 0 88dec Nel primo caso si genera riporto dalla colonna dei bit pi significativi ma non si verifica overflow (addendi discordi); nel secondo caso non si genera riporto e non vi overflow. (h) Il programma C a lato somma a due valori diversi (a, b) la stessa quantit K (valori float, in virgola mobile). impossibile, possibile oppure certo che sia eseguita la seconda istruzione printf()? Si giustifichi (molto brevemente) la risposta. [1 punto] Nella rappresentazione in virgola mobile le cifre a disposizione per la mantissa sono limitate, e la precisione della rappresentazione diminuisce man mano che il modulo del valore si allontana da zero.
int main () { float a = 0.000001; float b = 0.000002; const float K = 1000000.0; if ( a != b ) printf("Sono diversi\n"); a = a + K; b = b + K; if ( a == b ) printf("Sono uguali\n"); return 0; }

quindi possibile che i valori di a e b siano distinti prima della somma di K e uguali dopo, per intervenuto errore di approssimazione. Non certo, poich il numero di bit per il tipo float non specificato esattamente dallo standard ANSI C. [Ad es. Dev-C++ 4.0 esegue entrambe le printf()]

Esercizio 16 - Sintesi di codice: Algoritmica di base ( 7 punti )


Definiamo il grado minimo g e il grado massimo G di una parola P rispettivamente come il minimo e il massimo numero di occorrenze delle lettere di P in P. Ad esempio:
ISTANBUL BOSFORO GALATASARAY MARMARA g=1, g=1, g=1, g=2, G=1 G=3 G=5 G=3 ( ( ( ( tutte le lettere della parola compaiono in essa una e una sola volta ) B, S, F, R compaiono una sola volta, O compare tre volte ) G, L, T, S, R, Y compaiono una sola volta, A compare cinque volte ) M e R compaiono due volte, A compare tre volte )

G e g valgono convenzionalmente 0 per la parola vuota (cio per una parola priva di caratteri). (a) Si dichiari opportunamente il prototipo di una funzione C gradi() che riceve come parametro una stringa di lunghezza generica che rappresenta P, calcola G e g, e li comunica al programma chiamante. Si completi poi il main riportato qui sotto con lopportuna chiamata alla funzione dichiarata. [1 punto] Scelgo di passare entrambi gli interi per indirizzo void gradi (char *P, int *g, int *G)
} int main() { int max, min; char par[14]="PASSAMONTAGNA";

gradi( par, &min, &max );


printf("%s: g=%d, G=%d",par,min,max); return 0;

(b)

Si descriva sinteticamente (ma in modo preciso, possibilmente per punti) un algoritmo per la funzione
gradi() adatto ad essere codificato. Si trascuri la gestione dei caratteri maiuscoli e minuscoli. [2 punti]

1. Inizializzo i gradi in modo conservativo: G=0, g=lunghezza di P. Certamente infatti G non pu essere minore, e g non pu essere maggiore. Inoltre, se la parola vuota, sono valori corretti (lunghezza = 0) 2. Per ogni lettera P[i] della parola, a. Inizializzo a 0 un contatore delle occorrenze di P[i] b. Scandisco nuovamente dallinizio la parola, e per ogni lettera P[j] i. Se P[i] uguale a P[j] incremento il contatore. N.B.: ogni lettera confrontata anche con se stessa (caso i=j), quindi count incrementato almeno una volta per ogni lettera. c. Se le occorrenze di P[i] sono meno di g, aggiorno g al valore del contatore d. Se le occorrenze di P[i] sono pi di G, aggiorno G al valore del contatore 3. Gli aggiornamenti di G e g avvengono direttamente sulle variabili nellambiente del programma chiamante, quindi la funzione pu terminare senza fare altro. (c) Si codifichi in C la funzione dichiarata al punto (a) secondo lalgoritmo descritto al punto (b). [4 punti ]
void gradi (char * p, int * g, int * G) { int i, j, count, lun = strlen(p); *g = lun; *G = 0; for ( i=0; i<lun; i++ ) { count = 0; for ( j=0; j<lun; j++ ) { if ( p[i] == p[j] ) count++; } if ( count < *g ) *g = count; if ( count > *G ) *G = count; } return; }

Esercizio 17 - Funzioni e Memoria Dinamica - ( 9 punti )


Il dispositivo di cronometraggio di una gara a tappe genera, allarrivo di ogni tappa, una lista dinamica con i numeri di maglia e i tempi di tappa dei vari concorrenti. Unaltra lista conserva la classifica generale della gara, con i numeri di maglia e il tempo totale di percorrenza. I dati hanno la seguente struttura:
typedef struct { int ore; int min; int sec; int cent; } Tempo; typedef struct Elem { int concorrente; Tempo t_tappa; struct Elem * next; } Arrivo; typedef Arrivo * Tappa; typedef struct Nodo { int concorrente; Tempo t_totale; struct Nodo * next; } Posizione; typedef Posizione * Classifica;

Il dispositivo di cronometraggio genera la lista in ordine di arrivo (e quindi anche in ordine crescente di tempo) aggiungendo in coda nuovi elementi man mano che arrivano i concorrenti. (g) [4 punti ] Si implementino, dopo averne definito precisamente i prototipi, le funzioni
sum(Tempo t1, Tempo t2, ); restituisce il tempo somma t = t1 + t2 cmp(Tempo t1, Tempo t2, ); restituisce 0 se t1 = t2, un intero positivo se t1 > t2, negativo altrimenti

Suggerimento: si implementino e utilizzino le due funzioni long int TempoCent(Tempo t) e Tempo CentTempo(long int t) per convertire un Tempo in centesimi di secondo e viceversa.

La funzione sum() pu restituire il tempo somma tramite return, cos non sono necessari altri parametri. Per cmp() si pu restituire direttamente il valore in millisecondi (con segno) della differenza tra t1 e t2. Le funzioni di conversione sfruttando troncamento e resto della divisione tra interi, senza difficolt. long int TempoCent( Tempo t ) long int tmp = t.cent 100 * t.sec (60*100) * t.min (60*60*100) * t.ore; return tmp; } { + + + Tempo CentTempo( Tempo tmp; tmp.ore = t / t = t % tmp.min = t / t = t % tmp.sec = t / tmp.cent = t % return tmp; } long int t ) { (60*60*100); (60*60*100); (60*100); (60*100); 100; 100;

Tempo sum( Tempo t1, Tempo t2 ) { return CentTempo( TempoCent(t1) + TempoCent(t2) ); } long int cmp( Tempo t1, Tempo t2 ) { long int tmp1 = TempoCent(t1) long int tmp2 = TempoCent(t2); return temp1 temp2; }

(h) [5 punti ] Supponendo di disporre di: (1) le funzioni di ordinamento Tappa ordTappaM( Tappa t ) e Classifica ordClassM( Classifica c ), che ordinano le liste t e c in ordine crescente di numero di maglia; (2) la funzione Classifica ordClassT( Classifica c ), che restituisce c ordinata in base al tempo totale crescente (campo t_totale); (3) le funzioni definite al punto (a), si progetti la funzione aggiorna() che aggiorna la classifica generale operando come segue: riceve come parametri la lista con gli arrivi dellultima tappa, cos come generata dal dispositivo di cronometraggio, e la lista che rappresenta la classifica generale; aggiunge al tempo totale di ogni concorrente il tempo conseguito nellultima tappa; elimina dalla classifica generale, deallocandoli, gli eventuali concorrenti non arrivati in fondo alla tappa (ritirati), agendo direttamente sulla lista originale (si perdono, quindi, i loro dati); riordina la classifica generale in base ai nuovi tempi totali. Si definisca precisamente il prototipo [1 punto] della funzione aggiorna(), si descriva sinteticamente e per punti un possibile algoritmo [1 punto] per implementarla, e lo si codifichi in C [3 punti]. Suggerimento: conviene ordinare le liste in base allo stesso criterio per poi scandirle in parallelo.
void aggiorna( Tappa t, Classifica *c ) La classifica direttamente modificata, quindi c passato per indirizzo (doppio puntatore a Posizione). 1) Ordina le liste ricevute in ingresso in base al numero di maglia dei concorrenti cos non occorre alcuna funzione di ricerca nella lista per trovare i concorrenti 2) Gestisce leventuale cancellazione del primo nodo della classifica, deallocando i nodi della classifica finch non si raggiunge il primo concorrente arrivato nella tappa. 3) Scandisce in parallelo le parti restanti delle due liste a. segnalando un errore se arrivato nella tappa un concorrente non presente in classifica generale b. avanzando in entrambe le liste e sommando i tempi se il numero di maglia corrisponde c. deallocando il concorrente ritirato e avanzando solo nella classifica se 4) Ordina la classifica generale in base al tempo void aggiorna( Tappa t, Classifica *c ) { Classifica cur, tmp; t = ordTappaM( t ); *c = ordClassM( *c ); /* ora le due liste sono ordinate coerentemente cur = *c; /* lassegnamento devessere dopo lordinamento while ( (*c)->concorrente < t->concorrente ) { *c = (*c)->next; free(cur); /* gestione (separata) della testa della lista cur = *c; } while ( cur != NULL && t != NULL ) { if ( cur->concorrente > t->concorrente ) { printf(ERRORE: il conc. %d non era in classifica!!\n, t->concorrente); exit(-1); } else if ( cur->concorrente == t->concorrente ) { cur->t_totale = sum( cur->t_totale, t->t_totale ); cur = cur->next; t = t->next; } else { /* dealloco il nodo se il conc. non arrivato tmp = cur; cur = cur->next; free(tmp); } } while ( cur != NULL) { tmp = cur; /* dealloco gli eventuali ultimi non arrivati cur = cur->next; free(tmp); } *c = ordClassT( *c ); /* La classifica riordinata per tempo totale return; }

*/ */

*/

*/

*/

*/

Esercizio 18 - Analisi di codice: puntatori, parametri, ricorsione - ( 4 punti )


Si consideri il seguente programma C, completando la definizione di MATR con la propria matricola.
#define MATR "......" void f( char * p ) { if ( *p != '\0' ) f(p+1); printf(".%s", p); }
/* Ad esempio, "623372" */

int f2( char * p ) { if ( strcmp(p,"") == 0 ) return 0; else return f2(p+1) + ((*p)-'0'); }


/* ATT: d un doppio puntatore */

char * f3( char * p ) { if ( strlen(p) != 0 ) if ( strlen(f3(p+1)) == 2 ) printf("%s", p); return p; }

void f4( char ** d ) { if ( strlen(*d) > 0 ) { printf("%c", ((**d)+1) ); ++(*d); f4(d); } }

int main() { char m[7] = MATR; char *x = m; f( m ); printf("\n%d - %s\n", f2(m), m); printf(" - %s\n", f3(m)); f4( &x ); return 0; }

Indicare le quattro righe di output stampate dal programma, simulandone lesecuzione (si presti attenzione a impilare e disimpilare bene le chiamate ricorsive e a considerare tutte le chiamate alla funzione printf). Si tenga anche presente che i caratteri '0', '1', , '9' hanno codici ASCII consecutivi, crescenti in questordine, ed eventualmente che il carattere successivo a '9' il carattere ':'. Per ogni linea si dia una breve giustificazione, spiegando come opera la funzione ricorsiva corrispondente. Linea 1

[con 623372] > ..2.72.372.3372.23372.623372 La funzione f() impila sette chiamate ricorsive avanzando nella scansione del vettore di caratteri (p+1 un incremento del puntatore pari alla memoria occupata da un elemento del vettore) e stampa quindi in ordine inverso le stringhe che iniziano dai caratteri via via puntati.
Linea 2

[con 623372] > 23 - 623372 La funzione f2() scandisce il vettore finch il puntatore non punta a una stringa la cui lunghezza 0 (cio ha raggiunto il '\0' in fondo al vettore) e somma le cifre della matricola, restituendo il totale. La somma ottenuta sfruttando la codifica ASCII.
Linea 3

[con 623372] > 372 623372 La funzione f3() scandisce il vettore controllando via via la lunghezza della coda di stringa restante. Quando la lunghezza della coda successiva 2, stampa la coda corrente, composta quindi dai 3 ultimi caratteri della matricola.
Linea 4

[con 623372] > 734483 La funzione f4() scandisce il vettore stampando a video via via il carattere successivo (nella tabella ASCII) al carattere correntemente puntato.

Esercizio 19 Facoltativo

( incremento libero )

Un albero N-ario generico un albero i cui nodi possono avere un numero arbitrariamente grande di rami uscenti. Si definisca una struttura dati adatta a rappresentare un albero N-ario. Per semplicit si consideri il caso in cui i nodi contengono, come dati utili, dei semplici numeri interi. Ogni nodo contiene, invece di una coppia di puntatori a nodi, come nel caso degli alberi binari, una lista di puntatori a nodo. Tale lista una lista concatenata semplice, realizzata tramite la struttura Ramo.
typedef struct Knot { int dato; struct Branch * rami; } Nodo; typedef Nodo * Albero; typedef struct Branch { Albero child; struct Branch * next; } Ramo;

Si progetti (e/o codifichi) la funzione int conta( ) che conta il numero di nodi di un albero N-ario.

Si pu utilizzare una funzione ricorsiva che per ogni nodo valido restituisce 1 pi il totale dei nodi accumulato scandendo la lista dei puntatori ai sottoalberi, tramite un ciclo while.
int conta ( Albero t ) { if ( t != NULL ) { int n = 1; Ramo * cur = t->rami; while ( cur != NULL ) { n += conta( cur->child ); cur = cur->next; } return n; } return 0; }

Se ne pu anche scrivere una versione pi elegante, completamente ricorsiva, che non usa il ciclo while per scandire la lista dei sottoalberi.
int contaNodi ( Albero t ) { if ( t == NULL ) return 0; else return 1 + contaRami( t->rami ); } int contaRami ( Ramo * b ) { if ( b == NULL ) return 0; else return contaNodi( b->child ) + contaRami( b->next ); }

Informatica A a.a. 05/06 Terzo Appello 18/9/2006


Esercizio 20 - Algebra di Boole, Aritmetica Binaria, Codifica delle Informazioni ( 4 punti ) (i) Si costruisca la tabella di verit della seguente espressione booleana, prestando attenzione alla precedenza tra gli operatori logici. Eventualmente si aggiungano le parentesi. [1,5 punti] A and not A or not B or A and not B or A and not C
con altra notazione:

AA+B+AB+AC

Il primo disgiunto una contraddizione, e si pu elidere subito. Per indicare chiaramente la soluzione, scegliamo una strategia associativa (ad es. a sinistra). ((not B) or (A and not B)) or (A and not C)
1 1 0 0 1 1 0 0 0 0 1 1 0 0 1 1 1 1 0 0 1 1 0 0 0 0 0 0 1 1 1 1 0 0 0 0 1 1 0 0 1 1 0 0 1 1 0 0 0 0 1 1 0 0 1 1 1 1 0 0 1 1 1 0 0 0 0 0 1 1 1 1 0 0 0 0 1 0 1 0 1 0 1 0 1 0 1 0 0 1 0 1 0 1 0 1

Una ulteriore semplificazione si poteva ottenere subito notando che


not B or A and not B not B

(j)

Si stabilisca il minimo numero di bit sufficiente a rappresentare in complemento a due i numeri A = 123dec e B = 132dec, li si converta, se ne calcolino la somma (A + B) e la differenza (A B) in complemento a due e si indichi se si genera riporto sulla colonna dei bit pi significativi e se si verifica overflow. [2 punti] Con 9 bit si rappresentano in C2 i numeri da 256dec a +255dec; 8 bit non sono sufficienti per B. Col metodo dei resti in binario assoluto, e poi in C2: 123dec = 1111011bin = 001111011C2 123dec = 110000100 + 000000001 = 110000101C2 132dec = 10000100bin = 010000100C2 132dec = 101111011 + 000000001 = 101111100C2 Applicando lalgoritmo di addizione, ricordando che -123-(-132) si calcola come -123+132, e indicando (riporto) e [overflow] con le rispettive parentesi: 1 1 0 0 0 0 1 0 1 -123dec + 1 0 1 1 1 1 1 0 0 -132dec = (1) [0] 1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 1 0 1 0 1 0 0 0 0 1 0 0 -255dec -123dec 132dec

= (1) [0] 0 0 0 0 0 1 0 0 1 9dec In entrambi i casi si genera riporto dalla colonna dei bit pi significativi, ma non si verifica overflow. (k) Qual il numero minimo di bit necessario per codificare univocamente gli elementi dellinsieme B54, costituito dai numeri rappresentabili in base 5 con quattro cifre? Si giustifichi (brevemente) la risposta. [0,5 punti] Con quattro cifre in base cinque si codificano i numeri da 00005 a 44445. Essendo 44445 = 4*53 + 4*52 + 4*51 + 4*50 = 4*125dec + 4*25dec+ 4*5dec + 4 = 500dec + 100dec + 20dec + 4dec = 624dec si deduce che linsieme B54 ha seicentoventicinque elementi (54=625dec, lo si poteva subito notare!!). Occorrono quindi 10 bit (con cui si rappresenta lintervallo [0,1023]) Non bastano, infatti, 9 bit, con cui si rappresenta lintervallo [0,511]

Esercizio 21 - Sintesi di codice: Algoritmica di base ( 9 punti )


In matematica un insieme (in inglese set) non ammette elementi duplicati (diversamente si parla di multiinsieme, in inglese bag). Un insieme di numeri interi pu essere rappresentato da un vettore di interi i cui elementi non sono ordinati. Si chiede di definire e codificare in linguaggio C le due funzioni descritte in seguito. Le funzioni richieste non gestiscono n linizializzazione n li/o. Eventuali funzioni ausiliarie devono essere anchesse definite e codificate in C. (a) La funzione tuttidiversi() riceve come parametro un vettore di lunghezza arbitraria (la lunghezza, cio, libera, e deve quindi essere passata anchessa come parametro) e restituisce 1 se nel vettore non vi sono elementi duplicati, 0 altrimenti. Si dichiari opportunamente il prototipo della funzione e si descriva brevemente (ma in modo preciso) un algoritmo per implementarla. [1,5 punti]
int tuttidiversi( int *, int );

Un semplice algoritmo consiste nello scandire il vettore confrontando ogni numero con tutti i numeri successivi, interrompendo la scansione e restituendo 0 appena si trovi una coppia di numeri uguali: 1. Per ogni intero v[i] del vettore (tranne lultimo): a. per ogni intero v[j] successivo a v[i] in v: i. Se v[i] uguale a v[j] restituisci 0 e termina; 2. Restituisci 1 e termina;

In alternativa si pu ordinare il vettore e poi verificare che non ci siano numeri adiacenti uguali.

(b)

Si codifichi in C la funzione secondo lalgoritmo definito al punto (a). [3 punti] int tuttidiversi( int v[], int n ) { int i, j; for ( i = 0; i < n-1; i++ ) for ( j = i+1; j < n; j++ ) if ( v[i] == v[j] ) return 0; return 1; }

(c)

La funzione consecutivi() riceve come parametro un vettore di lunghezza arbitraria e restituisce 1 se linsieme rappresentato contiene tutti i numeri di un intervallo, senza omissioni, e 0 altrimenti. Ad esempio la propriet sussiste per gli insiemi {4, 6, 5} e {-2, 0, 1, -3, -1}, ma non per {0, 3, -1, 1} (dove evidentemente manca il numero 2). Si dichiari opportunamente il prototipo della funzione e si descriva brevemente (ma in modo preciso) un algoritmo per implementarla. [1,5 punti]
int consecutivi( int *, int);

Un possibile algoritmo consiste nel trovare il minimo m del vettore v di lunghezza n e verificare che gli n numeri compresi tra m e m+n-1 siano tutti presenti nel vettore (o simmetricamente con il massimo): 1) Assegna a m il primo elemento del vettore 2) Per ogni elemento v[i] del vettore successivo al primo a. Se v[i] < m assegna m = v[i] 3) Per ogni intero k compreso tra m e m+n-1: a. Assegna TROVATO = 0 b. Per ogni elemento v[i] del vettore, purch sia TROVATO == 0 i. Se v[i] == k assegna TROVATO = 1 c. Se TROVATO == 0 alla fine di una iterazione del ciclo precedente, restituisci 0 e termina, perch il numero k non nel vettore 4) Restituisci 1, perch tutti i numeri dellintervallo sono stati trovati

In alternativa si pu ordinare il vettore in senso crescente e verificare che i [0,n-1] v[i] == v[i+1]-1
(d) Si codifichi in C la funzione secondo lalgoritmo definito al punto (c). [3 punti]
int consecutivi( int v[], int n ) { int i, k, min = v[0], trovato; for ( i = 1; i < n; i++ ) /* Ricerca del minimo del vettore */ if ( min > v[i] ) min = v[i]; for ( k = min; k < min + n; k++ ) { /* Ricerca dei valori nel vettore */ trovato = 0; for ( i = 0; !trovato && i < n; i++ ) if ( v[i] == k ) /* Numero k contenuto nel vettore */ trovato = 1; if ( !trovato ) return 0; } return 1; }

Esercizio 22 - Allocazione e struttura della memoria ( 3 punti )


Le seguenti dichiarazioni definiscono una struttura dati che rappresenta una lista di acronimi (un acronimo una sigla in cui ogni lettera liniziale di una parola, come ATM = Azienda Trasporti Milanesi).
typedef char Word[100]; typedef struct WNode { Word parola; struct WNode * next; } WordNode; typedef WordNode * WordList; typedef struct Acro { Word sigla; WordList listaparole; } Acronym; typedef struct ANode { Acronym acronimo; struct ANode * next; } AcroNode; typedef AcroNode * AcroList;

Si faccia riferimento a un sistema in cui sizeof(char) = 1 e sizeof(void *) = 4. (a) Calcolare la dimensione in byte del tipo Acronym (cio il valore dellespressione sizeof(Acronym)); Il tipo Word ha dimensione fissa di 100 char, e quindi 100 byte, indipendentemente dalla porzione effettivamente utilizzata dalla sigla. Il puntatore alla lista di parole ha dimensione 4 byte (come ogni puntatore, indipendentemente dal tipo puntato). Quindi Acronym ha dimensione 100 + 4 = 104 byte.

(b)

Si dia una rappresentazione grafica della struttura dati costruita dalla sequenza di dichiarazioni statiche e di allocazioni dinamiche riportata qui a lato, che costruisce un acronimo (si trascuri ptr, che una variabile di servizio).

Acronym x; WordList ptr; strcpy(x.sigla, "ATM"); x.listaparole = (WordNode *) malloc(sizeof(WordNode)); ptr = x.listaparole; strcpy(ptr->parola, "Azienda"); ptr->next = (WordNode *) malloc(sizeof(WordNode)); ptr = ptr->next; strcpy(ptr->parola, "Trasporti"); ptr->next = (WordNode *) malloc(sizeof(WordNode)); ptr = ptr->next; strcpy(ptr->parola, "Milanesi"); ptr->next = NULL;

sigla listaparole

ATM

parola next parola next


Azienda

Milanesi

parola next

Trasporti

(c)

Si calcoli la dimensione in byte della memoria complessivamente allocata (sia staticamente sia dinamicamente) per la struttura costruita. Si badi a considerare la parte statica e la parte dinamica. La parte statica (in blu nella rappresentazione al punto precedente) una variabile di tipo Acronym, quindi ha dimensione 104 byte (cfr. punto (a)). La parte dinamica si compone di tre variabili di tipo WordNode, che contengono anche esse una variabile di tipo Word (100 byte) e un puntatore (4 byte). La dimensione di tale parte di 312 byte. In totale la struttura occupa 104 + 312 = 416 byte.

Esercizio 23 - Memoria Dinamica e strutture dati - ( 9+1 punti )


Si consideri ancora la struttura dati definita nellesercizio 3. (a) Si dichiari (tramite opportuno prototipo) la funzione acronimogiusto() che riceve come parametro una struttura di tipo Acronym e restituisce 1 se le iniziali delle parole della lista di parole corrispondono esattamente alle lettere della sigla, 0 altrimenti. Si descriva poi brevemente (ma in modo preciso) un algoritmo per implementarla e la si codifichi in C. [6 punti]
ATTENZIONE: Gli acronimi possono essere sbagliati per diversi motivi. Ecco tre esempi di acronimi sbagliati: ATM = Azienda Trasporti Romani, ATM = Azienda Tessile, ATM = Associazione Turistica Milano Marittima. Se la sigla una stringa vuota o la lista di parole una lista vuota, lacronimo (convenzionalmente) giusto se e solo se anche laltro componente dellacronimo vuoto.

int acronimogiusto( Acronym );

Un possibile algoritmo consiste nello scandire in parallelo in uno stesso ciclo le lettere della sigla e la lista delle parole, confrontando man mano la lettera e liniziale della parola corrispondente. Alla prima discrepanza, si restituisce 0. La condizione di permanenza nel ciclo che n la sigla n la lista siano ancora terminate. Al termine del ciclo occorre verificare che non restino n altre lettere nella sigla n altre parole nella lista (le lunghezze, cio, devono corrispondersi):
int acronimogiusto( Acronym a ) { int i = 0, lung = strlen(a.sigla); WordList tmp = a.listaparole; while ( i < lung && tmp != NULL ) { if ( a.sigla[i] != tmp->parola[0] ) return 0; i = i + 1; tmp = tmp->next; } return ( i == lung && tmp == NULL ); }

(b)

Si codifichi in C (preferibilmente in modo ricorsivo, bonus +1) una funzione che effettui la deallocazione di una lista di acronimi, secondo il prototipo:
void freeAcroList( AcroList )

Si badi a non deallocare solamente gli elementi di tipo AcroNode che compongono la lista di acronimi, ma anche le liste di parole contenute in ciascun acronimo. Eventuali funzioni ausiliarie devono essere anchesse codificate. [3(+1) punti]
void freeAcroList( AcroList list ) { if ( list != NULL ) { AcroList tmp = list->next; freeWordList( (list->acronimo).listaparole ); free( list ); freeAcroList( tmp ); } return; } void freeWordList( WordList list ) { if ( list != NULL ) { WordList tmp = list->next; free( list ); freeWordList( tmp ); } return; }

Esercizio 24 - Analisi di codice: puntatori, parametri, ricorsione - ( 3 punti )


Si consideri il seguente programma C, completando la definizione di MATR con la propria matricola.
#define MATR "......"
/* Ad esempio, "623372" */

void f( char * p ) { if ( p == NULL ) return; else { printf("%c", *p); g( p+1 ); /* +1 !! */ return; } }

void g( char * p ) { if ( strlen(p) == 0 ) return; else { f( p-1 ); /* -1 !! */ printf("%c", *p); return; } }

int main() { char m[7] = MATR; f( m+4 ); printf("SE ARRIVO QUI HO FINITO\n"); return 0; }

Si spieghi brevemente il comportamento del programma; per comprenderlo, si suggerisce di simularne lesecuzione (prestando attenzione a impilare ed eventualmente disimpilare bene le chiamate ricorsive e a prestare attenzione a quali istruzioni sono eseguite e quali no). Indicare anche loutput stampato a video dal programma (attenzione alla posizione delle chiamate a printf).

Il programma inizia invocando f() con argomento pari a un puntatore alla quinta cifra della matricola, stampa tale cifra (il carattere '7' nel caso dellesempio) e invoca g() sul carattere successivo, che per non viene stampato, perch prima viene invocata nuovamente f() sul carattere precedente e cos si instaura un ciclo potenzialmente infinito in cui g ed f continuano a invocarsi mutuamente in sempre nuove attivazioni, puntando sempre agli stessi due caratteri. La sesta cifra della matricola quindi non sar mai stampata, come nemmeno la stringa SE ARRIVO QUI HO FINITO. Il programma, tuttavia, termina, seppure in modo anomalo e difficilmente prevedibile, quando lo stack (su cui sono impilati i record di attivazione) arriva a saturare la memoria disponibile. Le chiamate mutue saranno tipicamente in numero molto grande, perch i record di attivazione sono di dimensioni ridotte (circa 700.000 record di attivazione sul sistema su cui ho effettuato la prova - NdProf). Loutput : 7777777777777777777777777777777.777

segnalazione stack full o chiusura improvvisa (crash)

Informatica A a.a. 06/07 Prima Prova in Itinere 24/11/2006


Esercizio 25 - Algebra di Boole, Aritmetica Binaria, Codifica delle Informazioni ( 3 punti ) (l) Si dimostri la seguente equivalenza. [1 punto] ( not A or B or not C ) and A and C A and B and C
con altra notazione:

(A+B+C)AC ABC

Verificando lidentit delle tabelle di verit (scegliendo di associare ad es. a sinistra): (((not A or B) or not C ) and A) and C
1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 1 1 0 0 1 1 0 0 1 1 1 1 1 1 1 0 1 1 1 0 1 0 1 0 1 0 0 1 0 1 0 1 0 1 0 0 0 0 1 0 1 1 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 1 0 1 0 1 0 1 0 1

( A and B ) and C
0 0 0 0 1 1 1 1 0 0 0 0 0 0 1 1 0 0 1 1 0 0 1 1 0 0 0 0 0 0 0 1 0 1 0 1 0 1 0 1

In alternativa: (A+B+C)AC =distrib AAC + ABC + ACC =contradd false + ABC + false = ABC (m) Si stabilisca il minimo numero di bit sufficiente a rappresentare in complemento a due i numeri A = 13dec e B = 27dec, li si converta, se ne calcolino la somma (A + B) e la differenza (A B) in complemento a due e si indichi se si genera riporto sulla colonna dei bit pi significativi e se si verifica overflow. [1,5 punti] Con 6 bit si rappresentano in C2 i numeri da 32dec a +31dec; 5 bit non sono sufficienti per B. Col metodo dei resti in binario assoluto, e poi in C2: 13dec = 1101bin = 001101C2 13dec = 110010 + 000001 = 110011C2 27dec = 100100 + 000001 = 100101C2 27dec = 11011bin = 011011C2 Applicando lalgoritmo di addizione, ricordando che 13-(-27) si calcola come 13+27, e indicando (riporto) e [overflow] con le rispettive parentesi: 0 0 1 1 0 1 13dec + 1 0 0 1 0 1 -27dec = (0) [0] 1 1 0 0 1 0 0 0 1 1 0 1 0 1 1 0 1 1 -14dec 13dec 27dec

= (0) [1] 1 0 1 0 0 0 40 dec ?? 24dec !! In nessun caso si genera riporto dalla colonna dei bit pi significativi, ma nel secondo caso si verifica overflow (il risultato atteso, infatti, supera la capacit di rappresentazione dei bit impiegati). (n) Si consideri un (ipotetico) sistema di codifica binaria in virgola mobile [nvm=M*BE] nel quale: a. la base B il numero (irrazionale) di Nepero e (e = 2,7182) b. la mantissa M e lesponente E sono entrambi rappresentati in complemento a due su 4 bit (ogni numero occupa quindi 8 bit in totale) e assumono i valori interi ammessi dalla loro codifica. Quante delle 28 diverse codifiche possibili in tale sistema corrispondono a valori strettamente positivi? Si giustifichi (brevemente) la risposta, ricordando che x x0=1. [0,5 punti] Chiaramente ex>0 x intero. Il segno dato solo dalla mantissa. Con i 4 bit di M si rappresentano 7 valori positivi, quindi ci sono 7*24 = 7*16 = 112 codifiche positive.
Se invece di considerare la precedenza tra moltiplicazione e elevamento a potenza nvm=M*(BE), che quella prevista dallo standard e che garantisce simmetria ai valori rappresentati, si ragionasse con lo schema nvm=(M*B)E, si dovrebbe fare la seguente analisi: Con i quattro bit di M si rappresentano 7 valori positivi, lo zero e 8 valori negativi: Se M >0 tutti gli esponenti (che sono 24=16) generano valori positivi. 7*16 = 112 codifiche. Se M =0, rappresentiamo n=1 con E=0, n=0 con E>0, n=indefinito (bit sprecati!) con E<0. 1*1 = 1 codifica. Se M <0, E=0 n=1, E dispari (8 val.) n<0, E pari e E0 (7 val.) n>0. 8*(1+7) = 64 codifiche. Ci sarebbero quindi 112+1+64 = 177 codifiche strettamente positive (con 16 rappr. del valore 1 e solo 64 valori negativi validi). Di 56 valori, peraltro, non sarebbe rappresentabile lopposto aritmetico

Se si continua sul retro di qualche foglio, indicare dove


Informatica A a.a. 05/06 Prima Prova in Itinere 24/11/200 Esercizio n. 25 -- pagina 31 di 52

Esercizio 26 - Sintesi di codice: codifica vincolata su analisi di sequenze ( 4 punti ) Si definisce terna pitagorica intera (TP) una terna a,b,c di numeri interi positivi che soddisfano la relazione di Pitagora (cio sono le lunghezze dei cateti e dellipotenusa di un triangolo rettangolo). Esempio: 10,6,8 (infatti 100=36+64). Una TP si dice irriducibile (TPI) se non esiste una TP da cui la si deriva moltiplicando per un fattore costante (Es: 3,4,5 irriducibile, 8,6,10 e 15,9,12 non lo sono). Si codifichi un programma C che legge da standard input una sequenza (di lunghezza a priori illimitata) di interi terminata dal valore 0 e, al termine della sequenza, visualizza su standard output un messaggio che indica quante TPI di numeri adiacenti sono contenute nella sequenza. Esempio (si noti che ogni numero pu appartenere a 0, 1, 2 o 3 TPI): > -4 11 4 5 3 4 5 12 13 -6 -8 26 10 24 70 74 -4 -5 -3 1 35 12 37 6 0 riducibile > Ci sono 5 terne irriducibili riducibile N.B.: Nella soluzione si definisca e si utilizzi la funzione: int tpi(int,int,int); // Restituisce 1 se i parametri sono una TPI, 0 altrimenti Suggerimenti e aiuti: inutile (e fortemente sconsigliato) memorizzare la sequenza La funzione mcd(int,int), che calcola e restituisce il M.C.D. dei parametri, pu essere usata senza ridefinirla ( definita qui a lato) Attenzione: 4,5,3 5,3,4 3,4,5 4,3,5 5,4,3 3,5,4 sono tutte TPI I tre numeri che formano una TPI sono a due a due primi fra loro
int tpi (int a, int b, int c) { if ( a<=0 || b<=0 || c<=0 ) return 0; return ( ( a*a == b*b + b*b == a*a + c*c == a*a + && mcd(a,b) == 1 } int main() { int terneirriducibili = 0, a, b = 0, c = 0; do { a = b; b = c; scanf("%d", &c); terneirriducibili += tpi(a,b,c); } while ( c != 0 ); c*c || c*c || b*b ) ); // se una TP controlla anche che sia una TPI // Basta controllare una coppia qualsiasi
int mcd(int m, int n) { while ( m != n ) if ( n > m ) n -= m; else m -= n; return n; }

// Controlla che sia un triangolo non degenere

// L'inizializzazione b=0, c=0 innocua

// Slittamento in avanti della "memoria" // Lettura del nuovo valore // Controlla lirriducibile pitagoricit // Termina digitando lo 0 ("sentinella")

printf("Ci sono %d terne irriducibili\n", terneirriducibili); return 0; }

Se si continua sul retro di qualche foglio, indicare dove


Informatica A a.a. 05/06 Prima Prova in Itinere 24/11/200 Esercizio n. 26 -- pagina 32 di 52

Esercizio 27 - Sintesi di codice: codifica libera su matrici - ( 4 punti ) Si definisca una funzione C di prototipo int controlla(char m[][N]) che (1) riceve come unico parametro una matrice quadrata NxN (dove N una costante positiva gi definita), (2) controlla se la parola leggibile sulla diagonale principale leggibile anche in una delle righe o colonne della matrice stessa, e (3) restituisce 1 in caso affermativo, 0 altrimenti. Prima del codice [2,5 punti] si spieghi (in modo sintetico ma preciso) lalgoritmo scelto [1,5 punti] Esempi (per N = 5):
E E X G B N S I O R E S A M E A O A M V S R D A E A I P A D R N A N A C A S S Z A N S I A A E A A A P P A C M E A C U A N U U O M S R T R M O A A E A C P D E A C A O S N Y U L A S A R O M I M A R E A

Per i che va da 0 a N-1 scandisco dapprima la i-esima riga e poi la i-esima colonna, controllando (un carattere alla volta) lidentit tra la parola in diagonale e la parola via via scandita. Quando trovo due caratteri diversi in posizioni corrispondenti posso interrompere subito la scansione corrente e passare alla riga o colonna successiva. La prima volta che trovo interamente la parola cercata posso terminare la funzione restituendo 1, mentre solo dopo aver controllato anche lultima colonna posso restituire 0. int controlla(char m[][N]) { int i, j, trovato; for( i=0; i<N; i++ ) { /* Ogni iterazione controlla una riga e una colonna */ j = 0; trovato = 1; while ( trovato && j<N ) { /* Scansione della i-esima riga */ if ( m[j][j] != m[i][j] ) trovato = 0; /* carattere diverso -> parola non trovata */ j++; } if ( trovato ) return 1; /* Pu restituire 1 appena trova la parola */ j = 0; /* reset per controllare la colonna */ trovato = 1; while ( trovato && j<N ) { /* Scansione della i-esima colonna */ if ( m[j][j] != m[j][i] ) trovato = 0; // idem... j++; } if ( trovato ) return 1; } return 0; } Un'altra possibile soluzione, pi sintetica ma forse meno intuitiva: int controlla(char m[][N]){ int i, j, contOriz, contVert; for( i=0; i<N; i++ ){ /* Ogni iterazione controlla una riga e una colonna */ contOriz = contVert = 0; /* Riazzera i contatori */ for( j=0; j<N; j++ ) { /* Scansione simultanea */ if(m[j][j]==m[i][j])contOriz++; /* conta caratteri uguali sulla riga i */ if(m[j][j]==m[j][i])contVert++; /* conta caratteri uguali sulla colonna i */ } if ( contVert==N || contOriz==N ) /* restituisce 1 appena trova la parola */ return 1; /* cio se ci sono N caratteri uguali */ } /* su una stessa riga o colonna */ return 0; } Se si continua sul retro di qualche foglio, indicare dove
Informatica A a.a. 05/06 Prima Prova in Itinere 24/11/200 Esercizio n. 27 -- pagina 33 di 52

Informatica A a.a. 06/07 Seconda Prova in Itinere 29/1/2007


Esercizio 28 - Gestione semplice di file e liste ( 6 punti )
Si progetti e codifichi un programma C che apre il file di testo input.txt e genera il file di testo output.txt, che contiene, in ordine inverso ma inalterate, le sole linee di input.txt che hanno lunghezza compresa tra 10 e 20 caratteri (ignorando le altre linee). A tale scopo, il programma alloca una lista dinamica che contiene le linee da trasferire, genera il file scandendo la lista e la dealloca prima di terminare. Suggerimenti e aiuti: - Si usi la seguente definizione della lista: typedef struct EL { char linea[21];
struct EL * next; } Nodo; typedef Nodo * Lista;

- Si provi a costruire la lista nel modo pi semplice che permetta poi di generare facilmente l'output - Si pu assumere che l'ultima linea di input.txt termini con '\n' e che nessuna linea superi i 255 caratteri Prima di scrivere il codice del programma e delle eventuali funzioni ausiliarie (5 punti) si dia una breve ma precisa descrizione della strategia di soluzione (1,5 punti).

ATTENZIONE a non perdersi nei dettagli: (c altro, dopo!)


Essendo chiaramente assai pi complicato (e inutile) scandire il file un carattere alla volta, scandisco il file di input una linea per volta con fgets(), controllo subito se ogni linea da trasferire, e nel caso la inserisco in testa alla lista dinamica, in un nodo appositamente allocato. Cos facendo, al termine del file di input la lista in memoria contiene le linee gi in ordine inverso: il file di output si genera con una semplice scansione lineare, durante la quale si dealloca la lista. int main () { FILE * fp; char buffer[256]; Lista lis = NULL, tmp;

// max 255 caratteri + 1 per il '\0'

if ( (fp=fopen("input.txt","r")) == NULL ) // Selezione linee da tenere return -1; while( fgets( buffer, 256, fp ) != NULL ) if ( strlen(buffer) >= 10 && strlen(buffer) <= 20 ) insTesta(&lis, buffer); close(fp); if ( (fp=fopen("output.txt","w")) == NULL ) return -2; while ( lis != NULL ) { // Man mano che trascrivo, dealloco fputs( lis->linea , fp ); tmp = lis; lis = lis->next; free(tmp); } close(fp); return 0; } void insTesta( Lista *lista, char * line ) { Lista tmp = *lista; *lista = (Lista) malloc(sizeof(Nodo)); strcpy( (*lista)->linea, line ); (*lista)->next = tmp; }
Informatica A a.a. 05/06 Prima Prova in Itinere 24/11/200

// Normale inserimento in // testa con assegnamento // del "dato" con strcpy

Se si continua sul retro di qualche foglio, indicare dove


Esercizio n. 28 -- pagina 34 di 52

Esercizio 29 - Liste dinamiche ( 3 punti )


Due liste di interi si dicono equipotenti se sono di uguale lunghezza e, confrontando i valori in posizioni corrispondenti, risulta che i valori della prima lista maggiori dei corrispondenti valori nella seconda sono esattamente in numero uguale ai valori della seconda lista maggiori dei corrispondenti valori nella prima. Data la lista definita come:
typedef struct EL { int valore; struct EL * next; } Nodo; typedef Nodo * Lista;

si descriva un algoritmo e si proponga una codifica per la funzione di prototipo


int equipotenti ( Lista L1, Lista L2 );

che restituisce 1 se le liste sono equipotenti, 0 altrimenti.

Con una scansione parallela delle due liste, che si arresta appena una delle due terminata, conto separatamente gli elementi maggiori e quelli minori. Restituisco 1 se al termine i conteggi si equivalgono e le liste terminano contemporaneamente, 0 altrimenti.

int equipotenti ( Lista L1, Lista L2 ) { int maggiori = 0, minori = 0; while ( L1 != NULL && L2 != NULL ) { if ( L1->valore > L2->valore ) maggiori++; else if ( L1->valore < L2->valore ) minori++; L1 = L1->next; L2 = L2->next; } return (L1 == L2) && (maggiori == minori); } // continuo su entrambe

// doppio passaggio ai successivi

// non basta che i due contatori // abbiano lo stesso valore: le liste // devono anche essere entrambe finite

Chi preferisce le soluzioni ricorsive apprezzer la versione seguente. Conviene introdurre una funzioncina ausiliaria, per propagare il conteggio dello "sbilanciamento dei maggiori" come parametro. Si noti che uso una sola variabile da tenere bilanciata a zero con incrementi e decrementi, per non propagare due contatori.

int equipotenti ( Lista L1, Lista L2 ) { return controlla(L1, L2, 0); } int controlla ( Lista L1, Lista L2, int bilancio ) { if ( L1 == NULL || L2 == NULL ) return ( L1 == L2 && bilancio == 0 ); if ( L1->valore > L2->valore ) bilancio++; else if ( L1->valore < L2->valore ) bilancio--; return controlla( L1->next, > L2->next, bilancio ); }

Se si continua sul retro di qualche foglio, indicare dove


Informatica A a.a. 05/06 Prima Prova in Itinere 24/11/200 Esercizio n. 29 -- pagina 35 di 52

Esercizio 30 - Sintesi di codice ricorsione e puntatori ( 2,5 punti )


La funzione di libreria strlen() potrebbe essere (teoricamente) codificata in modo ricorsivo, come segue
int strlenRic ( const char * s ) { if ( *s == '\0' ) return 0; else return 1 + strlenRic( s+1 ); }

anche se ci non accade mai in pratica, per ovvi motivi di efficienza. Si chiede tuttavia di proporre, a titolo dimostrativo, una codifica ricorsiva anche per le funzioni indicate in seguito. Si badi a considerare bene i casi base e a progettare bene i "passi induttivi". Si tenga eventualmente presente che il codice ASCII del carattere '\0' (terminatore di stringa) il numero zero.
/ / Ha lo stesso stesso effetto di strcpy() in string.h : s1 riceve tutti i caratteri contenuti in s2 (incluso il '\0') void strcpyRic( char *s1, const char *s2 );

void strcpyRic( char *s1, const char *s2 ) { *s1 = *s2; if ( *s2 != '\0' ) strcpyRic( s1+1, s2+1 ); return; }
// Versione "provocatoria": gli assegnamenti sono espressioni, '\0' in ASCII 0, il "return void" implicito void strcpyRic2( char *s1, const char *s2 ) { if ( *s1 = *s2 ) strcpyRic2( s1+1, s2+1 ); }

/ / Come strcmp() in string.h : restituisce 0 se le stringhe sono uguali, n<0 (n>0) se s1 precede (segue) alfabeticamente s2 int strcmpRic( const char *s1, const char *s2 );

int strcmpRic( const char *s1, const char *s2 ) { int n = *s1 - *s2; if ( n || *s1 == '\0' || *s2 == '\0' ) return n; else return strcmpRic( s1+1, s2+1 ); }
// Versione pi "analitica", che restituisce sempre 1, 0 o -1

int strcmpRic( const char *s1, const char *s2 ) { if ( *s1 == '\0' && *s2 == '\0' ) return 0; else if ( *s1 < *s2 ) return -1; else if ( *s1 > *s2 ) return 1; else return strcmpRic( s1+1, s2+1 ); }
Se si continua sul retro di qualche foglio, indicare dove
Informatica A a.a. 05/06 Prima Prova in Itinere 24/11/200 Esercizio n. 30 -- pagina 36 di 52

Esercizio 31 - Analisi di codice - ( 2 punti )


Si consideri il seguente programma C, completando la definizione di MATR con la propria matricola. Il programma (che ha una funzione ricorsiva) elabora i vettori mat e p, e alla fine stampa a video una linea.
#define MATR

"......"

/* Ad esempio, "623372"

*/

void f( char * x, char ** y ) { *y = x++; if ( *x != '\0' ) f( x++, ++y ); return; }

int main() { char mat[7] = MATR; char * p[7]; char ** d = p; f( mat+1, d ); for( d=d+4; d>p; d-- ) (*d)++; printf("%s %s %c\n", mat+2, p[2], **p); return 0; }

a) Si dia una rappresentazione grafica precisa dell'ambiente del main, indicando il valore di tutte le variabili, all'istante t1 in cui la funzione f esegue per la prima volta listruzione return; e all'istante t2 . Si in cui il main esegue listruzione return 0; individuate, nel codice, dalla freccia rappresentino, come di consueto, i vettori come blocchi contigui, i valori dei puntatori come frecce e i valori non inizializzati come punti interrogativi. (1,5 punti)

t1
mat 6 2 3 3 7 2 '\0' mat 6 2 3 3 7 2 '\0'

t2

b) Riportare qui di seguito la riga di output generata dal programma su stdout (0,5 punti):

Nel caso dellesempio: 3372 72 2

Se si continua sul retro di qualche foglio, indicare dove


Informatica A a.a. 05/06 Prima Prova in Itinere 24/11/200 Esercizio n. 31 -- pagina 37 di 52

Es. 32 Facoltativo Analisi e progetto di codice su alberi binari - ( incremento libero )


Si consideri la seguente definizione di un albero binario (binario=con due rami in ogni nodo):
typedef struct EL { struct EL * left, * right; } node; typedef node * tree; / / N.B. : nessun "dato", solo "struttura"

L'operatore binario & del C, detto AND bit a bit (binario=con due operandi), confronta bit a bit i suoi operandi e produce un valore ottenuto dall'and logico dei bit in posizioni corrispondenti. Esempi (su 8 bit):
00001111 & 00111100 00001100 00000100 & 00000010 00000000 00000010 & 00000010 00000010

Attenzione: l'operatore binario & non ha nulla a che vedere con l'operatore unario & che referenzia le variabili!

Si dica giustificando la risposta quale propriet dell'albero t verificata dalla seguente funzione ricorsiva.
int f( tree t ) { if ( t == NULL ) return 1; else return 2 * ( f(t->left) & f(t->right) ); } / / t vuoto / / altrimenti return 1 il doppio dell'and bit a bit di

Osserviamo dapprima che raddoppiare un valore intero significa fare slittare (scorrere) gli "1" della sua rappresentazione binaria di una posizione verso sinistra. Un albero vuoto vale 1 (vero). Per un albero non vuoto, quindi, f calcola lo "scorrimento a sinistra" del risultato dell'and bit a bit della f calcolata sui due rami. Si noti che (induttivamente) f pu restituire solo 0 o un numero con un solo bit a 1 (cio sempre e solo una potenza di due), perch f restituisce 1 nel caso base e un valore con al pi un solo bit a 1 nei casi non base. Un albero vuoto restituisce 1 (20), un albero di un solo nodo restituisce 2 (21), un albero con pi nodi restituisce 2n se e solo se i due rami restituiscono ENTRAMBI il valore 2n-1 (e 0 ogni volta che le potenze di due restituite sono diverse). I due sottoalberi cio devono essere ad ogni livello o entrambi vuoti o uguali. chiaro quindi che la funzione restituisce 2n (un valore diverso da zero, cio true) se e solo se l'albero pieno e bilanciato (e ha profondit n), mentre restituisce 0 (false) altrimenti.

Una funzione di prototipo int speculare(tree t); verifica se un albero specularmente simmetrico rispetto alla radice (si vedano gli esempi in figura, notando che pari profondit e pari numerosit dei due "semialberi" non sono condizioni sufficienti). Si progetti e si spieghi nel modo pi preciso possibile un algoritmo per implementare la funzione (la stesura del codice non richiesta).

No

No

Attenzione: la specularit una propriet globale dell'albero, e la radice l'unico centro di simmetria. Suggerimento: si rifletta sulla necessit di una struttura dati di supporto all'esplorazione dell'albero.

Informatica A a.a. 06/07 Primo Appello 19/2/2007


Esercizio 33 - Algebra di Boole, Aritmetica Binaria, Codifica delle Informazioni ( 3 punti ) (o) Si costruisca la tabella di verit della seguente espressione booleana, prestando attenzione alla precedenza tra gli operatori logici. Eventualmente si aggiungano le parentesi (1 punto) A and not C or not A and not ( B or C )

con altra notazione: AC + A (B+C)


0 1 0 1 0 1 0 1

A and not C or not A and not ( B or C )


1 1 1 1 0 0 0 0 1 0 1 0 0 0 0 0 1 0 1 0 1 0 1 0 0 1 0 1 0 1 0 1 1 0 1 0 0 0 0 0 1 1 1 1 0 1 1 1 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 1 1 0 0 1 1 0 1 1 1 0 1 1 1

(p)

Si stabilisca il minimo numero di bit sufficiente a rappresentare in complemento a due i numeri A = 52dec e B = 29dec, li si converta, se ne calcolino la somma (A + B) e la differenza (A B) in complemento a due e si indichi se si genera riporto sulla colonna dei bit pi significativi e se si verifica overflow. [1,5 punti] Con 7 bit si rappresentano in C2 i numeri da 64dec a +63dec; 6 bit non sono sufficienti per A. Col metodo dei resti in binario assoluto, e poi in C2: 52dec = 110100bin = 0110100C2 52dec = 1001011 + 0000001 = 1001100C2 29dec = 11101bin = 0011101C2 29dec = 1100010 + 0000001 = 1100011C2 Applicando lalgoritmo di addizione, ricordando che -52-(-29) si calcola come -52+29, e indicando (riporto) e [overflow] con le rispettive parentesi: 1 0 0 1 1 0 0 -52dec 1 1 0 0 0 1 1 -29dec (1) [1] 0 1 0 1 1 1 1 -81dec ?? 47dec !! 1 0 0 1 1 0 0 -52dec 0 0 1 1 1 0 1 29dec (0) [0] 1 1 0 1 0 0 1 -23dec

+ =

+ =

Nel primo caso si genera riporto dalla colonna dei bit pi significativi e overflow, nel secondo caso n riporto n overflow.

(q)

Si consideri il numero codificato in base tredici N = 1BA13 (le cifre utilizzate nella codifica sono {0,1,2,3,4,5,6,7,8,9,A,B,C}). Lo si converta in base sette. [0,5 punti] N10 = 1 * 132 + 11 * 131 + 10 * 130 = 169 + 143 + 10 = 322 322 : 7 46 : 7 6:7 = 46 ( resto 0 ) = 6 ( resto 4 ) = 0 ( resto 6 ) N = 1BA13 = 6407

Esercizio 34 - Sintesi di codice: vettori multidimensionali, algoritmica di base ( 6,5 punti ) Si considerino due matrici di interi A e B, di uguali dimensioni (R e C, costanti, che indicano il numero di righe e colonne). Diciamo che A domina B se, confrontando i valori in posizioni corrispondenti, risulta che il numero dei valori in A maggiori dei corrispondenti valori in B pi grande del numero di quelli di B maggiori dei corrispondenti in A e inoltre gli elementi corrispondenti non sono mai uguali (se due elementi corrispondenti sono uguali la dominanza non definita).
(e)

Si proponga un prototipo per la funzione domina(...)che riceve le matrici come parametri e restituisce 1 se la prima domina la seconda, -1 se la seconda domina la prima, 0 altrimenti; [1 punto]
int domina ( int A[][C], int B[][C] );

(f)

Si dia una descrizione breve ma precisa di un algoritmo per implementare la funzione; [1,5 punti]
Conviene scandire le due matrici in parallelo, usando un accumulatore per memorizzare il "bilancio" di dominanza tra le matrici. La scansione avviene con due cicli annidati, e l'accumulatore incrementato quando il valore maggiore in A e decrementato quando in B. Possiamo immediatamente restituire 0 appena si trovano due elementi corrispondenti uguali, poich le matrici in tal caso non si dominano. necessario invece scandire tutta la matrice prima di restituire il valore -1 o 1 (in base ai valori dell'accumulatore). Se al termine della scansione l'accumulatore vale zero si restituisce zero.

(g)

Si codifichi la funzione in C. [4 punti]


int domina ( int A[][C], int B[][C] ) { int bilancio = 0, i, j; /* Ipotizziamo che gli interi possano */ /* rappresentare il valore R*C */ for ( i=0; i<R; i++ ) for ( j=0; j<C; j++ ) { if ( A[i][j] == B[i][j] ) return 0; if ( A[i][j] > B[i][j] ) bilancio++; else bilancio--; } if ( bilancio > 0 ) return 1; if ( bilancio < 0 ) return -1; return 0; }

Esercizio 35 - Memoria Dinamica e strutture dati - ( 9+2 punti )


Si consideri la seguente definizione: typedef struct Elem { char * parola;
struct Elem * next; typedef Nodo * Lista; } Nodo;

(a)

Si disegni la struttura allocata dalle istruzioni del blocco di codice a lato, si indichino i valori di ogni variabile (sia statica sia dinamica: per i puntatori si usino le frecce, per i valori ignoti i punti interrogativi), e se ne calcoli la dimensione totale in byte (sizeof(char) = 1, sizeof(void *) = 4). [3 punti]

Nodo n, *p; char * s; s = (char *) malloc(strlen("fischio")+1); p = (Lista) malloc(sizeof(Nodo)); n.next = (Lista) malloc(sizeof(Nodo)); p->parola = (char *)malloc(strlen("maschio")+1); n.next->next = NULL; n.parola = s; p->next = n.next; s = (char *) malloc(strlen("rischio")+1); p->next->parola = s;

n p

? ? s ? ? ? ? ? ? ?

= 1 byte

Dimensione: 2 puntatori, 3 array da 8 char, 3 nodi formati da 2 puntatori = 8 ptr + 24 char = 56 byte

(b)

Due parole si dicono simili se hanno al pi due caratteri diversi. Una catena di parole si dice compatibile col telefono senza fili (cctsf) se ogni parola simile alle adiacenti. La funzione int simili (char *s1, char *s2 ); restituisce 1 se s1 e s2 sono simili, 0 altrimenti. Usando la funzione simili() (senza codificarla), si codifichi in C una funzione f(), preferibilmente ricorsiva, che riceve come parametro una lista dinamica di parole (secondo la definizione soprastante) e restituisce 1 se la lista rappresenta una catena cctsf, 0 altrimenti. [3 punti, +1 se in versione ricorsiva]
int f( Lista a ) { if ( a == NULL || a->next == NULL ) return 1; if ( !simili(a->parola, a->next->parola) ) return 0; else return f( a->next ); }

/* Versione ricorsiva */

int f( Lista a ) { /* Versione "provocatoria" */ return a==NULL || a->next==NULL || simili(a->parola, a->next->parola) && f(a->next); }

int f( Lista a ) { while ( a != NULL && a->next != NULL ) { if ( !simili(a->parola, a->next->parola) ) return 0; a = a->next; } return 1; }

/* Versione iterativa */

(c)

Si consideri poi la definizione di una lista di catene di parole (da ogni NodoTesta inizia una Lista).
typedef struct Elem2 { Lista catena; struct Elem2 * next; typedef NodoTesta * ListaDiListe; } NodoTesta;

Si codifichi (preferibilmente in modo ricorsivo: bonus +1) una funzione che riceve una lista di liste cos definita e dealloca interamente dalla lista di liste le sequenze di parole che non sono cctsf. Attenzione: si ipotizzi che nelle catene non ci siano parole allocate staticamente, e si badi a deallocare tutta la memoria dinamica. [3 punti, +1 se in versione ricorsiva]

void deallocaCatena( Lista a ) { if ( a != NULL ) { deallocaCatena( a->next ); free(a->parola); /* Bisogna deallocare anche il vettore dinamico */ free( a ); } } ListaDiListe sfrondaListaDiListe( ListaDiListe b ) { if ( b != NULL ) { ListaDiListe tmp = sfrondaListaDiListe( b->next ); if ( !f( b->catena ) ) { deallocaCatena( b->catena ); free( b ); return tmp; } else b->next = tmp; } return b; }

/* La soluzione iterativa ... troppo noiosa, e quindi lasciata per esercizio */

Esercizio 36 - Analisi di codice: puntatori, parametri, ricorsione - ( 3,5 punti )


Si consideri il seguente programma C, completando la definizione di MATR con la propria matricola.

#define MATR "......" /* Ad esempio, "623372" */ #define CIFRE "0987654321" void f( int ); int main() { f( 3 ); return 0; void g( char *, char * );

} void g( char * mat, char * cif ) { if ( *mat != '\0' ) { char * start = cif; while ( *cif != *mat ) cif++; printf("+ %c%d +", *mat, cif-start); f( 7 - strlen(mat) ); } return; }

void f( int i ) { char mat[7] = MATR; char cif[11] = CIFRE; mat[0]=mat[1]=mat[2]='A'+i-3; if ( mat[i] != '\0' ) g( mat+i, cif ); return; }
(a)

Si disegni lo stack dei record di attivazione nel momento in cui la sua "altezza" massima (cio subito prima che sia eseguita per la prima volta un'istruzione return). Si rappresentino i valori di tutte le variabili automatiche con le solite convenzioni (vettori: blocchi contigui; puntatori: frecce; valori indefiniti: punti interrogativi). [2,5 punti]

cif '0' '9' '8' '7' '6' '5' '4' '3' '2' '1' '\0'

mat 'D' 'D' 'D' '3' '7' '2' '\0'

start

cif

mat

cif '0' '9' '8' '7' '6' '5' '4' '3' '2' '1' '\0'

mat 'C' 'C' 'C' '3' '7' '2' '\0'

start

cif

mat

cif '0' '9' '8' '7' '6' '5' '4' '3' '2' '1' '\0'

mat 'B' 'B' 'B' '3' '7' '2' '\0'

start

cif

mat

f main

cif '0' '9' '8' '7' '6' '5' '4' '3' '2' '1' '\0'

mat 'A' 'A' 'A' '3' '7' '2' '\0'

Record di attivazione del main, vuoto

(b)

Si indichi inoltre la linea di output visualizzata [1 punto]

Nel caso dellesempio:

+ 37 ++ 73 ++ 28 +

Esercizio 37 Facoltativo Alberi ( incremento libero )


Si consideri un albero ternario privo di dati e rappresentato dalla seguente definizione ricorsiva:
typedef struct Elemento { struct Elemento * left, * center, * right; } Nodo; typedef Nodo * Tree;

Si progetti un algoritmo ed eventualmente si codifichi una funzione C che restituisce 1 se tutti i cammini dalla radice dell'albero alle foglie hanno la stessa lunghezza, e restituisce 0 altrimenti. Attenzione: si considerino attentamente gli esempi sottostanti.

No

No

No

int isobato ( Tree t ) {


int foo; return isobatoAux( t, &foo ); } int isobatoAux( Tree t, int * depth ) { int dl, dc, dr, il, ic, ir; /* 3 profondit per i rami e 3 booleani */ if ( t == NULL ) { *depth = 0; return 1; }

/* Un albero vuoto isobato di profondit 0 */

il = isobatoAux(t->left, &dl); ic = isobatoAux(t->center, &dc); ir = isobatoAux(t->right, &dr);

/* Controlliamo l'isobaticit dei rami */ /* Memorizzando le (eventuali) profondit */

if ( !il || !ic || !ir || dl != dc || dc != dr ) return 0; *depth = dl + 1; return 1; } /* tanto le profondit sono tutte uguali */

Informatica A a.a. 06/07 Secondo Appello 18/7/2007


Esercizio 38 - Algebra di Boole, Aritmetica Binaria, Codifica delle Informazioni ( 3 punti )
(r) Si costruisca la tabella di verit della seguente espressione booleana, badando alla precedenza tra gli operatori logici. Eventualmente si aggiungano le parentesi [1 punto]

not( A and not B ) or not A or B or A and C

con altra notazione: AB + A + B + AC


0 0 0 0 1 1 1 1 0 0 0 0 0 1 0 1 0 1 0 1 0 1 0 1

( ( not( A and not B ) or not A ) or B ) or ( A and C )


1 1 1 1 0 0 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 0 0 1 1 0 0 1 1 0 0 0 0 1 1 0 0 1 1 1 1 1 1 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 1 1 0 0 1 1 0 0 1 1 1 1 1 1 0 1 1 1

(s)

Si stabilisca il minimo numero di bit sufficiente a rappresentare in complemento a due i numeri A = 85dec e B = 43dec, li si converta, se ne calcolino la somma (A+B) e la differenza (AB) in complemento a due e si indichi se si genera riporto sulla colonna dei bit pi significativi e se si verifica overflow. [1,5 punti]

Con 8 bit si rappresentano in C2 i numeri da 128dec a +127dec; 7 bit non sono sufficienti per A. Col metodo dei resti in binario assoluto, e poi in C2: 85dec = 1010101bin = 01010101C2 85dec = 10101010 + 0000001 = 10101011C2 43dec = 101011bin = 00101011C2 43dec = 11010100 + 0000001 = 11010101C2 Applicando lalgoritmo di addizione e indicando (riporto) e [overflow] con le rispettive parentesi: + = 1 0 1 0 1 0 1 1 0 0 1 0 1 0 1 1 (0) [0] 1 1 0 1 0 1 1 0 1 0 1 0 1 0 1 1 1 1 0 1 0 1 0 1 (1) [0] 1 0 0 0 0 0 0 0 -85dec 43dec -42dec -85dec -43dec -128dec

+ =

Nel primo caso non si generano n riporto n overflow, nel secondo caso riporto ma non overflow.

(t)

Si consideri il numero codificato in base sette N = 1227; lo si converta in base undici. Le cifre delle codifiche siano A7={0,1,2,3,4,5,6} e A11={0,1,2,3,4,5,6,7,8,9,A}). [0,5 punti]

N10 = 1 * 72 + 2 * 71 + 2 * 70 = 4910 + 1410 + 210 = 6510 65 : 11 5 : 11 = 5 ( resto 10 ) = 0 ( resto 5 ) N = 1227 = 6510 = 5A11

Esercizio 39 - Sintesi di codice: array e algoritmica di base ( 7 punti )

SimpleScrabble una versione del gioco Scrabble dove il valore delle parole dipende solo dalle lettere che
le compongono. Ogni lettera ha un valore (da 1 a 10) ed disponibile in un certo numero di esemplari (da 1 a 12). Il valore di una parola la somma dei valori delle sue lettere. Gli array valore e numero, definiti nell'ambiente globale come segue, indicano il valore e il numero di esemplari disponibili di ognuna delle 26 lettere, in ordine alfabetico. Consideriamo per semplicit solo le lettere maiuscole.
int numero[26] = {9,2,2,4,12,2,3,2,9,1,1,4,2,6,8,2, 1,6,4,6,4,2,2,1,2, 1}; int valore[26] = {1,2,3,2, 1,4,2,4,1,8,5,1,3,1,1,3,10,1,1,1,1,4,4,8,4,10};

Ci sono quindi 9 A da 1 punto, 2 B da 2 pt, 2 C da 3 pt, 4 D da 2 pt, 12 E da 1 pt, , 1 Z da 10 pt. Una funzione punteggio() calcola e restituisce il valore della parola ricevuta come parametro, assegnando 0 alle parole non valide. Una parola valida se di almeno 2 lettere ed ottenibile con le lettere a disposizione (CAB vale 3+1+2=6 pt, KARAOKE non valida, perch disponiamo di una sola K). (h) Si propongano prototipi opportuni per la funzione punteggio(...) e per una funzione valida(...), che restituisce 1 se la parola valida, 0 altrimenti; [1 punto]
int punteggio ( char parola[] ); int valida ( char parola[] );

(i)

Si codifichi in C la funzione punteggio(...), utilizzando valida(...) per stabilire la validit [3 punti]

int punteggio ( char parola[] ) { int punt = 0, lun; if ( valida(parola) ) for ( lun = strlen(parola)-1 ; lun >= 0 ; lun-- ) punt += valore[parola[lun]-'A']; return punt; }

(j)

Si codifichi in C anche la funzione valida(...). [3 punti]

int valida ( char parola[] ) { int i, p, cont, lun = strlen(parola); if ( lun < 2 ) return 0; for ( i=0 ; i<26 ; i++ ) { cont = 0; for( p=0 ; p<lun ; p++ ) if ( parola[p] == 'A'+i ) cont++; if ( cont > numero[i] ) return 0; } return 1; }

Esercizio 40 - Memoria Dinamica e strutture dati - ( 10+2 punti )


Si consideri la seguente definizione: typedef struct Elem { int x, y;
struct Elem * next; typedef Punto * Linea; } Punto;

Una linea spezzata rappresentata da una lista di punti. Si noti che i punti hanno coordinate intere. Definiamo una spezzata aperta non degenere (AND) se i suoi punti sono almeno due e tutti diversi tra loro. Consideriamo solo spezzate AND. La lunghezza di una spezzata la somma delle distanze euclidee tra punti consecutivi (i segmenti che la compongono). Date due spezzate A e B, diciamo che A e B sono disgiunte se non hanno punti in comune, che B una scorciatoia di A se A e B hanno gli stessi estremi ma B ha lunghezza minore, che A contiene B se B una sottosequenza di A, e che A estende B se A contiene B e hanno l'ultimo punto in comune, e infine definiamo la concatenazione di due liste disgiunte A e B come la linea C = AB ottenuta aggiungendo B in coda ad A. Se A e B non sono disgiunte, AB = . Si progettino e codifichino opportunamente le funzioni seguenti.
int scorciatoia(Linea A, Linea B) [3 punti] int estende(Linea A, Linea B) [3 punti] Linea concatena(Linea A, Linea B) [2 punti]
restituisce 1 se A scorciatoia di B, 0 altrimenti restituisce 1 se A estende B, 0 altrimenti restituisce C=AB riusando i nodi di A e B.

float tortuosit(Linea A) [2 punti] restituisce il rapporto tra la lunghezza di A e la distanza tra i suoi estremi

Bonus per l'uso diffuso della ricorsione (laddove abbia senso): 2 punti E' indispensabile, per una valutazione positiva, definire e codificare altre opportune funzioni di supporto da usare (ed eventualmente riusare) per definire in modo pi compatto e leggibile le funzioni richieste.
int uguali( Punto a, Punto b ) { return a.x == b.x && a.y == b.y; } int numNodi( Linea A ) { if ( A == NULL ) return 0; return 1 + numNodi(A->next); } /* Punti uguali se coordinate uguali */

/* Conteggio ricorsivo standard dei nodi */

int AND( Linea A ) { /* Iterativa: vers. ricorsiva lasciata per esercizio */ Linea tmp; if ( numNodi(A) < 2 ) /* Se non almeno un segmento, non AND */ return 0; while( A != NULL ) { /* Per ogni punto */ tmp = A->next; while ( tmp != NULL ) { /* vediamo se ce n' un altro uguale */ if ( uguali(*A, *tmp) ) return 0; /* e se c' restituiamo subito 0 */ tmp = tmp->next; } A = A->next; } return 1; /* Se arriviamo in fondo, AND */ }

Definita la funzione AND(),ipotizziamo senza ledere la generalit che le linee passate come parametro alle altre funzioni siano AND, a cura dei rispettivi programmi chiamanti (che possono verificare i parametri prima di procedure all'invocazione). Ci coerente con l'indicazione della traccia "consideriamo solo spezzate AND". Punto * last( Linea A ) { if ( A == NULL || A->next == NULL ) return A; return last(A->next); } /* Un puntatore all'ultimo punto */ /* (se non c' nessun punto, NULL) */

float dist( Punto p, Punto q ) { return sqrt( (p.x-q.x)*(p.x-q.x)+(p.y-q.y)*(p.y-q.y) ); }

float lung( Linea A ) { if ( A == NULL || A->next == NULL ) return 0.0; return dist(*A, *(A->next)) + lung(A->next); } int lineeUguali( Linea A, Linea B ) { if ( A == NULL && B == NULL ) return 1; if ( A == NULL || B == NULL ) return 0; return uguali(*A, *B) && lineeUguali(A->next, B->next); }

Passiamo quindi a definire le funzioni richieste:

int scorciatoia( Linea A, Linea B ) { if ( uguali(*A, *B) && uguali(*last(A), *last(B)) ) return lung(A) < lung (B); return 0; } int estende( Linea A, Linea B ) { if ( A == NULL ) return 0; if ( uguali(*A, *B) ) return lineeUguali(A, B); return estende(A->next, B); }

/* N.B.: B AND per ipotesi */

Linea concatena( Linea A, Linea B ) { (last(A))->next = B; return A; } float tortuosit( Linea A ) { return lung(A) / dist(*A, *last(A)); }

Esercizio 41 - Analisi di codice: puntatori, parametri, ricorsione - ( 4 punti )


Si consideri il seguente programma C, completando la definizione di MATR con la propria matricola. #define MATR "......" void f( char *, int ); int main() { char mat[7] = MATR; f( mat, 10 ); return 0; } void g( char **pp ) { char i; if ( strlen((*pp)+4) ) { for ( i=*((*pp)+4) ; i!='0' ; i-- ) printf("%s", "A"); f( (*pp)+1, 10 ); } return; }
/* Ad esempio, "623372" */

void g( char ** );

void f( char *p, int k ) { char c = *p; char *x = &c; printf(" %d ", (c-'0')*k); *x = '\0'; if ( p[0] != '\0' ) g( &p ); return; } (c)

Si disegni lo stack dei record di attivazione nel momento in cui la sua "altezza" massima (cio subito prima che sia eseguita per la prima volta un'istruzione return). Si rappresentino i valori di tutte le variabili automatiche con le solite convenzioni (vettori: blocchi contigui; puntatori: frecce; valori indefiniti: punti interrogativi). [2,5 punti]
i ?

pp

k 10

c '\0'

i '0'

pp

k 10

c '\0'

i '0'

pp

f main

k 10

c '\0'

Record di attivazione del main contiene solo l'array mat

mat '6' '2' '3' '3' '7' '2' '\0'

(d)

Si indichi la linea stampata dal programma su stdout. [1 punto]

Nel caso dellesempio:


(e)

60 AAAAAAA 20 AA 30

Si discuta il comportamento del programma nel caso in cui il main sia modificato come segue [0,5 punti] L'istruzione in pi sostituisce il '\0' terminatore della matricola con un carattere. Cos non ci sono pi garanzie di terminazione (non detto che in memoria nelle posizioni successive ci siano dei bit interpretabili come '\0'), e l'esecuzione prosegue fino a generare un errore per violazione di memoria o fino a quando non si incontra (casualmente) una sequenza di bit effettivamente pari a '\0'.

int main() { char mat[7] = MATR; *(mat+6) = *mat; f( mat, 10 ); return 0; }

Informatica A a.a. 06/07 Terzo Appello 21/9/2007


Esercizio 42 - Algebra di Boole, Aritmetica Binaria, Codifica delle Informazioni ( 3 punti )
(u) Si costruisca la tabella di verit della seguente espressione booleana in quattro variabili, badando alla precedenza tra gli operatori logici. Eventualmente si aggiungano le parentesi [1,5 punti]

( A or B and C ) and ( not A or C and D )


0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 0 0 1 1 0 0 1 1 0 0 1 1 0 0 0 0 0 0 1 1 0 0 0 1 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 0 0 0 1 0 0 1 1 0 0 1 1 0 0 0 1 0 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1

con altra notazione: (A+BC)(A+CD)

Poich fin qui not A 1, CD irrilevante Poich da qui in gi A=1, BC irrilevante

In alternativa, svolgendo il prodotto:


(A+BC)(A+CD) = AA+ACD+ABC+BCD = ACD+ABC+BCD = (ABCD+ABCD)+(ABCD+ABCD)+(ABCD+ABCD) = ABCD+ABCD+ABCD+ABCD

(v)

Si stabilisca il minimo numero di bit sufficiente a rappresentare in complemento a due i numeri A = 200dec e B = 60dec, li si converta, se ne calcolino la somma (A+B) e la differenza (AB) in complemento a due e si indichi se si genera riporto sulla colonna dei bit pi significativi e se si verifica overflow. [1,5 punti]

Con 9 bit si rappresentano in C2 i numeri da 256dec a +255dec; 8 bit non sono sufficienti per A. Col metodo dei resti in binario assoluto, e poi in C2: 200dec = 11001000bin = 011001000C2 60dec = 1111100bin = 000111100C2 60dec = 111000011 + 0000001 = 111000100C2 Applicando lalgoritmo di addizione, ricordando che 200-(-60) si calcola come 200+60, e indicando (riporto) e [overflow] con le rispettive parentesi: + = 0 1 1 0 0 1 0 0 0 200dec 1 1 1 0 0 0 1 0 0 -60dec (1) [0] 0 1 0 0 0 1 1 0 0 140dec

+ =

0 1 1 0 0 1 0 0 0 200dec 0 0 0 1 1 1 1 0 0 60dec (0) [1] 1 0 0 0 0 0 1 0 0 260dec ?? -252dec !!

Nel primo caso si genera riporto dalla colonna dei bit pi significativi ma non overflow, nel secondo caso non vi riporto ma c' overflow (il risultato eccede l'intervallo di rappresentazione).

Esercizio 43 - Sintesi di codice: array e algoritmica di base ( 7 punti )


Una matrice quadrata di NxN punti del piano cartesiano definita come segue:
typedef struct Point { float x, y; } Punto; typedef Punto Matrice[N][N];

Gli N punti della diagonale, quelli di ogni riga e quelli di ogni colonna definiscono linee spezzate di N-1 lati. Diciamo che una matrice di punti regolare se la lunghezza della spezzata definita dalla diagonale maggiore della lunghezza di tutte le spezzate definite dalle righe e dalle colonne della matrice. (k) Si proponga un prototipo per la funzione regolare(...) che, data una matrice, restituisce 1 se regolare, 0 altrimenti; [1 punto] int regolare( Matrice m ); (l) oppure int regolare( Punto m[][N] );

Si codifichi in C la funzione regolare(...), sapendo di poter disporre di una funzione di prototipo float dist( Punto a, Punto b ) che calcola la distanza euclidea tra due punti. [4,5 punti]

int regolare( Matrice m ) { int i, j; float lunD = 0.0, lunR, lunC; for ( i = 0 ; i < N-1 ; i++ ) lunD += dist( m[i][i], m[i+1][i+1] ); for ( i = 0 ; i < N ; i++ ) { lunR = lunC = 0.0;

/* La diagonale principale */

/* Azzero per la nuova riga(colonna) */

for ( i = 0 ; i < N-1; i++ ) { lunR += dist( m[i][j], m[i][j+1] ); lunC += dist( m[j][i], m[j+1][i] ); }

/* i-esimo segmento riga */ /* i-esimo segmento colonna */

if ( lunR > lunD || lunC > lunD ) /* Basta una sola lunghezza maggiore */ return 0; } return 1; }

(m)

Si proponga una modifica alla funzione codificata in (b) per fare il confronto con la spezzata definita dalla diagonale secondaria (invece che con quella definita dalla diagonale principale). Si cerchi di individuare la minima modifica necessaria. La diagonale principale va dall'elemento 0,0 all'elemento N-1,N-1, la diagonale secondaria va dall'elemento 0,N-1 all'elemento N-1,0. [1,5 punti]

sufficiente calcolare diversamente lunD, nel primo ciclo for, lasciando inalterato tutto il resto:
lunD += dist( m[i][N-1-i], m[i+1][N-2-i] ); /* La diagonale secondaria */

Esercizio 44 - Analisi di codice: puntatori, parametri, ricorsione - ( 5 punti )


Si consideri il seguente programma C.
void f( int a, int *b ) { if ( *b < 2 || *b > 10 ) return; if ( a / *b > 0 ) f( a / *b, b ); printf ( "%i", a % *b ); return; } int main() { int a[4] = { 5, 200, 100, 4565204 }; int b[4] = { 2, 2, 5, 10 }, i; for ( i = 0; i < 4; i++ ) { f( *(a+i), b+i ); printf("\n"); } return 0; }

Si indichi la funzionalit realizzata dalla funzione ricorsiva f(). Non si chiede di descrivere il codice, ma di dire sinteticamente a che cosa serve (esempio: "Visualizza tutti i divisori pari dei parametri"). [4 punti]

La funzionalit realizzata la stampa di ogni numero dellarray a codificato nella base del corrispondente elemento dellarray b

Si indichi l'output del programma su stdout. [1 punto]


101 11001000 400 4565204