La stazione di monitoraggio 2 - Soluzione

Dovendo stampare i dati in ordine invertito rispetto a quello del file, è necessario caricare in memoria tutti i dati per poi stamparli in senso inverso. Il modo più semplice per farlo è memorizzare un vettore di strutture, nel quale ciascuna struttura memorizza i dati di una riga.

La prima cosa da fare, è quindi di realizzare la struttura che ospiterà i dati delle singole righe. Un esempio è il seguente:

struct misura {
    int aa, MM, gg;             // data
    int hh, mm, ss, ms;         // orario
    char id[MAX_ID_LUNG + 1];   // identificativo del dispositivo
    float temp;                 // temperatura
    int umid;                   // umidità
    float vel;                  // velocità del vento
};

NOTA: il nome dei campi è abbreviato per velocizzare la scrittura del codice. Ciononostante, gli identificatori utilizzati sono sufficientemente esplicativi da permetterne una immediata comprensione.

La formulazione del problema permette di stimare il numero massimo di righe nel file. Questo permette di allocare un vettore staticamente, con una dimensione che permette di ospitare il massimo numero di elementi. Il vettore deve essere dichiarato nel main.

Un esempio di funzione di lettura è il seguente:

void leggi_file(FILE *f, struct misura *elenco, int *n)
{
    char buf[1000];
    int nconv;        // numero di elementi convertiti

    /* inizializza a zero il numero di strutture caricate */
    int i = 0;
    /* ad ogni iterazione legge una nuova riga */
    while (fgets(buf, sizeof(buf), f)) {
        /* punta all'elemento corrente da memorizzare */
        nconv = sscanf(buf, "%d-%d-%d %d:%d:%d.%d %s %f %d%% %f",
                &(elenco[i].aa), &(elenco[i].MM), &(elenco[i].gg),
                &(elenco[i].hh), &(elenco[i].mm), &(elenco[i].ss), &(elenco[i].ms),
                (elenco[i].id), &(elenco[i].temp), &(elenco[i].umid), &(elenco[i].vel));
        /* passa alla riga successiva se non ci sono esattamente 11
         * elementi da convertire */
        if (nconv != 11) continue;
        /* se si arriva qui, la riga è stata letta e memorizzata
         * correttamente; incremento il numero di righe lette */
        i++;
        /* per precauzione controlla che non si leggano più
         * dati di quelli che possono essere ospitati nel vettore;
         * questo non dovrebbe mai succedere se il file è corretto */
        if (i >= MAX_DATI) break;
    }
    *n = i;
}

La funzione è ben commentata, e dovrebbe essere semplice seguirne la logica.

La funzione richiede tre argomenti: il file da leggere f, già aperto in lettura tramite fopen prima di chiamare elabora_file (S8.2.1); il puntatore elenco, che punta al vettore di strutture nel quale verranno memorizzati i dati letti; il puntatore all'intero n che permette alla funzione di restituire il numero di elementi letti tramite passaggio per riferimento.

