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 dichar
non serve la&
)
- ricorda che per estrarre una stringa con
- viene chiamata la funzione
notturno
, la quale restituisce1
oppure0
(cioè vero oppure falso) se l'orario indicato dal parametroh
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 lafgets
restituisce l'indirizzo del puntatore usato per contenere la stringa letta da file (quindi, in ogni caso, un puntatore diverso daNULL
). Se non ci sono altre righe da leggere, lafgets
restituisceNULL
. Dal momento che la costanteNULL
equivale al numero0
, la condizione del ciclowhile
diventa0
(cioè falsa) quando la lettura del file è terminata. E di conseguenza il ciclowhile
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.