Ordinare e cercare in C - qsort, bsearch e puntatori a funzione
In questa pagina 5
In questa pagina 4
In Python si scrive sorted(v, key=...) e bisect. In C la libreria standard (<stdlib.h>) offre qsort e bsearch, che lavorano su qualsiasi tipo di array e ricevono dal chiamante una funzione di confronto: serve quindi il puntatore a funzione (Puntatori in CUn puntatore contiene un indirizzo di memoria; operatori & e *, NULL, puntatori come parametri per modificare variabili del chiamante, aritmetica dei puntatori, legame tra array e puntatori, const, puntatori a puntatori.Puntatori in C →, Funzioni in CDefinizione e prototipo di una funzione C, tipo di ritorno e void, passaggio dei parametri sempre per valore, variabili locali, globali e static, file header e compilazione separata, funzioni ricorsive.Funzioni in C →).
Puntatori a funzione
Il nome di una funzione, senza parentesi, è il suo indirizzo. Un puntatore a funzione si dichiara indicando firma e nome tra parentesi tonde con *:
#include <stdio.h>
int somma(int a, int b) { return a + b; }
int prodotto(int a, int b) { return a * b; }
int applica(int (*f)(int, int), int x, int y) { /* f: puntatore a funzione */
return f(x, y); /* chiamata tramite il puntatore */
}
int main(void) {
printf("%d %d\n", applica(somma, 3, 4), applica(prodotto, 3, 4)); /* 7 12 */
return 0;
}int (*f)(int, int) è un puntatore a una funzione con due int che restituisce int; senza le parentesi, int *f(int, int) sarebbe una funzione che restituisce int *. Con typedef int (*Confronto)(const void *, const void *); il tipo ha un nome.
qsort
void qsort(void *base, size_t n, size_t dim, int (*cmp)(const void *, const void *));base: indirizzo del primo elemento;n: numero di elementi;dim: dimensione in byte di un elemento (sizeof v[0]).cmp(a, b)riceve puntatori agli elementi (comeconst void *) e restituisce un numero< 0,0o> 0se*ava prima, pari o dopo*b. Si converte il puntatore al tipo vero dell'elemento.
#include <stdlib.h>
#include <string.h>
int cmp_int(const void *a, const void *b) {
int x = *(const int *)a, y = *(const int *)b;
return (x > y) - (x < y); /* mai x - y: può andare in overflow */
}
int cmp_str(const void *a, const void *b) { /* array di char *: elementi di tipo char * */
return strcmp(*(const char * const *)a, *(const char * const *)b);
}
typedef struct { char nome[32]; long num; } Voce;
int cmp_voce(const void *a, const void *b) { /* per nome, poi per numero */
const Voce *p = a, *q = b;
int c = strcmp(p->nome, q->nome);
return c != 0 ? c : (p->num > q->num) - (p->num < q->num);
}int v[] = {5, -2, 9, 0};
qsort(v, 4, sizeof v[0], cmp_int); /* -2 0 5 9 */
const char *nomi[] = {"Zeno", "Ada", "Mia"};
qsort(nomi, 3, sizeof nomi[0], cmp_str); /* Ada Mia Zeno */
Voce rubrica[100]; int n = 0; /* ... riempita ... */
qsort(rubrica, n, sizeof rubrica[0], cmp_voce);qsort non è stabile (elementi uguali possono cambiare ordine relativo), costa tipicamente e usa la stessa funzione cmp per ogni coppia: deve essere un ordine coerente (cmp(a,b) e cmp(b,a) di segno opposto).
bsearch
void *bsearch(const void *chiave, const void *base, size_t n, size_t dim,
int (*cmp)(const void *, const void *));Ricerca binaria (Ricerca lineare e binariaRicerca lineare su sequenze qualsiasi in O(n); ricerca binaria su sequenze ordinate in O(log n), versione iterativa e ricorsiva, invariante e errori di indice; modulo bisect.Ricerca lineare e binaria →) in : restituisce il puntatore all'elemento trovato o NULL. L'array deve essere già ordinato con lo stesso cmp. La chiave è un puntatore a un elemento "di prova" (anche un Voce con solo il campo chiave riempito); l'indice si ricava con la differenza di puntatori.
int cmp_nome(const void *a, const void *b) { /* confronta solo il campo chiave */
return strcmp(((const Voce *)a)->nome, ((const Voce *)b)->nome);
}
qsort(rubrica, n, sizeof rubrica[0], cmp_nome); /* ordinata per nome */
Voce cerca = { .nome = "Mia" }; /* basta riempire il campo chiave */
Voce *p = bsearch(&cerca, rubrica, n, sizeof rubrica[0], cmp_nome);
if (p != NULL)
printf("indice %ld, numero %ld\n", (long)(p - rubrica), p->num);Con cmp_voce (che guarda anche num) la ricerca per solo nome fallirebbe: la chiave di prova viene confrontata con lo stesso cmp dell'ordinamento, quindi cmp deve guardare solo i campi che identificano l'elemento cercato.
Dizionario ordinato su array dinamico di struct
È l'analogo C del dizionario su array ordinato di Realizzare contenitori su array e listeCome si realizza un ADT contenitore partendo da un array: lunghezza logica e capacità con raddoppio (costo ammortizzato), dizionario su array ordinato con ricerca binaria, coda doppia su array circolare, coda con priorità a livelli, ADT costruiti sopra altri ADT (pila di code, pila reversibile); tabella dei costi.Realizzare contenitori su array e liste →: ricerca con bsearch, inserimento con memmove, capacità che raddoppia con realloc (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 →).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct { char nome[32]; long num; } Voce;
typedef struct { Voce *v; 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); }
Voce *rubrica_cerca(const Rubrica *r, const char *nome) { /* O(log n) */
Voce chiave;
strncpy(chiave.nome, nome, sizeof chiave.nome - 1);
chiave.nome[sizeof chiave.nome - 1] = '\0';
return bsearch(&chiave, r->v, r->n, sizeof(Voce), cmp_nome);
}
int rubrica_inserisci(Rubrica *r, const char *nome, long num) { /* O(n); 0 = ok */
Voce *p = rubrica_cerca(r, nome);
if (p != NULL) { p->num = num; return 0; } /* nome presente: sostituisce */
if (r->n == r->cap) { /* pieno: raddoppia */
size_t nuova = r->cap ? 2 * r->cap : 4;
Voce *t = realloc(r->v, nuova * sizeof(Voce));
if (t == NULL) return -1;
r->v = t; r->cap = nuova;
}
size_t i = 0; /* posto: primo elemento >= nome */
while (i < r->n && strcmp(r->v[i].nome, nome) < 0) i++;
memmove(&r->v[i + 1], &r->v[i], (r->n - i) * sizeof(Voce)); /* sposta a destra (memmove, non memcpy) */
strncpy(r->v[i].nome, nome, sizeof r->v[i].nome - 1);
r->v[i].nome[sizeof r->v[i].nome - 1] = '\0';
r->v[i].num = num;
r->n++;
return 0;
}
int main(void) {
Rubrica r; rubrica_init(&r);
rubrica_inserisci(&r, "Zeno", 3);
rubrica_inserisci(&r, "Ada", 1);
rubrica_inserisci(&r, "Mia", 2);
rubrica_inserisci(&r, "Ada", 9); /* sostituisce */
for (size_t i = 0; i < r.n; i++) printf("%s %ld\n", r.v[i].nome, r.v[i].num);
Voce *p = rubrica_cerca(&r, "Mia");
printf("%s\n", p ? "trovata" : "assente");
rubrica_libera(&r);
return 0;
}Output: Ada 9, Mia 2, Zeno 3, trovata. Il posto d'inserimento è cercato qui in modo lineare per brevità; con una ricerca binaria "del punto d'inserimento" (come bisect_left) l'intero inserisci resta comunque per lo spostamento.
Errori tipici
- Funzione di confronto che restituisce
x - y: overflow con interi grandi. - Dimenticare il cast del
void *al tipo vero, o usare un livello di puntatore sbagliato (negli array di stringhe l'elemento è unchar *, il confronto ricevechar **). - Chiamare
bsearchsu un array non ordinato o ordinato con un confronto diverso: risultato imprevedibile. - Passare a
qsortil numero di byte invece del numero di elementi, osizeof v(dimensione dell'array intero) al posto disizeof v[0]. - Usare
memcpyper spostare zone che si sovrappongono: servememmove. - Perdere il vecchio puntatore con
v = realloc(v, ...)quandoreallocfallisce (restituisceNULL): si usa un puntatore temporaneo.
Versione ripasso
Puntatori a funzione
int (*f)(int, int): puntatore a funzione con due int che restituisce int (senza parentesi sarebbe una funzione che restituisce int *); il nome della funzione è il suo indirizzo; chiamata f(x, y) (Puntatori in CUn puntatore contiene un indirizzo di memoria; operatori & e *, NULL, puntatori come parametri per modificare variabili del chiamante, aritmetica dei puntatori, legame tra array e puntatori, const, puntatori a puntatori.Puntatori in C →, Funzioni in CDefinizione e prototipo di una funzione C, tipo di ritorno e void, passaggio dei parametri sempre per valore, variabili locali, globali e static, file header e compilazione separata, funzioni ricorsive.Funzioni in C →).
qsort
qsort(base, n, sizeof base[0], cmp); cmp(const void *a, const void *b) riceve puntatori agli elementi e restituisce <0, 0, >0. Mai x - y (overflow): (x > y) - (x < y). Array di stringhe: elementi char *, quindi strcmp(*(const char * const *)a, *(const char * const *)b); struct: confronto sul campo chiave. Non stabile, .
bsearch
bsearch(&chiave, base, n, sizeof base[0], cmp): puntatore all'elemento o NULL, ; l'array dev'essere ordinato con lo stesso cmp; indice = p - base (Ricerca lineare e binariaRicerca lineare su sequenze qualsiasi in O(n); ricerca binaria su sequenze ordinate in O(log n), versione iterativa e ricorsiva, invariante e errori di indice; modulo bisect.Ricerca lineare e binaria →).
Dizionario ordinato su array dinamico di struct
struct { Voce *v; size_t n, cap; }: cerca con bsearch ; inserisci: nome presente → sostituisce; pieno → realloc raddoppiando (puntatore temporaneo); memmove per fare posto, (Realizzare contenitori su array e listeCome si realizza un ADT contenitore partendo da un array: lunghezza logica e capacità con raddoppio (costo ammortizzato), dizionario su array ordinato con ricerca binaria, coda doppia su array circolare, coda con priorità a livelli, ADT costruiti sopra altri ADT (pila di code, pila reversibile); tabella dei costi.Realizzare contenitori su array e liste →, 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 →).
Errori tipici: confronto x - y; cast o livello di puntatore sbagliato; bsearch su array non ordinato; sizeof v al posto di sizeof v[0]; memcpy su zone sovrapposte; v = realloc(v, ...) che perde il blocco se fallisce.