Salta al contenuto
Note per Studenti Esercizio 24 · C, lanterne estive (lista concatenata)

Esercizio 24C, lanterne estive (lista concatenata)

In questa pagina 5

Testo (scritto del 09/09/2025, parte 3, in C). La Festa delle Lanterne Estive. 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 di Linux da terminale con man.

Una famiglia ha decorato il giardino con lanterne luminose appese lungo un sentiero. Ogni lanterna può essere blu, verde o rossa. La disposizione è rappresentata da una lista concatenata: ogni nodo contiene color, il colore della lanterna (di tipo intero). Il colore è rappresentato con tre bit, ciascuno per un colore: 001 blu, 010 verde, 100 rosso.

Si deve verificare che il numero di lanterne di ciascun colore sia lo stesso. Se non lo è, il programma cambia il colore di tutte le lanterne in questo modo: se il numero di lanterne verdi è dispari le colora tutte di rosso; altrimenti (considera pari lo zero) ripete il colore delle prime tre lanterne per tutte le altre (ad esempio con prime tre lanterne blu, verde, rossa: la quarta sarà blu, la quinta verde, la sesta rossa, e così via).

La lista è fornita già implementata: typedef struct node { int color; struct node *next; } Node;. Funzioni da implementare:

  1. void countColors(Node *node, int *colors) conta le lanterne di ciascun colore: colors è un array di 33 interi e la funzione lo aggiorna (blu in colors[0], verde in colors[1], rosso in colors[2]); per ogni nodo, se color & 001 incrementa colors[0], se color & 010 incrementa colors[1], se color & 100 incrementa colors[2]; poi richiama se stessa sul nodo successivo;
  2. int checkLanternColors(Node *head) restituisce TRUE (non zero) se i tre conteggi sono uguali, FALSE altrimenti; in questo caso applica le regole sopra (se colors[1] è pari: legge i colori dei primi tre nodi in un array pattern di 33 interi inizializzato a 00 e chiama applyPattern(head, pattern); altrimenti chiama uniformColor(head, 100));
  3. void uniformColor(Node *node, int color) imposta color su ogni nodo collegato a node (ricorsiva);
  4. void applyPattern(Node *node, int *pattern) imposta il colore di ogni nodo in base al pattern, con indice che scorre 0,1,2,0,1,2,…0, 1, 2, 0, 1, 2, \dots (index <- (index + 1) % 3).

Lo pseudocodice non è codice C completo: non considera i tipi, l'uso di puntatori e quindi l'operatore -> al posto di .. Si possono creare struct e funzioni ausiliarie.


Richiami

