Scritto Settembre 2008

Di seguito la soluzione commentata dell’esame scritto di Settembre 2008 (traccia, soluzione completa).

Esercizio 1

Ci viene chiesto di implementare un sistema per gestire i Gran Premi di Formula 1.

Identifichiamo subito la classe Circuito, che ha un nome (stringa), una nazione (stringa), una lunghezza (float) e un numero di giri (intero). L’interfaccia di tale classe sarà la seguente.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Circuito
{
public:
    Circuito();
 
    string getNome() const { return nome; }
    void setNome(string value) { nome = value; }
 
    string getNazione() const;
    void setNazione(string value);
 
    float getLunghezza() const;
    void setLunghezza(float value);
 
    int getGiri() const;
    void setGiri(int value);
 
private:
    string nome;
    string nazione;
    float lunghezza;
    int giri;
};

Alle linee 6 e 7 è stata fornita anche l’implementazione di un metodo get e di un metodo set, quindi abbiamo già soddisfatto uno dei requisiti della traccia (senza usare troppa penna).

Un Gran Premio è identificato da una data e si svolge in un circuito. Per ogni Gran Premio siamo interessati ai tempi dei giri delle diverse fasi: prove libere, prove ufficiali, warmup e gara. Di ogni giro ci interessa il numero progressivo (ad esempio se è il decimo giro di gara), la vettura che l’ha effettuato e il tempo che la vettura ha impiegato per completarlo. A sua volta, di una vettura ci interessa il numero, la squadra a cui appartiene e il pilota che la guida. Vediamo come spezzare tutte queste informazioni.

Iniziamo dalla vettura, che può essere modellata tramite la seguente classe.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Vettura
{
public:
    Vettura();
 
    int getNumero() const;
    void setNumero(int value);
 
    string getSquadra() const;
    void setSquadra(string value);
 
    string getPilota() const;
    void setPilota(string value);
 
private:
    int numero;
    string squadra;
    string pilota;
};

Poiché per squadra e pilota non c’è la necessità di memorizzare informazioni strutturate né di eseguire particolari operazioni, se non al più un confronto di uguaglianza, una stringa sarà più che sufficiente.

A questo punto possiamo raggruppare le informazioni relative a un singolo giro in una classe. Il numero progressivo sarà rappresentato dal campo numero di tipo intero, mentre la vettura da un’istanza della classe Vettura. Per il tempo, visto che in aula ne abbiamo poco, scegliamo la soluzione più semplice e utilizziamo un intero che rappresenti i millisecondi (magari mettiamo un commento, per non lasciare dubbi a chi leggerà il nostro codice).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Giro
{
public:
    Giro();
 
    int getNumero() const;
    void setNumero(int value);
 
    // tempo espresso in millisecondi
    int getTempo() const;
    void setTempo(int value);
 
    Vettura getVettura() const;
    void setVettura(Vettura value);
 
private:
    int numero;
    int tempo;
    Vettura vettura;
};

Per il Gran Premio ci interessavano data, circuito e giri: conviene definire una nuova classe. Visto che non dovremo fare operazioni sulla data, e tenendo sempre d’occhio l’orologio, utilizziamo una stringa per rappresentarla. Per il circuito abbiamo definito una classe e per i giri utilizzeremo quattro liste di Giro.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class GranPremio
{
public:
    GranPremio();
 
    Data getData() const;
    void setData(Data value);
 
    Circuito getCircuito() const;
    void setCircuito(const Circuito & value);
 
    list<giro> & getGiriProveLibere();
    list<giro> & getGiriProveUfficiali();
    list<giro> & getGiriWarmUp();
    list<giro> & getGiriGara();
 
    const list<giro> & getGiriProveLibere() const;
    const list<giro> & getGiriProveUfficiali() const;
    const list<giro> & getGiriWarmUp() const;
    const list<giro> & getGiriGara() const;
 
    // Determina il tempo finale del pilota (la somma di tutti i suoi giri)
    int calcolaTempoFinale(string pilota) const;
 
    // Determina il pilota che ha effettuato il miglior giro in gara
    string pilotaGiroRecord() const;
 
private:
    Data data;
    Circuito circuito;
    list<giro> giriProveLibere;
    list<giro> giriProveUfficiali;
    list<giro> giriWarmUp;
    list<giro> giriGara;
};

Non si badi ai metodi definiti alle linee 22-26, la loro utilità sarà spiegata nel seguito.

