Salta al contenuto
Note per Studenti Esercizio 29 · C, centralina meteo (lista ordinata)

Esercizio 29C, centralina meteo (lista ordinata)

Esame
In questa pagina 4

Testo (secondo appello 2025-26, luglio 2026, parte 3, in C). Centralina meteo del laboratorio. Svolgi il seguente esercizio sviluppando le funzioni richieste in C. I prototipi delle funzioni non possono essere modificati. Durante l'esame non è possibile consultare appunti o materiale didattico; è consentito utilizzare la documentazione Linux con man.

Nel laboratorio di elettronica è stata installata una piccola centralina che registra una misura di temperatura ogni volta che uno studente avvia un test. Le misure arrivano in ordine non garantito, ma il software deve conservarle in una lista ordinata per minuto di acquisizione. Ogni misura è rappresentata da:

c
typedef struct reading { int minute; double temperature; } Reading;
typedef struct node { Reading data; struct node *next; } Node;

Il campo minute indica il minuto dall'inizio dell'esperimento; temperature la temperatura in gradi Celsius. Funzioni da completare:

  1. Node *insertSorted(Node *log, Reading reading): inserisce una nuova rilevazione nella lista, mantenendo i nodi ordinati in modo crescente rispetto a minute. Deve allocare dinamicamente un nuovo nodo e restituire la nuova testa della lista.
  2. int removeOutOfRange(Node **log, double minTemperature, double maxTemperature): rimuove dalla lista tutte le rilevazioni con temperatura minore di minTemperature o maggiore di maxTemperature. Deve aggiornare la testa quando vengono rimossi i primi nodi, liberare la memoria dei nodi eliminati e restituire il numero di rilevazioni rimosse.
  3. double averageTemperatures(Node *log): calcola la media aritmetica delle temperature presenti nel registro. Se la lista è vuota restituisce 0,00{,}0.
  4. int countReadingsAboveThreshold(Node *log, double threshold): conta quante rilevazioni hanno temperatura strettamente maggiore di threshold. Se la lista è vuota restituisce 00.

Indicazioni: la lista è semplicemente concatenata; si possono definire funzioni ausiliarie; non modificare main() e le funzioni di test. Esempio: inserendo le misure (30,22,0)(30, 22{,}0), (10,20,5)(10, 20{,}5) e (20,21,0)(20, 21{,}0) la lista finale deve avere i minuti nell'ordine 10,20,3010, 20, 30. Se in una lista sono presenti temperature 18,018{,}0, 41,041{,}0, 22,022{,}0 e −3,0-3{,}0, con soglie 0,00{,}0 e 35,035{,}0 devono essere rimossi i nodi con temperature 41,041{,}0 e −3,0-3{,}0.


Richiami