Liste concatenate in C, ricorsione sulle liste (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 operatori bit a bit (&) su interi (vedi 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 → per puntatori e array).

Soluzione

c
#define BLU    1   /* 001 */
#define VERDE  2   /* 010 */
#define ROSSO  4   /* 100 */

typedef struct node {
    int color;
    struct node *next;
} Node;

void countColors(Node *node, int *colors) {
    if (node == NULL)
        return;
    if (node->color & BLU)   colors[0]++;
    if (node->color & VERDE) colors[1]++;
    if (node->color & ROSSO) colors[2]++;
    countColors(node->next, colors);
}

void uniformColor(Node *node, int color) {
    if (node == NULL)
        return;
    node->color = color;
    uniformColor(node->next, color);
}

void applyPattern(Node *node, int *pattern) {
    int index = 0;
    for (Node *cur = node; cur != NULL; cur = cur->next) {
        cur->color = pattern[index];
        index = (index + 1) % 3;
    }
}

int checkLanternColors(Node *head) {
    int colors[3] = {0, 0, 0};
    countColors(head, colors);
    if (colors[0] == colors[1] && colors[1] == colors[2])
        return 1;
    if (colors[1] % 2 == 0) {                 /* verdi pari (anche zero) */
        int pattern[3] = {0, 0, 0};
        Node *cur = head;
        for (int i = 0; i < 3 && cur != NULL; i++) {
            pattern[i] = cur->color;
            cur = cur->next;
        }
        applyPattern(head, pattern);
    } else {
        uniformColor(head, ROSSO);
    }
    return 0;
}

Punti da notare

  • 100 nel testo è binario. In C il letterale 100 è il numero decimale cento. Il rosso è 100 in binario, cioè 44: va scritto 4, 1 << 2 o con una costante (ROSSO). Scrivere uniformColor(head, 100) imposterebbe il valore decimale cento (binario 1100100): nei conteggi apparirebbe come rosso (il bit del rosso è acceso), ma non è il valore 44 atteso e un confronto del tipo color == ROSSO fallirebbe.
  • & e non &&: node->color & VERDE isola il bit; un nodo con colore 33 (011) conta sia come blu sia come verde, perché la rappresentazione a bit permette combinazioni.
  • Il pattern si legge prima di modificare la lista: applyPattern riscrive anche i primi tre nodi, quindi i loro colori vanno copiati in pattern prima di chiamarla.
  • Liste con meno di 33 nodi: pattern è inizializzato a 00 e il ciclo di lettura si ferma a NULL, quindi nessun accesso fuori dalla lista; applyPattern usa solo pattern[0..1].
  • Lista vuota: i conteggi sono tutti 00, quindi uguali: restituisce vero senza modificare nulla.
  • Complessità: ogni funzione scorre la lista una volta: Θ(n)\Theta(n) tempo, Θ(n)\Theta(n) stack per le due ricorsive (profondità nn).

Prove eseguite

Compilato con gcc -std=c99 -Wall -Wextra e provato su liste costruite a mano:

Lista (valori di color) Risultato Lista dopo
1,2,41, 2, 4 vero invariata
1,1,2,4,41, 1, 2, 4, 4 (verdi: 11, dispari) falso 4,4,4,4,44, 4, 4, 4, 4
1,2,2,4,4,41, 2, 2, 4, 4, 4 (verdi: 22, pari) falso 1,2,2,1,2,21, 2, 2, 1, 2, 2
lista vuota vero —
1,11, 1 falso 1,11, 1
3,4,7,13, 4, 7, 1 (blu 33, verdi 22, rossi 22) falso 3,4,7,33, 4, 7, 3

Errori comuni

  • uniformColor(head, 100) con 100 decimale, invece di 4.
  • Usare && o == al posto di & nel conteggio: color == VERDE non vede i colori combinati.
  • Leggere il pattern dopo aver cominciato a riscrivere i nodi.
  • Dimenticare il caso della lista con meno di 33 nodi (deferenziare NULL).
  • Prototipi modificati (il testo lo vieta).

Versione ripasso

Testo. Lista concatenata di lanterne (color: bit 001 blu, 010 verde, 100 rosso). Se i tre conteggi non sono uguali: verdi dispari ⇒ tutte rosse; altrimenti si ripetono i colori delle prime tre. Implementare in C countColors(Node*, int*), checkLanternColors(Node*), uniformColor(Node*, int), applyPattern(Node*, int*).

  • Costanti (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 →): BLU = 1, VERDE = 2, ROSSO = 4; il 100 binario del testo è 4, non cento.
  • countColors: per nodo if (color & BLU) colors[0]++ ecc., poi ricorsione su next.
  • checkLanternColors: conta; tre conteggi uguali ⇒ vero; colors[1] % 2 == 0 ⇒ copia i primi ≤3\le 3 colori in pattern (inizializzato a 00) e applyPattern; altrimenti uniformColor(head, ROSSO); restituisce falso.
  • applyPattern: index = (index + 1) % 3 scorrendo la lista; uniformColor: ricorsiva.
  • Prove: [1,2,4][1,2,4] vero; [1,1,2,4,4]→[4,4,4,4,4][1,1,2,4,4] \to [4,4,4,4,4]; [1,2,2,4,4,4]→[1,2,2,1,2,2][1,2,2,4,4,4] \to [1,2,2,1,2,2]; [3,4,7,1]→[3,4,7,3][3,4,7,1] \to [3,4,7,3]. Θ(n)\Theta(n).
  • Codice: if (node->color & BLU) colors[0]++; (ripetuto per VERDE, ROSSO); uniformColor: node->color = color; uniformColor(node->next, color);; applyPattern: cur->color = pattern[index]; index = (index + 1) % 3;.
  • Controllo finale: [1,2,4][1,2,4] vero; [1,1,2,4,4][1,1,2,4,4]: verdi 11 ⇒ tutte rosse; [1,2,2,4,4,4][1,2,2,4,4,4]: verdi 22 ⇒ [1,2,2,1,2,2][1,2,2,1,2,2].
  • Prove con colori combinati: [3,4,7,1][3,4,7,1] ha blu 33, verdi 22, rossi 22 ⇒ verdi pari ⇒ pattern [3,4,7][3,4,7] ⇒ [3,4,7,3][3,4,7,3]; lista vuota ⇒ vero senza modifiche; lista di due nodi ⇒ pattern[2] = 0 non usato.
  • Errori: 100 decimale; &&/== invece di &; pattern letto dopo la riscrittura; meno di 33 nodi.

Teoria collegata