Esercizio 18rubrica ordinata in C
In questa pagina 4
Testo (prova di programmazione di "appello simulato" di Fondamenti di Informatica, Ingegneria dell'Informazione UniPD, anno accademico non indicato nel testo; adattato da un tema d'esame in Java: il tipo di esercizio, un dizionario su array ordinato, è tipico anche di una prova in C, e qui è riscritto in C).
Si gestisce una rubrica telefonica: un dizionario di coppie nome numero in cui il nome è la chiave (nessun omonimo) e il numero è un intero grande (qui long long). Scrivere:
- il tipo
Rubricacon le operazioni di inserimento (se il nome c'è già si sostituisce il numero), ricerca, rimozione e stampa; la ricerca deve costare ; la stampa scrive una coppia per riga nel formatonome : numero; - un programma di prova che riceve due nomi di file,
file1efile2, dalla riga di comando; carica una prima rubrica dal contenuto difile1(stesso formato della stampa); poi legge dallo standard input nomi, uno per riga: se il nome è nella prima rubrica, la coppia viene spostata (rimossa dalla prima e inserita in una seconda rubrica); il ciclo termina con il carattereQ; infine scrive la seconda rubrica infile2.
Esempio di file1 (paperopoli.txt):
Pippo : 3424987574
Topolino : 3874628761
Paperino : 4866286343
Ciccio : 4992348384
Minnie : 3762476513
Gastone : 872348761
Rockerduck : 4802382434
Gambadilegno : 3759483281
Paperina : 4832340983e dello standard input (input.txt): Pippo, Topolino, Bassotti, Gambadilegno, Minnie, Q (una riga ciascuno). Lanciato come ./rubrica paperopoli.txt topolinia.txt < input.txt, il programma scrive in topolinia.txt le quattro coppie trovate, in ordine alfabetico; Bassotti non c'è: si segnala su standard error.
Teoria: Ordinare e cercare in C - qsort, bsearch e puntatori a funzionePuntatori a funzione e funzioni di confronto; qsort per ordinare array di interi, stringhe e struct; bsearch per la ricerca binaria; dizionario ordinato su array dinamico di struct con inserimento (memmove e realloc) e ricerca in O(log n).Ordinare e cercare in C - qsort, bsearch e puntatori a funzione →, Strutture (struct) in Cstruct per raggruppare campi di tipo diverso, typedef, accesso con punto e freccia, struct come parametri e valori di ritorno, array di struct, struct allocate dinamicamente e struct autoreferenziali.Strutture (struct) in C →, Gestione della memoria in CSegmenti di memoria di un processo (codice, dati statici, stack, heap); durata delle variabili; allocazione dinamica con malloc, calloc, realloc e free; errori classici: memory leak, dangling pointer, double free, buffer overflow; strumenti di controllo.Gestione della memoria in C →, File di record e controllo degli errori riga per rigaSchema per leggere un file (o lo standard input) di record, uno per riga: formati con separatore, controllo di ogni riga, messaggi di errore con il numero di riga su standard error, righe da saltare, fine dell'input su riga vuota; versione in Python con split, int, float e eccezioni; versione in C con fgets, sscanf e strtol.File di record e controllo degli errori riga per riga →.
Progetto
- Voce =
structcon il nome (array dichardi lunghezza fissa, così la copia è semplice) e il numero inlong long: nel testo originale il numero è unlongJava (64 bit); in Clongha 32 bit su Windows e 64 su Linux, e numeri come4992348384superano , quindi servelong long(con%lldestrtoll). - Rubrica = array dinamico di
Voceordinato per nome, connvoci e capacitàcapche si raddoppia conrealloc. L'ordine rende possibilebsearch() e la stampa ordinata. - Inserimento : se il nome c'è già (trovato con
bsearch) si sostituisce il numero; altrimenti si cerca il posto, si fa spazio conmemmove(le zone si sovrappongono:memcpysarebbe scorretto) e si copia la voce. - Rimozione :
bsearch, copia della voce in un parametro di uscita,memmoveper chiudere il buco. - Caricamento: righe
nome : numero; per ogni rigastrstr(riga, " : ")trova il separatore,strtollconverte il numero controllandofine; le righe errate sono segnalate sustderrcon il numero di riga e saltate. main: due rubriche,fgetsper i nomi (togliendo il'\n'constrcspn),strcmp(nome, "Q")per terminare; ogni nome trovato viene spostato conrubrica_rimuoviseguito darubrica_inseriscinella seconda; la seconda si scrive sufile2; si liberano entrambe.
Codice
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LUNG_NOME 32
typedef struct {
char nome[LUNG_NOME];
long long num; /* long long: su Windows long ha solo 32 bit */
} Voce;
typedef struct {
Voce *v; /* array ordinato per nome */
size_t n, cap;
} Rubrica;
static int cmp_nome(const void *a, const void *b) {
return strcmp(((const Voce *)a)->nome, ((const Voce *)b)->nome);
}
void rubrica_init(Rubrica *r) { r->v = NULL; r->n = r->cap = 0; }
void rubrica_libera(Rubrica *r) { free(r->v); rubrica_init(r); }
/* O(log n): puntatore alla voce o NULL */
Voce *rubrica_cerca(const Rubrica *r, const char *nome) {
Voce chiave;
snprintf(chiave.nome, sizeof chiave.nome, "%s", nome);
return bsearch(&chiave, r->v, r->n, sizeof(Voce), cmp_nome);
}
/* O(n) per gli spostamenti; restituisce 0 se ok, -1 se la memoria manca */
int rubrica_inserisci(Rubrica *r, const char *nome, long long num) {
Voce *p = rubrica_cerca(r, nome);
if (p != NULL) { p->num = num; return 0; } /* chiave unica: sostituisce */
if (r->n == r->cap) {
size_t nuova = r->cap ? 2 * r->cap : 8;
Voce *t = realloc(r->v, nuova * sizeof(Voce));
if (t == NULL) return -1;
r->v = t; r->cap = nuova;
}
size_t i = 0;
while (i < r->n && strcmp(r->v[i].nome, nome) < 0) i++; /* primo elemento >= nome */
memmove(&r->v[i + 1], &r->v[i], (r->n - i) * sizeof(Voce));
snprintf(r->v[i].nome, sizeof r->v[i].nome, "%s", nome);
r->v[i].num = num;
r->n++;
return 0;
}
/* toglie la voce e la copia in *uscita; 0 se rimossa, -1 se assente */
int rubrica_rimuovi(Rubrica *r, const char *nome, Voce *uscita) {
Voce *p = rubrica_cerca(r, nome);
if (p == NULL) return -1;
*uscita = *p;
size_t i = (size_t)(p - r->v);
memmove(&r->v[i], &r->v[i + 1], (r->n - i - 1) * sizeof(Voce));
r->n--;
return 0;
}
/* una voce per riga, nel formato "nome : numero" */
void rubrica_stampa(const Rubrica *r, FILE *f) {
for (size_t i = 0; i < r->n; i++)
fprintf(f, "%s : %lld\n", r->v[i].nome, r->v[i].num);
}
/* legge un file "nome : numero"; segnala su stderr le righe errate; restituisce le voci lette */
int rubrica_carica(Rubrica *r, FILE *f) {
char riga[128];
int n = 0, nriga = 0;
while (fgets(riga, sizeof riga, f) != NULL) {
nriga++;
riga[strcspn(riga, "\n")] = '\0';
if (riga[0] == '\0') continue;
char *sep = strstr(riga, " : ");
if (sep == NULL || sep == riga || sep - riga >= LUNG_NOME) {
fprintf(stderr, "riga %d: formato errato\n", nriga);
continue;
}
*sep = '\0';
char *fine;
long long num = strtoll(sep + 3, &fine, 10);
if (fine == sep + 3 || *fine != '\0') {
fprintf(stderr, "riga %d: numero non valido\n", nriga);
continue;
}
if (rubrica_inserisci(r, riga, num) == 0) n++;
}
return n;
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "uso: %s file1 file2 < nomi\n", argv[0]);
return 1;
}
FILE *f1 = fopen(argv[1], "r");
if (f1 == NULL) { perror(argv[1]); return 1; }
Rubrica r1, r2;
rubrica_init(&r1); rubrica_init(&r2);
rubrica_carica(&r1, f1);
fclose(f1);
char nome[128];
while (fgets(nome, sizeof nome, stdin) != NULL) {
nome[strcspn(nome, "\n")] = '\0';
if (strcmp(nome, "Q") == 0) break; /* Q: fine */
Voce v;
if (rubrica_rimuovi(&r1, nome, &v) == 0)
rubrica_inserisci(&r2, v.nome, v.num); /* spostata da r1 a r2 */
else
fprintf(stderr, "%s: non trovato\n", nome);
}
FILE *f2 = fopen(argv[2], "w");
if (f2 == NULL) { perror(argv[2]); return 1; }
rubrica_stampa(&r2, f2);
fclose(f2);
rubrica_libera(&r1); rubrica_libera(&r2);
return 0;
}Alcuni punti da notare:
snprintf(dest, sizeof dest, "%s", s)copia troncando se serve e termina sempre con'\0': più sicuro distrcpy, e diverso dastrncpy, che non aggiunge'\0'se la sorgente è lunga.bsearchrestituisce un puntatore dentro l'array: dopo unrealloco unmemmovequel puntatore non è più valido. Inrubrica_rimuovilo si usa solo prima di modificare l'array, e l'indice si ricava conp - r->v.- In
rubrica_inseriscila ricerca del posto è lineare per semplicità: l'inserimento è comunque per lo spostamento, quindi non cambia l'ordine di grandezza; con una ricerca binaria del punto d'inserimento si risparmiano solo i confronti.
Prova ed esito
Compilato con cc -Wall -Wextra senza avvisi e provato così:
$ ./rubrica paperopoli.txt topolinia.txt < input.txt
Bassotti: non trovato (su standard error)
$ cat topolinia.txt
Gambadilegno : 3759483281
Minnie : 3762476513
Pippo : 3424987574
Topolino : 3874628761Altre prove fatte: un file con righe vuote, senza separatore, con un numero non valido e con un nome ripetuto (Ada : 1 poi Ada : 9: resta Ada : 9; le righe errate sono segnalate con il loro numero e saltate); uno stress con 2000 righe casuali (nomi ripetuti) e tutti i nomi in ordine casuale in ingresso: il file di uscita coincide con la rubrica attesa, ordinata e senza duplicati.
Errori comuni
- Usare
longper i numeri di telefono: su Windows ha 32 bit e4992348384va in overflow;long longcon%lld. memcpyal posto dimemmoveper fare spazio o chiudere un buco.bsearchsu un array che non è ordinato con lo stesso confronto.- Usare un puntatore ottenuto da
bsearchdopo aver modificato l'array. - Non togliere il
'\n'letto dafgets:"Pippo\n"non coincide con"Pippo". v = realloc(v, ...)senza usare un puntatore temporaneo: sereallocfallisce si perde il blocco.- Non liberare le rubriche: memory leak.
Versione ripasso
Prova di "appello simulato" di Fondamenti di Informatica (UniPD, in Java; adattato a C). Teoria: Ordinare e cercare in C - qsort, bsearch e puntatori a funzionePuntatori a funzione e funzioni di confronto; qsort per ordinare array di interi, stringhe e struct; bsearch per la ricerca binaria; dizionario ordinato su array dinamico di struct con inserimento (memmove e realloc) e ricerca in O(log n).Ordinare e cercare in C - qsort, bsearch e puntatori a funzione →, Strutture (struct) in Cstruct per raggruppare campi di tipo diverso, typedef, accesso con punto e freccia, struct come parametri e valori di ritorno, array di struct, struct allocate dinamicamente e struct autoreferenziali.Strutture (struct) in C →.
Dati. Voce { char nome[32]; long long num; } (long long: long è a 32 bit su Windows, 4992348384 va in overflow); Rubrica { Voce *v; size_t n, cap; } array ordinato per nome, capacità raddoppiata con realloc (puntatore temporaneo).
Operazioni.
cerca:bsearchconcmp_nome, .inserisci: nome presente → sostituisce il numero; altrimentimemmoveper fare posto (zone sovrapposte, nonmemcpy), .rimuovi:bsearch, copia in*uscita,memmoveper chiudere il buco.stampa:nome : numeroper riga.carica:fgets,strstr(riga, " : "),strtollcon controllo difine; righe errate sustderre saltate.
Programma. Argomenti file1 file2; nomi da stdin (fgets + strcspn per il '\n', Q termina); nome trovato → rimuovi da r1 e inserisci in r2; scrittura di r2 in file2; rubrica_libera per entrambe. Con paperopoli.txt e input.txt escono Gambadilegno, Minnie, Pippo, Topolino in ordine; Bassotti segnalato su stderr.
Errori comuni: long per i numeri; memcpy al posto di memmove; bsearch su array non ordinato; puntatore di bsearch usato dopo aver modificato l'array; '\n' non tolto; realloc senza temporaneo; memory leak.