Liste concatenate e puntatore a puntatore (vedi Liste concatenate in CLista singolarmente concatenata in C con nodo struct e testa passata per riferimento (Nodo **); addHead, addTail ricorsiva e iterativa, pop, inversione in loco, liberazione; coda con puntatori a testa e coda; ricorsione sulle liste; costi.Liste concatenate in C → e Puntatori, struct e memoria dinamica in CPuntatori e passaggio per riferimento in C, array e aritmetica dei puntatori, stringhe, struct e typedef con l'operatore ->, malloc e free, puntatore a puntatore per modificare una testa; compilazione con Makefile; errori tipici (puntatori pendenti, perdite di memoria, off-by-one).Puntatori, struct e memoria dinamica in C →). Si usa il modello "puntatore al puntatore da modificare" (Node **p), che tratta uniformemente testa e nodi intermedi.

Soluzione

c
Node *insertSorted(Node *log, Reading reading) {
    Node *n = malloc(sizeof(Node));
    n->data = reading;
    Node **p = &log;                                  /* p: indirizzo del puntatore da modificare */
    while (*p != NULL && (*p)->data.minute <= reading.minute)
        p = &(*p)->next;                              /* si ferma al primo minuto maggiore */
    n->next = *p;
    *p = n;
    return log;
}

int removeOutOfRange(Node **log, double minTemperature, double maxTemperature) {
    int removed = 0;
    Node **p = log;
    while (*p != NULL) {
        double t = (*p)->data.temperature;
        if (t < minTemperature || t > maxTemperature) {
            Node *dead = *p;
            *p = dead->next;                          /* scollega (anche la testa) */
            free(dead);
            removed++;
        } else
            p = &(*p)->next;
    }
    return removed;
}

double averageTemperatures(Node *log) {
    double sum = 0.0;
    int n = 0;
    for (; log != NULL; log = log->next) {
        sum += log->data.temperature;
        n++;
    }
    return n == 0 ? 0.0 : sum / n;
}

int countReadingsAboveThreshold(Node *log, double threshold) {
    int c = 0;
    for (; log != NULL; log = log->next)
        if (log->data.temperature > threshold)
            c++;
    return c;
}

Idea del puntatore a puntatore. p contiene l'indirizzo del puntatore che punta al nodo corrente: all'inizio è &log (la testa), poi diventa &(*p)->next (il campo next del nodo precedente). Inserire o togliere significa scrivere in *p, e funziona allo stesso modo per la testa e per un nodo qualsiasi: non servono casi speciali (lista vuota, inserimento in testa, rimozione della testa).

Dettagli.

  • insertSorted usa <= nel confronto: una misura con un minuto già presente viene messa dopo quelle con lo stesso minuto (inserimento stabile). Con < andrebbe prima: il testo non lo specifica, vanno bene entrambe purché la lista resti ordinata in modo non decrescente.
  • Nella rimozione si legge dead->next prima di free(dead); dopo la free il nodo non si deve più usare. Dopo una rimozione p non avanza (il nodo successivo è ora in *p); avanza solo se il nodo resta.
  • averageTemperatures divide per n solo se n>0n > 0; la somma è double, quindi nessuna divisione intera.
  • Complessità: insertSorted O(n)O(n) (scorre fino al punto di inserimento, O(1)O(1) se va in testa); removeOutOfRange, averageTemperatures, countReadingsAboveThreshold Θ(n)\Theta(n).

Prove eseguite

  • Inserimenti (30,22,0)(30, 22{,}0), (10,20,5)(10, 20{,}5), (20,21,0)(20, 21{,}0) e (20,25,0)(20, 25{,}0): lista (10, 20.5) (20, 21.0) (20, 25.0) (30, 22.0) (minuti ordinati; il secondo 2020 dopo il primo).
  • Temperature 18,018{,}0, 41,041{,}0, 22,022{,}0, −3,0-3{,}0 ai minuti 0,1,2,30, 1, 2, 3, soglie 0,00{,}0 e 35,035{,}0: rimossi 22 nodi, restano (0,18,0)(0, 18{,}0) e (2,22,0)(2, 22{,}0); media 20,0020{,}00; sopra 19,019{,}0: 11.
  • Con soglie 19,019{,}0 e 21,021{,}0 su quella lista: rimossi 22 (testa e coda), lista vuota; media 0,00{,}0 e conteggio 00 ✓.

Errori comuni

  • Passare Node *log a removeOutOfRange (il prototipo ha Node **): se si rimuove la testa il chiamante non lo vede.
  • In insertSorted non gestire l'inserimento in testa o in lista vuota: con il puntatore a puntatore il caso è automatico.
  • Fare free(dead) e poi leggere dead->next.
  • Avanzare p dopo una rimozione (si salta un nodo).
  • Dividere per 00 nella media con lista vuota (serve il controllo n>0n > 0).
  • Dimenticare > stretto in countReadingsAboveThreshold (la soglia stessa non conta).

Versione ripasso

Testo. Lista di Reading { int minute; double temperature; } ordinata per minuto; implementare insertSorted (restituisce la nuova testa), removeOutOfRange(Node **, min, max) (libera i nodi, restituisce quanti), averageTemperatures (0,00{,}0 se vuota), countReadingsAboveThreshold (stretto).

  • Modello Node **p (vedi Liste concatenate in CLista singolarmente concatenata in C con nodo struct e testa passata per riferimento (Nodo **); addHead, addTail ricorsiva e iterativa, pop, inversione in loco, liberazione; coda con puntatori a testa e coda; ricorsione sulle liste; costi.Liste concatenate in C →): p = &log o &(*p)->next; scrivere in *p vale per testa e nodi intermedi.
  • insertSorted: while (*p && (*p)->data.minute <= reading.minute) p = &(*p)->next; n->next = *p; *p = n;.
  • removeOutOfRange: se fuori soglia: dead = *p; *p = dead->next; free(dead); removed++ (senza avanzare), altrimenti avanza.
  • Media e conteggio: scansione; media 0.0 se n=0n = 0; > stretto.
  • Costi: tutte Θ(n)\Theta(n) (inserimento O(n)O(n)).
  • Prove: minuti 10,20,3010, 20, 30; [18,41,22,−3][18, 41, 22, -3] con soglie 00 e 3535 ⇒ 22 rimossi, media 20,0020{,}00, sopra 1919: 11.
  • Codice: insertSorted: Node **p = &log; while (*p && (*p)->data.minute <= reading.minute) p = &(*p)->next; n->next = *p; *p = n; return log;; removeOutOfRange: fuori soglia ⇒ dead = *p; *p = dead->next; free(dead); removed++; altrimenti p = &(*p)->next.
  • Media: ∑t/n\sum t / n con n>0n > 0, altrimenti 0,00{,}0; conteggio con > stretto. Prove: minuti 10,20,3010, 20, 30; [18,41,22,−3][18, 41, 22, -3] con soglie 00 e 3535 ⇒ 22 rimossi, media 20,0020{,}00.
  • Modello Node **p: p = indirizzo del puntatore da modificare (&log, poi &(*p)->next): inserimento e rimozione valgono per testa e nodi intermedi senza casi speciali. Stesso minuto: con <= la nuova misura va dopo.
  • Errori: Node * al posto di Node **; free prima di leggere next; avanzare dopo una rimozione; divisione per 00.

Teoria collegata