Ordinamento di un array di strutture






Riprendiamo l'esercizio di ieri e cerchiamo di ordinare gli elementi dell'array di strutture.



#include <stdio.h>     // per printf()
#include <string.h>    // per le funzioni di gestione delle stringhe
#include <stdlib.h>    // per malloc()



struct temp{char titolo[100];
                 char *autore;
                 int collocazione;};


typedef struct temp libro;


int main()
{int i=0;
 char temp[4];
 libro elenco[30];

// ciclo di inizializzazione
 for(i = 0;i < 30 ; i++)
  {
   elenco[i].collocazione = 0 ;
   elenco[i].titolo[0] = '\0' ;
   // o, in modo analogo
   * ( elenco[i].titolo ) = '\0' ;
   // allocazione di memoria
     elenco[i].autore = (char *) malloc ( sizeof(char) * (50 + 1) );
  }
// fine ciclo di inizializzazione

// ciclo di assegnazione
for( i = 0 ; i < 30 ; i ++ )
  {
    printf(" Vuoi inserire autore e titolo di un libro ? (s/n)");
    scanf("%s",temp);
    if( strncmp(temp,"s",1) == 0 ) break;    // in questo modo anche viene accettato
    printf("\n Inserisci il titolo del libro (max 99 car.)");
    gets( elenco[i].titolo ) ;
    printf("\n Inserisci nome e cognome dell'autore del libro (max 50 car.)");
    gets( elenco[i].autore ) ;
    printf("\n Inserisci la collocazione del libro");
    scanf("%d", & ( elenco[i].collocazione ) ); // uso & perché il parametro è un int
  }

// fine ciclo di assegnazione
/* inizio ciclo di visualizzazione; devo stampare SOLO gli elementi dell'array in cui l'utente ha inserito titolo, autore e collocazione. Distinguo tali elementi dagli altri sia perché la loro collocazione è diversa da zero sia perché la stringa titolo inizia con un carattere diverso da '\0'
*/

for(i = 0 ; i < 30 ; i ++ )
{
   if(elenco[i].collocazione == 0) break;
   printf("Il titolo del libro e\' : %s \n", elenco[i].titolo ) ;
   printf("L\'autore del libro e\' : %s \n", elenco[i].autore ) ;
   printf("La collocazione del libro e\' : %d \n", elenco[i].collocazione ) ;
  }


return 0;}












Nota: se utilizziamo la notazione a puntatore, gli elementi dell'array titolo contenuto all'interno della struttura si individuano, come per tutti gli array, incrementando il puntatore al primo elemento dell'array. Se vogliamo raggiungere l'n-esimo elemento:


   * ( elenco[i].titolo + n )

e NON

   elenco[i]. * ( titolo + n )


in quanto la variabile titolo esiste SOLO come membro della struttura elenco[i]


























A questo punto siamo in grado di creare un array di strutture e di inizializzarle. Possiamo ordinarne gli elementi ? (ad esempio per collocazione)





I due ingredienti fondamentali di un algoritmo di ordinamento sono:

(1) criterio di ordinamento

(2) capacità di scambiare gli elementi




















(1) criterio di ordinamento


elenco[ i ].collocazione < elenco[ i + 1 ].collocazione



(2) scambio degli elementi ( -> scambio dei valori degli elementi contenuti nella struttura)
Noi sappiamo scambiare tra loro variabili dei tipi primitivi, quindi possiamo implementare questa procedura.


supponiamo di voler effettuare uno scambio tra i valori degli elementi della struttura i-esima e di quelli della struttura (i+1)-esima usando la metodologia adottata fino ad ora.
Essendoci 3 elementi ho bisogno di tre variabili temporanee.



int temp_i;
char temp_s[100];
char *temp_p;



temp_i = elenco[ i ].collocazione ;
elenco[ i ].collocazione = elenco[ i + 1 ].collocazione ;
elenco[ i + 1 ].collocazione = temp_i ;



strcpy(temp_s , elenco[ i ].titolo ) ;
strcpy(elenco[ i ].titolo , elenco[ i + 1 ].titolo ) ;
strcpy(elenco[ i + 1 ].titolo , temp_s ) ;



temp_p = elenco[ i ].autore ;
elenco[ i ].autore = elenco[ i + 1 ].autore ;
elenco[ i + 1 ].autore = temp_p ;



Domanda: è possibile applicare il metodo utilizzato per l'elemento autore all'elemento titolo ?


Qual è la differenza tra i tre scambi ? Nei primi due vengono modificati i valori delle aree di memoria cui si fa riferimento (ovvero un intero ed un array di caratteri); nel terzo caso le aree di memoria non vengono alterate, si fa semplicemente uno scambio di indirizzi.
























Prima dello scambio:























Dopo lo scambio




















La stessa cosa potevamo farla usando l'operatore di assegnazione (che esegue una copia membro a membro)


