Cerchi - Soluzione

In questo tutorial non verrà presentata la possibile soluzione nella sua interezza, ma soltano le parti interessanti, che dovranno essere opportunamente integrate per fornire la soluzione completa e compilabile. Per ottenere un programma completo e funzionante, si tratta pertanto di scrivere la funzione main che dichiara le variabili necessarie e che chiama opportunamente le funzioni descritte.

Per cominciare, si può dichiarare la seguente struttura:

struct cerchio {
    char *nome;
    int x;
    int y;
    unsigned int r;
};

Da notare che il nome viene gestito con un puntatore a carattere. Potrebbe anche essere dichiarato come char nome[128], modificando opportunamente la funzione di lettura riportata di seguito.

1) Numero di ingressi

struct cerchio* carica_elenco(FILE *infile, int *n)
{
    char buf[1000];
    struct cerchio *v;
    int dim;
    char nome[128];

    dim = 128;
    if (!(v = malloc(dim * sizeof(struct cerchio)))) {
        *n = -1;
        return NULL;
    }

    *n = 0;
    while(fgets(buf, sizeof(buf), infile)) {
        sscanf(buf, "%127s %d %d %d", 
                nome,
                &(v + *n)->x,
                &(v + *n)->y,
                &(v + *n)->r);
        (v + *n)->nome = strdup(nome);
        (*n)++;
        if (*n >= dim) {
            dim *= 2;
            if (!(v = realloc(v, dim * sizeof(struct cerchio)))) {
                *n = -1;
                return NULL;
            }
        }
    }
    v = realloc(v, (*n) * sizeof(struct cerchio));
    return v;
}

La funzione inizialmente usa la malloc (presentata in S9.5) per allocare spazio per dim strutture di tipo struct ingresso. L'indirizzo dell'area di memoria allocata è assegnato al puntatore v.

Si ricordi che malloc alloca una quantità di memoria espressa in byte. Pertanto la sizeof (introdotta in S5.10) viene usata per determinare la dimensione di una singola struttura di tipo struct cerchio. Il valore restituito da sizeof viene moltiplicato per dim per ottenere il numero totale di byte necessari per la memorizzazione del vettore di strutture.

Se v vale NULL allora non è stato possibile allocare correttamente la memoria e la funzione ritorna (termina), restituendo NULL per indicare al chiamante il fallimento della lettura.

Se l'allocazione iniziale della memora ha successo, il ciclo while usa la fgets per leggere una riga alla volta dal file infile. In ciascuna iterazione del ciclo, la funzione sscanf viene usata per estrarre i dati dalla riga letta e per memorizzarli in una struttura memorizzata a sua volta nell'area appena allocata. L'area di memoria viene acceduta tramite il puntatore v come fosse un vettore (vedi equivalenza tra puntatori e vettori illustrata in S9.2).

Da notare che il nome del cerchio viene caricato temporaneamente nel vettore nome dichiarato nella carica_elenco, e il valore della stringa viene assegnato al campo nome della struttura corrente con l'istruzione:

  (v + *n)->nome = strdup(nome);

che usa la strdup per duplicare la stringa contenuta in nome (la funzione strdup è spiegata in S9.5) e assegnare il puntatore risultante al campo nome dell'elemento di indice *n del vettore v.

Se, durante la lettura, il numero di elementi caricati nel vettore v eccede la dimensione del vettore dim, il valore di dim viene raddoppiato e la memoria allocata per v viene ridimensionata con la realloc. Il ridimensionamento della memoria allocata non tocca il contenuto della memoria precedentemente utilizzata, cosicché quanto è stato memorizzato fino a questo punto rimane tale, e si ha spazio per memorizzare ulteriori dati. Questo metodo viene illustrato in S9.5.1.

Alla fine del ciclo, il valore di *n conterrà il numero di cerchi letti dal file. Si ricordi che n è un puntatore, che viene usato con il passaggio per riferimento al fine di permette alla funzione carica_elenco di "restituire" il valore opportunamente modificato per riflettere il numero righe lette. Il passaggio di parametri per riferimento è spiegata in S6.6. Ricorda che, se n è un puntatore, il valore intero da esso puntato viene acceduto con la notazione *n (anteponendo l'asterisco - vedi S5.8.1).

Infine, il vettore v viene ridimensionato con realloc per contenere esattamente un numero di strutture pari a *n, e viene restituito l'indirizzo della memoria allocata e contenente i dati letti nella funzione.

Questa funzione può essere chiamata nel main. Il codice interessante è come segue:

    FILE *f;
    struct cerchio *cerchi;
    int n;

    /* ... apertura del file ... */

    cerchi = carica_elenco(f, &n);
    fclose(f);

    /* ... verifica della corretta lettura ... */

    /* ... rimanente codice nel main ... */

Per la stampa si puoi usare una funzione come la seguente:

void stampa_cerchi(struct cerchio *v, int n)
{
    int i;
    for (i = 0; i < n; i++) {
        printf("%s %d %d %d\n", (v+i)->nome, (v+i)->x, (v+i)->y, (v+i)->r);
    }
}

2) Ordinamento