Non ci resta che definire una classe che rappresenti l’intero sistema (durante la correzione la presenza/assenza di questa classe fa spesso la differenza). Chiameremo questa classe FIA, dalla sigla della Federazione Italiana Automobilismo (ma anche Sistema andrebbe bene). In questa classe manterremo la lista dei Gran Premi disputati e, per nostra comodità, la lista dei piloti iscritti alla federazione (per i piloti abbiamo usato una semplice stringa, quindi avremo una lista di stringhe). Inoltre, definiamo i metodi che la seconda parte dell’esercizio ci chiede di implementare.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class FIA
{
public:
    FIA();
 
    list<string> & getPiloti();
    list<granPremio> & getGranPremi();
 
    const list<string> & getPiloti() const;
    const list<granPremio> & getGranPremi() const;
 
    // esercizio b.1: Dato un gran premio determinare il pilota vincitore
    string determinaVincitore(const GranPremio & gp) const;
 
    // esercizio b.2: Determinare il nome del pilota che detiene il giro record
    //                in gara per un dato gran premio
    string determinaPilotaGiroRecord(const GranPremio & gp) const;
 
private:
    list<string> piloti;
    list<granPremio> granPremi;
};

L’esercizio b.1 ci chiede di implementare un metodo che dato un Gran Premio determini il pilota vincitore. Questo metodo è determinaVincitore(), che riceve proprio una istanza di GranPremio e restituisce una stringa, ovvero il pilota vincitore. L’implementazione sarà la seguente.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
string
FIA::determinaVincitore(
        const GranPremio & gp) const
{
    // inizializza il vincitore al primo pilota
    list<string>::const_iterator it = piloti.begin();
    string vincitore = *it;
 
    // calcola il tempo finale del vincitore (corrente)
    int tempoVincitore = gp.calcolaTempoFinale(*it);
    it++;
 
    // per ogni pilota
    for(; it != piloti.end(); it++)
    {
        // calcola il suo tempo finale
        int tempo = gp.calcolaTempoFinale(*it);
 
        // se il tempo è inferiore a quello del vincitore (corrente)
        if(tempo < tempoVincitore)
        {
            // abbiamo un nuovo vincitore
            vincitore = *it;
            tempoVincitore = tempo;
        }
    }
 
    return vincitore;
}

Iteriamo sui piloti iscritti alla federazione e inizializziamo il vincitore al primo di questi, calcolando il suo tempo totale di gara tramite il metodo calcolaTempoFinale(string) di Gran Premio (che DOBBIAMO ancora implementare). Per ogni altro pilota andiamo quindi a verificare se il tuo tempo totale di gara è minore di quello del vincitore; in questo caso aggiorniamo la variabile vincitore. Occorre precisare che inizializzare la variabile tempoVincitore a 0 sarebbe un errore, poiché stiamo cercando il minimo fra numeri positivi (e quindi lo 0 è sicuramente minore di tutti questi valori). Potrebbe andare bene inizializzare la variabile a MAX_INT.

L’implementazione del metodo ausiliario è la seguente.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int
GranPremio::calcolaTempoFinale(
        string pilota) const
{
    // inizializza il tempo a ZERO
    int tempo = 0;
 
    // per ogni giro del GP
    for(list<giro>::const_iterator it = giriGara.begin();
        it != giriGara.end(); it++)
        // se il giro è del pilota
        if(it->getVettura().getPilota() == pilota)
            // aggiungi il tempo del giro al tempo totale del pilota
            tempo += it->getTempo();
 
    return tempo;
}

Niente di strabiliante. Inizializziamo il tempo totale a 0 (visto che dobbiamo fare una somma). Quindi iteriamo su tutti i giri di gara e sommiamo i tempi del pilota dato in input.

Per l’esercizio b.2 dobbiamo implementare un metodo che dato un Gran Premio determini il pilota che detiene il giro record in gara. Definiamo allora il metodo determinaPilotaGiroRecord() che riceve in input un Gran Premio e restituisce una stringa rappresentante il pilota che ha eseguito nel minor tempo un giro di gara.

1
2
3
4
5
6
7
8
string
FIA::determinaPilotaGiroRecord(
        const GranPremio & gp) const
{
    // visto che bisogna accedere a molte strutture di GranPremio,
    // meglio implementare il metodo in quella classe
    return gp.pilotaGiroRecord();
}

Come specificato nel commento, deleghiamo il lavoro sporco alla classe GranPremio.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
string
GranPremio::pilotaGiroRecord() const
{
    // inizializza il giro record al primo giro del GP
    list<giro>::const_iterator it = giriGara.begin();
    list<giro>::const_iterator record = it;
    it++;
 
    // per ogni giro del GP
    for(; it != giriGara.end(); it++)
        // se il tempo è inferiore
        if(it->getTempo() < record->getTempo())
            // abbiamo un nuovo record
            record = it;
 
    // restituisci il pilota che ha effettuato il miglior giro
    return record->getVettura().getPilota();
}

Dobbiamo ancora una volta eseguire una ricerca di minimo. Inizializziamo il giro record al primo giro di gara e quindi iteriamo. Se troviamo un giro con tempo inferiore aggiorniamo il minimo. Alla fine restituiamo il pilota che detiene il giro record.

