2a Prova parziale di Programmazione 26 Gennaio 2003 TESTO e RISPOSTE Attenzione questa è una delle varianti del compito Esercizio 1 Comprensione codice C (punti 6 in prima approssimazione) Consideriamo il seguente codice C #include <stdio.h> int k = 13; /* i compiti differivano qui void p1(void) ; void p2(int) ; void p3(int, int *) ; void p4(void) ; main() { int j; int n = 7 ; /* p1 ( ) ; printf ("main 1: p2 ( n ) ; printf ("main 2: j = 1 ; p3 (n, &j) ; printf ("main 3: p4 ( ) ; } void p1(void) { void p2(int x) { i compiti differivano qui */ */ %d \n", k ) ; %d \n", n ) ; %d %d \n", j , n ) ; k++ ; x++ ; printf ("p1: printf ("p2: %d \n", k ) ; %d \n", k+x ) ; } } void p3(int x, int * y) { int aux = x; x = *y; *y = aux; printf ("p3: %d %d \n", x , *y ) ; } int q(int) ; void p4(void) { int k = 3 ; printf ( "p4: %d \n", q(k) ) ; } int q(int w) { return (k+w); } Sul foglio risposte scrivere l'output del programma; tutto l'output ! (l'unica cosa che potete ignorare sono gli spazi bianchi) 1 Risposta p1: ( Commenti NON richiesti ) 14 main 1: p2: 14 22 main 2: ( la variazione di k e` vista dal main ) ( = 14 + 8 ) 7 ( la variazione di n fatta da p2 non viene vista nel main perche` n e` stato passato per valore ) p3: 1 7 main 3: 7 p4: ( dentro p3 i valori vengono scambiati ) 7 17 ( all'uscita pero` n ritorna al valore precedente) ( il k che compare in q e` quello dichiarato all'inizio e vale 14 ) Esercizio 2 Progetto di algoritmo (punti 16 in prima approssimazione) Poker semplificato Carte (in ordine di valore crescente): 7, 8, 9, 10, Fante, Donna, Re, Asso; per ogni valore ci sono 4 carte (di "seme" diverso: cuori, quadri, picche, fiori; ma in quello che segue il seme non conta); per un totale di 32 carte. Ogni giocatore riceve 5 carte. Combinazioni interessanti: coppia: 2 carte di ugual valore (ma di seme diverso) tris: 3 carte di ugual valore (ma di seme diverso) poker: 4 carte di ugual valore (ma di seme diverso) coppia < tris < poker a parità di combinazione vince quella con carte di valore più alto; a parità di valore, abbiamo "partita pari"; se un giocatore ha 2 coppie, si considera solo quella con carte di valore piu alto; se ha un tris ed una coppia, si considera solo il tris. Si tratta di progettare un algoritmo per questo gioco. Più precisamente: due soli giocatori: A e B; il "mazzo di carte" è rappresentato da un file di 32 righe; quindi ogni riga rappresenta una carta (come, fa parte dell'esercizio); supponiamo che il file sia corretto e rappresenti un mazzo già "mescolato"; l'algoritmo, per prima cosa, serve le carte: la prima carta del mazzo ad A, la seconda a B, la terza ad A, la quarta a B,...., fino a che ciascun giocatore ha 5 carte; a questo punto, l'algoritmo controlla cosa hanno in mano i due giocatori e decide chi ha vinto (oppure se c'è parità). L'algoritmo deve essere strutturato come segue: il "main" apre il file poi per servire le carte chiama una procedura servi poi per capire cosa ha in mano A chiama una procedura/funzione conta poi per capire cosa ha in mano B chiama nuovamente conta in base alle info ricevute dalle due chiamate, il main decide chi ha vinto. 2 Non si possono usare "variabili globali". I passaggi di dati tra main e procedure e funzioni devono avvenire tramite parametri e il risultato delle funzioni. Si possono usare procedure / funzioni ausiliarie. Domande a) Come si rappresentano le carte nel file ? Se non trovo la risposta non correggo il resto. b) Come si rappresenta il gruppo di 5 carte in mano a ciascun Se non trovo la risposta non correggo il resto. giocatore ? c) Come si codificano le combinazioni; ad es. come si codifica "A ha un tris di fanti " ? Se non trovo la risposta non correggo il resto. d) Per la procedura servi: dire quali sono i parametri e dare un'idea di come funziona (a parole o in pseudo-codice ad alto livello). Se non trovo la risposta non correggo il codice di servi. e) La stessa cosa per conta. f) Scrivere, usando lo pseudo-codice utilizzato nelle dispense, oppure il C, l'algoritmo completo, commentando, quando necessario. A voce ho detto che: dato il tipo di esercizio, bisognava pensare bene alla parte di "progetto" (es domande b e c) anche a scapito del dettaglio nellarisposta alla domanda f). Risposte (ovviamente qui ci sono alternative ....) a) visto quello che dobbiamo fare, un modo semplice e` di usare dei numeri: 7, 8, 9, 10, 11 (Fante), 12 (Donna), 13 (Re), 14 (Asso) b) per semplificarci la vita in quello che segue, un modo e` : al giocatore A associo aa : array [ 7 .. 14 ] of integer aa [ k ] = x con l'idea che dopo aver servito le carte sse A ha ricevuto x carte di valore k Analogamente per B Nota: sia in a) che in b) sarebbe piu' elegante usare dei tipi enumerazione type Carte = {SETTE, OTTO, NOVE, DIECI, FANTE, DONNA, RE, ASSO} type Combinazioni = { NULLA, COPPIA, TRIS, POKER } eccetera ............... c) per facilitare il confronto (nella parte finale dell'algoritmo), possiamo rappresentare il punteggio di un giocatore con una coppia, cioè un record a due campi interi : type risultato = record { combi, carta : integer } 3 il campo combi ha 4 possibili valori : 2 (coppia), 3 (tris), 4 (poker), 0 (altrimenti) il campo carta ha valore : 7, 8, 9, ...14 ad A associo il record ra, a B il record rb quindi ra = [ 3 , 14 ] vuol dire che A ha un tris d'assi d) la procedura servi: ha 3 parametri : la procedura è molto semplice: legge dal file un numero alla volta e aggiorna, alternando, i due array .............. il file "mazzo" , parametro IN due parametri OUT che sono array [ 7 .. 14 ] of integer e) conta: e` abbasatanza naturale usare una funzione: function conta ( x : array [ 7 .. 14 ] of integer ) : risultato si usa una variabile ausiliaria res di tipo risultato con i due campi inizializzati a 0 si legge l'array x con un ciclo per k da 7 a 14 se x[ k ] è minore di 2 (nemmeno una coppia di valore k) non si fa niente, altrimenti : si confronta x[ k ] col valore corrente di res.combi: se x[ k ] < res. combi non si fa niente se x[ k ] > res. combi allora res. combi x[ k ] se x[ k ] = res. combi allora res.carta k alla fine la funzione restituisce res f) Algoritmo completo in pseudo-codice (senza commenti, perche` mi sembra che dopo le spiegazioni di sopra sia tutto chiaro ...) dichiarazioni type risultato = record { combi, carta : integer } procedura servi ( fmazzo : file .... IN a , b : array [ 7 .. 14 ] of integer OUT ) per i dettagli vedere sotto function conta ( x : array [ 7 .. 14 ] of integer ) : risultato per i dettagli vedere sotto variabili: ra, rb : risultato aa, bb : array [ 7 .. 14 ] of integer mazzo : file 4 istruzioni apri il file mazzo in lettura if errore nell'aprire il file then scrivi ( "errore" ) else { servi (mazzo, aa, bb) chiudi mazzo ra conta(aa) rb conta(bb) 5 if ra.comb < rb. comb then scrivi ( "vince B ") else if ra.comb > rb. comb then scrivi ( vince A ") else [ quindi a parita` di combinazione ] if ra.carta < rb. carta then scrivi ( "vince B ") else if ra.carta > rb. carta then scrivi ( "vince A ") else scrivi ( " partita pari ") procedura servi ( fmazzo : file .... IN a , b : array [ 7 .. 14 ] of integer OUT ) { variabili k , carta : integer a[ k ] 0 per k = 7, 8, ..., 14 : per k = 1, 2,... 5 : { ed anche b[ k ] 0 leggi (fmazzo, carta) a [ carta ] ++ leggi (fmazzo, carta) b [ carta ] ++ } } function conta ( x : array [ 7 .. 14 ] of integer ) : risultato { variabili: k intera, res di tipo risultato res.comb 0 ; res.carta 0 per k = 7, 8, 9, ..., 14: if x[k] > 1 then if x[k] > res.comb then { res.comb x[k]; res.carta k } else if x[k] = res.comb then res.carta k return (res); } /* Programma per il poker in C con pochi commenti dato che quasi tutto e` stato spiegato prima il programma contiene delle istruzioni di output "non richieste" 6 che pero` sono comode per verificare che funziona ..... si usano numeri per codificare le carte e le combinazioni un modo piu' elegante di fare le cose e` usare le enumerazioni */ #include <stdio.h> #define CARTE 8 typedef struct { int comb; int carta ; } risultato ; void servi ( FILE * , int a[ ], int b[ ] ) ; risultato conta ( int x [ ] ); main() { risultato ra, rb ; int aa[ CARTE ] ; /* aa [ j- 7 ] == quante carte di valore j int bb[ CARTE ] FILE mazzo ; possiede A */ /* idem per B */ * mazzo ; = fopen("mazzo","r"); if (mazzo == NULL ) else { printf ( "ERRORE DATI" ); servi (mazzo, aa, bb) ; fclose (mazzo) ; ra = conta(aa); rb = conta(bb); printf("\n %d %d", ra.comb, ra.carta); printf("\n %d %d", rb.comb, rb.carta); if (ra.comb < rb. comb) printf("\n vince B \n"); else if (ra.comb > rb. comb) printf("\n vince A \n"); else if (ra.carta < rb. carta) printf("\n vince B \n"); else if (ra.carta > rb. carta) printf("\n vince A \n"); else printf("\n partita pari \n"); } } void servi ( FILE * fmazzo, int a[ ], int b[ ] ) { int k , carta; for (k=0; k < CARTE; k++) a[ k ] = 0; for (k=0; k < CARTE; k++) b[ k ] = 0; for ( k = 1 ; k <= 5 ; k++ ) { fscanf (fmazzo, "%d", &carta) ; a[carta - 7]++; fscanf (fmazzo, "%d", &carta) ; b[carta - 7]++; } 7 printf("\n"); for (k=0; k < CARTE; k++) printf("\n"); for (k=0; k < CARTE; k++) printf("\n"); for (k=0; k < CARTE; k++) printf("%d ", k+7); printf("%d ", a[ k ] ); printf("%d ", b[ k ] ); } risultato conta ( int x[ ] ) { int k; risultato res; res.comb = 0; res.carta = 0; for (k = 0; k < CARTE ; k++) if ( x[k] > 1 ) if ( x[k] > res.comb ) { res.comb = x[k]; res.carta = k+7; } else if ( x[k] == res.comb ) res.carta = k+7; return (res); } Esercizio 3 Complessità (punti 8 in prima approssimazione) L'algoritmo che segue simula un torneo ad "eliminazione diretta"; la procedura elimina, ad ogni passo, simula una fase di eliminazione. Per semplicità supponiamo che i concorrenti siano n, con n che è potenza di 2; inoltre supponiamo che i concorrenti siano numeri interi diversi; tra due vince il maggiore. Algoritmo: const MAX = ............. var: n, k, j : int concorrenti : array [1 .. MAX ] of int leggi ( n ) qui si suppone n ≤ MAX per k = 1, 2, 3, .... n : leggi (concorrenti[ k ] ) j n div 2 divisione intera while ( j > 0 ) { elimina ( concorrenti, j ) j j div 2 } 8 stampa ( concorrenti [1]) ecco il vincitore ! ======================= procedura elimina ( aa : array [1 .. MAX ] of int IN-OUT , sup : int IN ) ; { qui si suppone sup ≤ MAX var i : int ; per i = 1, 2, 3 ..., sup: if aa[ 2*i - 1 ] < aa[ 2*i ] then aa[ i ] aa[ 2*i ] else aa[ i ] aa[ 2*i - 1 ] } Domande: a) Calcolare la complessità della chiamata: elimina ( concorrenti, j ) in funzione di j b) Calcolare la complessità dell'algoritmo in funzione di n Possibilmente dare stime in ( ...). Non fare conti troppo dettagliati, ma non limitarsi a dare il risultato o poco più; un risultato giusto, ma non motivato, non verrà contato..... Domande: a) Indichiamo con TT ( j ) funzione di j . la complessità della chiamata: elimina ( concorrenti, j ), in I costi sono tutti costanti (passaggio dei parametri, valutazione delle espressioni, esecuzione dell' istruzione if-then-else); il costo totale dipende solo da quante volte si esegue l' if-then-else (cioè quante volte si ripete il ciclo for). Quindi TT ( j ) = a j + b (con a, b costanti oppertune e a> 0) dunque TT ( j ) è in ( j ). b) Indichiamo con T ( n ) la complessità dell'algoritmo, in funzione di n. I costi sono tutti costanti, tranne che quello delle istruzioni per Il costo di per k = 1 ... n : .... e while è ovviamente lineare in n Il while si ripete per j = n/2, n/4, ..., n/(2i), ..., n/n il costo del while è dunque dato dalla sommatoria (c, d costanti, c > 0 ) (j = n/2, ....) (c j + d) = c (j = n/2, ....) j + (j = n/2, ....) d La seconda sommatoria ha per valore circa d( log2 n ) infatti gli j sono circa log2 n 9 La prima è c (n/2 + n/4 + ..... ) = c n ( 1/2 + 1/4 + 1/8 + ..... 1/n) = c n (1 - 1/n) [ vedere (*) sotto ] quindi è circa c n Sommando tutto, il termine dominante e` lineare in n quindi (*) T ( n ) è in ( n ). notare che : 1/2 + 1/4 = 1 - 1/4 1/2 + 1/4 + 1/8 = 1 - 1/8 1/2 + 1/4 + 1/8 + 1/16 = 1 - 1/16 eccetera questo si vede ancora meglio disegnando una "torta" 10