Stazione di monitoraggio 3 - Soluzione

La prima cosa da fare è 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 parte più interessante di questo problema è la funzione che si occupa della lettura del file. Un esempio di funzione di lettura è la seguente:

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

    /* inizializza a zero il numero di strutture caricate */
    *n = 0;

    /* legge e converte i limiti presenti ad inizio file */
    fgets(buf, sizeof(buf), f);
    sscanf(buf, "%d", &a);
    fgets(buf, sizeof(buf), f);
    sscanf(buf, "%d", &b);
    /* verifica che i limiti siano validi */
    if ((b < a) || (a < 0) || (b < 0)) return NULL;

    /* calcola e alloca la dimensione massima dell'array */
    size = (b - a + 1);
    elenco = malloc(size * sizeof(*elenco));

    /* legge e scarta le prime "a" righe */
    for (i = 0; i < a; i++)
        fgets(buf, sizeof(buf), f);

    /* ad ogni iterazione, legge una riga fino alla fine del file */
    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) >= size) break;
    }
    /* riduce la dimensione del vettore se sono stati letti meno
     * di "size" righe */
    if ((*n < size))
        elenco = realloc(elenco, (*n) * sizeof(*elenco));
    /* restituisce il vettore con i dati letti */
    return elenco;
}

Non c'è da spaventarsi della lunghezza, in quanto buona parte del codice è costituito da commenti. I commenti dovrebbero essere sufficienti per capire la logica del programma.

La funzione restituisce un puntatore al vettore allocato all'interno della funzione.

I parametri sono due:

  • il file f, già aperto in lettura prima di chiamare la leggi_file;
  • il puntatore all'intero n che serve a restituire il numero di righe lette tramite il passaggio di parametri per indirizzo.

La parte più significativa e istruttiva è l'uso di fgets e sscanf come istruzioni singole, all'interno di un ciclo for, e nel classico ciclo while.

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.

Si ricordi che devono essere lette dal file le righe nell'intervallo [a,b] a partire dalla prima riga contenente le misurazioni, e che le tali righe sono numerate a partire da 0.

Le istruzioni singole vengono usate per leggere le prime due righe del file, e assegnare il valore alle variabili a e b. Dopodiché si verifica che i valori siano coerenti. In caso non lo siano, la funzione termina restituendo NULL.

Alla variabile size viene assegnata la dimensione del vettore, calcolata come b - a + 1, in quanto gli estremi dell'intervallo [a,b] sono inclusi. In altri termini, se l'intervallo è - ad esempio - [3,3], il vettore deve contenere un singolo valore. La malloc viene poi utilizzata per l'allocazione del vettore.

Il primo ciclo for si "mangia" (ovvero salta) le prime a righe: le legge con la fgets ma non viene fatto nulla con il contenuto delle righe.

il ciclo while, invece, legge una riga per ogni iterazione, effettua la conversione del contenuto dei valori presenti nella riga, e li memorizza nell'elemento del vettore di indice *n.

Alla fine del ciclo while viene ridimensionata la lunghezza del vettore tramite la realloc, in modo che il vettore sia della esatta dimensione pari al numero di righe lette dal file.

Stampa invertita degli identificativi

La funzione che stampa gli identificativi in senso inverso è molto semplice e, ad esempio, può essere implementata come segue:

void stampa_identificativi(struct misura *elenco, int n)
{
    int i;

    for (i = n - 1; i >= 0; i--)
        printf("%s\n", (elenco + i)->id);
}

Essa riceve come argomenti il vettore delle strutture caricate da file e il numero di elementi nel vettore.

Effettua un ciclo for con contatore che parte dall'ultimo elemento del vettore (indice n - 1) e decrementa il contatore fino ad indirizzare l'elemento di indice 0 del vettore.

Hands-on

prova-tu Si consideri una variante del problema proposto. Il file di input contenga, prima delle righe con le misurazioni, un solo numero, che indica il numero totale di misurazioni presenti nel file. Un esempio di file con questo formato è il seguente:

3
2019-03-03 23:59:10.120 ID000 17.5 20% 0.4
2019-03-02 07:41:59.001 ID001 16.9 22% 0.2
2019-03-01 12:10:00.000 ID002 22.5 21% 0.3

Modifica la soluzione realizzata per questo tutorial in modo da leggere tutte le righe e stampare gli identificativi in ordine invertito. Utilizza lo stesso formato adottato in questo tutorial.

Puoi utilizzare pvcheck per testare il corretto funzionamento del programma. Il file contenente i test è misure-handson.test.

Il comando da eseguire per il test è il seguente:

pvcheck -f misure-handson.test ./a.out