libro temp;

temp = elenco[ i ] ;
elenco[ i ] = elenco[ i + 1 ];
elenco[ i ] = temp;










Scriviamo ora il codice completo di algoritmo di ordinamento; nel farlo supporremo che il numero di strutture inizializzate dall'utente sia num_init (dove num_init <= 30)






Scriviamo inizialmente il ciclo di controllo:




libro temp;

... // restante parte del codice




for( i = 0 ; i < num_init - 1 ; i++)
  {
   if(elenco[i].collocazione < elenco[i+1].collocazione)
     {temp = elenco[i];
       elenco[i] = elenco[i+1];
       elenco[i+1]=temp;}
  }


NOTA: prima di effetture questo tipo di scambio la variabile temp.autore non è stata inizializzata. Perché ?




















Il ciclo di ordinamento completo risulta:


int scambio = 1;
libro temp;
... // istruzioni

while(scambio)
{ scambio = 0;
   for( i = 0 ; i < num_init - 1 ; i++)
     {
      if(elenco[i].collocazione < elenco[i+1].collocazione)
        {temp = elenco[i];
          elenco[i] = elenco[i+1];
          elenco[i+1]=temp;
          scambio = 1; }
     }
}























Lo scambio è stato effettuato utilizzando, di volta in volta, l'operatore di assegnazione e le variabili di tipo libro. Proviamo ora ad effettuare la medesima cosa usando i puntatori a struttura.




int scambio = 1;
libro temp;

libro* punt1;
libro* punt2;

... // istruzioni

while(scambio)
{ scambio = 0;
   for( i = 0 ; i < num_init - 1 ; i++)
     {
      punt1 = & elenco[i] ;
      punt2 = & elenco[i] ;

      if( punt1 -> collocazione < punt2 -> collocazione)
        {temp = *punt1;
          *punt1 = *punt2;
          *punt2 = temp;
          scambio = 1; }
     }
}





















Ora, quindi, siamo in grado di effettuare lo scambio, in particolare, servendoci dei puntatori a struttura. Noi sappiamo come passare un puntatore ad una funzione, quindi possiamo delegare lo scambio ad una funzione. (risulta chiara, a questo punto, la necessità di dichiarare il prototipo di struttura all'esterno del corpo di main())






void scambia_libri (libro* primo, libro* secondo)
{libro temp;
  temp = *primo;
  *primo = *secondo;
  *secondo = temp;
  }

















Il codice quindi diventa :

int scambio = 1;
libro temp;

libro* punt1;
libro* punt2;

... // istruzioni

while(scambio)
{ scambio = 0;
   for( i = 0 ; i < num_init - 1 ; i++)
     {
      punt1 = & elenco[i] ;
      punt2 = & elenco[i] ;

      if( punt1 -> collocazione < punt2 -> collocazione)
        {scambia_libri( punt1 , punt2 );
          scambio = 1; }
     }
}

























Ora manca l'ultimo passaggio:

all'atto della dichiarazione di un vettore con allocazione statica della memoria il nome del vettore diventa automaticamente il puntatore al primo elemento del vettore.

int vettore[40];

vettore risulta essere un puntatore ad intero avente come valore:    & (vettore [0] )


Applichiamo questa regola per snellire ulteriormente il codice:






 libro elenco[30]; // allocazione statica di memoria


elenco è automaticamente una variabile di tipo libro * che contiene l'indirizzo del primo elemento del vettore di strutture, ovvero l'indirizzo di elenco[0]. Conoscendo tale indirizzo e sapendo utilizzare l'aritmetica dei puntatori possiamo muoverci all'interno dell'array di strutture.




elenco[i].collocazione     diventa     (elenco + i) -> collocazione





Ovvero, invece di accedere all'elemento della struttura mediante la variabile di tipo struttura, accedo all'elemento mediante il puntatore a quella struttura.



















In questo modo il codice, a parte l'uso dell'operatore di accesso alla struttura, ricorda molto il codice scritto a suo tempo per ordinare un array di char, int, ...




int scambio = 1;
libro temp;

... // istruzioni

while(scambio)
{ scambio = 0;
   for( i = 0 ; i < num_init - 1 ; i++)
     {
      if( (elenco + i) ->collocazione <  (elenco + i + 1) -> collocazione)
        {scambia_libri( elenco + i , elenco + i + 1 );
          scambio = 1; }
     }
}
















Un quesito per coloro a cui piacciono i puntatori (ma non solo). Supponiamo per assurdo che l'operatore di assegnazione non sia applicabile alle strutture: riuscireste ad implementare il compito svolto da tale operatore (ovvero copiare i valori dei membri di una struttura nei corrispondenti membri di una struttura ad essa identica) usando solo i puntatori alle due strutture e l'aritmetica dei puntatori (ed ovviamente l'operatore di assegnazione, a patto di non applicarlo alle strutture) ?