Nella funzione elabora_file:

  • il file viene letto con un ciclo attraverso fgets (S8.6);
  • da ogni riga, si estraggono i dati utili tramite sscanf, e si memorizzano in variabili locali alla funzione;
    • ricorda che per estrarre una stringa con sscanf si usa lo specificatore %s (S8.6!
    • nella sscanf, la variabile stringa nella quale memorizzare il nome estratto va passato per riferimento (e visto che si tratta di un vettore di char non serve la &)
  • viene chiamata la funzione notturno, la quale restituisce 1 oppure 0 (cioè vero oppure falso) se l'orario indicato dal parametro h corrisponde ad un orario notturno o meno; in tal caso, per semplicità , viene stampata direttamente la stringa caricata da file.
  • l'ultima istruzione if all'interno del ciclo consente di terminare l'elaborazione se per errore dovessero essere letti più dati di quelli memorizzabili nel vettore.

Per quanto riguarda la condizione del ciclo while, ricorda che la fgets restituisce l'indirizzo del puntatore usato per contenere la stringa letta da file (quindi, in ogni caso, un puntatore diverso da NULL). Se non ci sono altre righe da leggere, la fgets restituisce NULL. Dal momento che la costante NULL equivale al numero 0, la condizione del ciclo while diventa 0 (cioè falsa) quando la lettura del file è terminata. E di conseguenza il ciclo while termina a sua volta.

Una variante della funzione di lettura è la seguente, nella quale si sua l'aritmetica dei puntatori per rendere più concisa la scrittura degli argomenti della sscanf:

void leggi_file(FILE *f, struct misura *elenco, int *n)
{
    char buf[1000];
    int nconv;        // numero di elementi convertiti
    struct misura *m;

    /* inizializza a zero il numero di strutture caricate */
    *n = 0;
    /* ad ogni iterazione legge una nuova riga */
    while (fgets(buf, sizeof(buf), f)) {
        /* punta all'elemento corrente da memorizzare */
        m = elenco + (*n);
            nconv = sscanf(buf, "%d-%d-%d %d:%d:%d.%d %s %f %d%% %f",
                &(m->aa), &(m->MM), &(m->gg),
                &(m->hh), &(m->mm), &(m->ss), &(m->ms),
                (m->id), &(m->temp), &(m->umid), &(m->vel));
        /* passa alla riga successiva se non ci sono esattamente 11
         * elementi da convertire */
        if (nconv != 11) continue;
        /* se si arriva qui, la riga è stata letta e memorizzata
         * correttamente; incremento il numero di righe lette */
        (*n)++;
        /* per precauzione controlla che non si leggano più
         * dati di quelli che possono essere ospitati nel vettore;
         * questo non dovrebbe mai succedere se il file è corretto */
        if ((*n) >= MAX_DATI) break;
    }
}

La differenza consiste nell'assegnare alla variabile m, nella riga appena prima dell'istruzione sscanf, l'indirizzo dell'elemento corrente da leggere. L'indirizzo è calcolato sommando all'indirizzo base elenco il valore puntato da n. In modo da indicare i vari campi, nella sscanf, con m->....

Stampa in senso inverso

La funzione stampa_elenco può essere implementata come segue:

void stampa_elenco(struct misura *elenco, int n)
{
    int i;
    if (n <= 6) {
        /* stampa tutte le righe in ordine inverso */
        for (i = n - 1; i >= 0; i--)
            stampa_riga(elenco + i);
    } else {
        /* stampa le prime tre righe */
        for (i = 0; i < 3; i++)
            stampa_riga(elenco + i);
        /* stampa le ultime tre righe in ordine inverso */
        for (i = n - 1; i >= n - 3; i--)
            stampa_riga(elenco + i);
    }
}

Come si vede, un primo test viene svolto per controllare se ci sono meno di 6 righe. In tal caso vengono stampate tutte con il ciclo for.

Altrimenti, con il primo ciclo for nel ramo else vengono stampate le prime tre righe, mentre il secondo ciclo stampa le ultime tre righe.

Temperatura massima

Per il calcolo della temperatura massima può essere implementata una funzione come la seguente:

float max_temp(struct misura *elenco, int n)
{
    float max_temp;
    int i;

    /* esce in caso non vi siano elementi nel vettore */
    if (n <= 0) return -1000;
    /* il massimo viene inizializzato con il primo valore
     * disponibile */
    max_temp = elenco[0].temp;
    /* una iterazione per ogni struttura successiva alla
     * prima, se ce ne sono */
    for (i = 1; i < n; i++) {
        /* se la temperatura corrente è maggiore del
         * massimo attuale, aggiorna il massimo */
        if ((elenco + i)->temp > max_temp)
            max_temp = (elenco + i)->temp;
    }
    return max_temp;
}

Si faccia riferimento ai commenti nel codice per comprenderne il significato.