Tutti i requisiti della traccia sono stati soddisfatti. Possiamo quindi passare al secondo esercizio.

Esercizio 2

Il secondo esercizio chiede di implementare, tramite l’ereditarietà, una coda di interi dal prodotto limitato, ovvero una coda di interi nella quale il prodotto degli elementi è sempre inferiore a un intero dato.

Senza sforzarci troppo con la fantasia, chiameremo la nostra classe CodaIntProdottoLimitato. Ora dobbiamo scegliere da quale classe ereditare e in che modo. Possiamo estendere vector<int> o supporre che esista una classe CodaInt (magari anche implementarla). Nel primo caso dovremmo ereditare in modo privato e define i metodi utili per una coda, ovvero un metodo per l’inserimento e uno per l’estrazione. Se optiamo per la seconda scelta, invece, ereditiamo in modo pubblico perché i metodi della nuova classe saranno quelli di una coda.

La classe che estende vector<int> è la seguente.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class CodaIntProdottoLimitato : private vector<int>
{
public:
    CodaIntProdottoLimitato(int aMaxProd) : vector<int>() { maxProd = aMaxProd };
 
    void inserisci(int x);
    int preleva();
 
private:
    int maxProd;
 
    // calcola il prodotto degli interi presenti nella coda
    int calcolaProdotto() const;
};
 
void
CodaIntProdottoLimitato::inserisci(
        int x)
{
    if(calcolaProdotto() * x < maxProd)
        vector<int>::insert(vector<int>::end(), x);
}
 
int
CodaIntProdottoLimitato::preleva()
{
    assert(size() > 0);
 
    int x = at(0);
    vector<int>::erase(vector<int>::begin());
    return x;
}
 
int
CodaIntProdottoLimitato::calcolaProdotto() const
{
    int prod = 1;
    for(int i = 0; i < size(); i++)
        prod *= at(i);
    return prod;
}

Si noti che nella funzione che calcola il prodotto degli elementi nella coda la variabile viene inizializzata a 1 e non a 0. Questo perché l’elemento neutro rispetto al prodotto è 1.

Se invece optiamo per la seconda scelta avremo una classe CodaInt, come per esempio la seguente.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class CodaInt : private vector<int>
{
public:
    CodaInt() : vector<int>() {}
 
    void inserisci(int x) { vector<int>::insert(vector<int>::end(), x); }
    int preleva();
 
    int size() const { return vector<int>::size(); }
 
 
 
protected:
    int at(int index) const { return vector<int>::at(index); }
};
 
int
CodaInt::preleva()
{
    assert(size() > 0);
 
    int x = at(0);
    vector<int>::erase(vector<int>::begin());
    return x;
}

La classe CodaInt definisce il metodo at(int), necessario per calcolare il prodotto degli elementi nella coda. Senza questo metodo la classe CodaInt non sarebbe una buona classe base per CodaIntProdottoLimitato. Infatti, non è sufficiente memorizzare il prodotto attuale e aggiornarlo a ogni inserimento/rimozione perché il prodotto è un’operazione invertibile solo in assenza dello 0 (che però è un intero, e quindi potrebbe trovarsi nella coda).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class CodaIntProdottoLimitato : public CodaInt
{
public:
    CodaIntProdottoLimitato(int maxProd) : CodaInt(), maxProd(max) {}
 
    // in una coda dal prodotto limitato il prodotto degli interi
    // non deve eccedere maxProd, quindi bisogna ridefinire il
    // metodo per l'inserimento (per quello di rimozione va bene
    // l'implementazione della classe CodaInt).
    void inserisci(int x);
 
private:
    int maxProd;
 
    // calcola il prodotto degli interi presenti nella coda
    int calcolaProdotto() const;
};
 
void
CodaIntProdottoLimitato::inserisci(
        int x)
{
    if(calcolaProdotto() * x < maxProd)
        CodaInt::inserisci(x);
}
 
int
CodaIntProdottoLimitato::calcolaProdotto() const
{
    int prod = 1;
    for(int i = 0; i < size(); i++)
        prod *= at(i);
    return prod;
}

Segnala ogni incorrettezza o imprecisione lasciando un commento.

2 thoughts on “Scritto Settembre 2008”

  1. Nulla a che fare con questo.
    Vorrei proporre un’esercizio:
    Dato un vettore di caratteri costruire un albero binario di stringhe dove ogni elemento del vettore compare nelle foglie da sinistra verso destra il contenuto informativo del nodo padre sarà la concatenazione del contenuto informativo dei figli cosi da avere nella radice dell’albero la parola completa contenuta nel vettore.

            studiare
             /      
          stud      iare
          /         /  
        st   ud     ia   re
       /   /     /   / 
      s  t u   d  i  a r   e
    

    La dimensione del vettore è n=2^k, k>0.

Leave a Reply

Your email address will not be published. Required fields are marked *