Lettura di una lista da file, in ordine, con ricorsione

Questa è la prima funzione ricorsiva che crea/modifica una lista. Si tratta di una funzione che legge gli elementi che sono scritti su un file, e restituisce la lista composta da questi elementi nello stesso ordine in cui sono scritti sul file.

Usiamo ancora la definizione ricorsiva di lista (una lista può essere vuota, oppure composta da un elemento e dal resto). Soltanto che, questa volta, la lista va costruita, piuttosto che usata.

Per prima cosa, in che modo possiamo arrivare alla situazione in cui la lista da costruire è vuota? Se tentiamo di leggere un elemento da file, e la cosa non riesce, possiamo dire che non ci sono altri elementi da leggere, per cui la lista da restituire è vuota. Quindi, facciamo una fscanf, e se questa non ritorna 1, allora diciamo che non ci sono altri elementi da leggere su file, per cui ritorniamo la lista vuota.

D'altra parte, se la lettura riesce, allora la lista che va restituita non è vuota. In particolare, il primo elemento della lista è quello che abbiamo letto da file. Bisogna ora leggere il resto della lista. Qui sfruttiamo l'ipotesi ricorsiva, ossia che chiamando la funzione stessa, questa restituisce la lista dei rimanenti elementi che stanno su file, ossia assumiamo che la chiamata ricorsiva legga correttamente il resto della lista.

Consideriamo quindi una funzione che riceve un descrittore di un file già aperto, e che restituisce la lista degli elementi letti in ordine. L'intestazione di questa funzione è quindi: TipoLista LeggiFileRic(FILE *fd). In questa funzione, tentiamo di leggere un elemento da file. Se la cosa non riesce, diciamo che la lista è vuota, per cui si ritorna NULL. Se la lettura ha successo, l'elemento letto deve essere il primo della lista, mentre il resto della lista può venire creato con una chiamata ricorsiva. Quindi, si fa la allocazione di una struttura; nel campo val ci mettiamo il valore letto da file, mentre nel campo next ci mettiamo la lista creata dalla chiamata ricorsiva. La funzione è quindi la seguente.

TipoLista LeggiFileRic(FILE *fd) {
  TipoLista l;
  int x;
  int res;

  res=fscanf(fd, "%d", &x);
  if(res!=1)
    return NULL;
  else {
    l=malloc(sizeof(struct NodoLista));
    l->val=x;
    l->next=LeggiFileRic(fd);
    return l;
  }
}

Questa funzione richiede come parametro il descrittore di un file già aperto. Se vogliamo una funzione che prende come argomento il nome di un file (ossia una stringa) e restituisce la lista letta da file, dobbiamo aprire il file e passare il descrittore alla funzione di sopra. La lista da restituire è semplicemente quella ritornata dalla funzione di sopra. La funzione che fa questo è qui sotto.

TipoLista LeggiFile(char *nomefile) {
  FILE *fd;

  fd=fopen(nomefile, "r");
  if(fd==NULL) {
    perror("Errore in apertura del file");
    exit(1);
  }

  return LeggiFileRic(fd);
}

Capita abbastanza spesso, nella pratica, che si riesca a scrivere una funzione ricorsiva abbastanza facilmente, ma che la funzione da realizzare richieda dei passi preliminari. Un altro caso già visto è quello della funzione di stampa della serie di Fibonacci, che richiedeva di stampare i primi due valori della serie prima della chiamata ricorsiva. Se alcune operazioni vanno fatte solo all'inizio, è bene creare una funzione ausiliaria che effettua queste operazioni e poi chiama la funzione ricorsiva. Questo vale anche nel caso in cui la funzione ricorsiva ha dei parametri, che la funzione che ci occorre non deve avere: questo è per esempio il caso della funzione che stampa la serie di Fibonacci, in cui la funzione ricorsiva richiede tre parametri, mentre vogliamo poter chiamare una funzione di stampa che abbia come unico parametro il limite.