Per ordinare il file si può implementare una funzione ordina come la seguente:

void ordina(struct cerchio *v, int size)
{
    int i, j;
    struct cerchio temp;

    for (i = 0; i < size; i++) {
        for (j = i + 1; j < size; j++) {
            /*
             * confronto le aree
             * N.B. non serve fare il quadrato, ma basta confrontare i raggi
             */
            if (v[i].r > v[j].r) {
                temp = v[i];
                v[i] = v[j];
                v[j] = temp;
            }
        }
    }
}

Alternativamente, dal momento che i dati sono memorizzati in un vettore, è possibile utilizzare la funzione di libreria qsort nel modo seguente:

    qsort(cerchi, n, sizeof(*cerchi), cmp_cerchi);

dopo aver definito la funzione cmp_cerchi di confronto tra strutture, per esempio come segue:

int cmp_cerchi(const void *p1, const void *p2)
{
    const struct cerchio *c1 = p1, *c2 = p2;

    if (c1->r < c2->r) return -1;
    if (c1->r > c2->r) return 1;
    return 0;
}

Ricorda che la funzione di confronto deve restituire un valore negativo se il primo parametro è minore del secondo, un valore maggiore di zero se il primo è maggiore del secondo, oppure zero se sono uguali. In questo caso vengono confrontati i raggi dei due cerchi.

Attenzione che i parametri passati devono essere dei puntatori a void. Maggiori chiarimenti circa la funzione qsort sono disponibili in S13.3.

3) Relazioni

Le funzioni utili per il calcolo e la stampa delle relazioni sono le seguenti:

#define NESSUNA    (0)
#define INTERSECA  (1)
#define CONTIENE   (2)
#define COINCIDE   (3)

char *nome_relazione[] = { "", "INTERSECA", "CONTIENE", "COINCIDE" };
double dist(int x1, int y1, int x2, int y2)
{
    return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}

La funzione relazione restituisce un valore intero che codifica la relazione tra i due cerchi passati come parametro:

int relazione(struct cerchio *c1, struct cerchio *c2)
{
    int rel = NESSUNA;
    int sd;
    
    /* sd e` la distanza tra i centri */
    sd = sqrt(SQR(c1->x - c2->x) + SQR(c1->y - c2->y));
    /* se la distanza è minore della somma dei raggi c'è "interazione" tra i due cerchi */
    if ( sd <= (c1->r + c2->r) ) {
        /* se centro e raggio sono identici, i cerchi coincidono */
        if ( c1->x == c2->x && c1->y == c2->y && c1->r == c2->r ){
            rel = COINCIDE;
        /* se la differenza tra i raggi è minore di sd c'è intersezione */
        } else if ( sd >= abs(c1->r - c2->r) ) {
            rel = INTERSECA;
        /* se un raggio è minore dell'altro il primo contiene il secondo */
        } else if ( c1->r > c2->r) {
            rel = CONTIENE;
        }
    }
    return rel;
}

La funzione è sufficientemente commentata da non richiedere ulteriori spiegazioni.

Nella funzione, SQR è uan macro che calcola il quadrato di un numero. Essa deve essere definita come segue:

#define SQR(x) ((x)*(x))

Per creare una macro come questa si può consultare S3.1.

I codici sono definiti tramite delle #define (vedi S3.1 per una spiegazione di queste direttive del preprocessore). È importante che i codici abbiano valori che partono da 0 in avanti, e non ci sono "buchi" nella sequenza di valori, in quanto in questo modo il valore restituito da relazione può essere usato come indice nel vettore nome_relazione che contiene le stringhe che poi vengono stampate a video nella funzione stampa_relazioni seguente:

void stampa_relazioni(struct cerchio *v, int n)
{
    int i, j, rel;

    for (i = 0; i < n; i++) {
        for (j = 0; j < n; j++) {
            if (i != j) {
                rel = relazione(v+i, v+j);
                if ( rel != NESSUNA ) 
                    printf("%s %s %s\n", (v+i)->nome, nome_relazione[rel], (v+j)->nome);
            }
        }
    }
}