Visualizzazione dei risultati da 1 a 8 su 8
  1. #1

    [C] - funzione ricorsiva ritorna sempre lo stesso valore

    Ciao ragazzi,

    ho scritto un piccolo programma in C per risolvere il famoso giro del cavallo. Il tutto si basa su una funzione ricorsiva il cui scopo è muovere il cavallo dentro la scacchiera. Un contatore viene aumentato ad ogni casella visitata dal cavallo; se il contatore arriva a valere 63 (partendo da 0), significa che tutte le 64 caselle della scacchiera sono state visitate e che quindi il giro è stato completato con successo.

    La mia necessità è quella di capire durante l'esecuzione del programma quando il giro viene completato con successo. Per ottenere ciò, uso un blocco if che verifica il valore del contatore. Se vale 64, la funzione ritorna 0 (=successo). Un altro blocco if ritorna 1 (=insuccesso) se il cavallo risulta bloccato (cioè se si trova in una posizione da cui non può più spostarsi verso nuove caselle della scacchiera).

    Il problema è che le istruzioni return non funzionano a dovere, in quanto ritornano sempre il valore "8" (non mi è chiaro da dove questo valore derivi). Anche se l'istruzione è return 1, il valore ritornato è sempre 8.


    codice:
    #include <stdio.h>
    #define LATOMATRICE 8
    
    /* prototipi di funzione */
    
    void inizializzaMatrice ( int a [][ LATOMATRICE ] );
    void stampaMatrice ( int a [][ LATOMATRICE ] );
    int percorriScacchiera ( int a [][ LATOMATRICE ], int b [ ], int c [ ], int d, int e, int f );
    
    int main() {
    
        int scacchiera [ LATOMATRICE ][ LATOMATRICE ];
        int spostamentoOrizzontale [ LATOMATRICE ] = { 2, 1, -1, -2, -2, -1, 1, 2 };
        int spostamentoVerticale   [ LATOMATRICE ] = { -1, -2, -2, -1, 1, 2, 2, 1 };
        /* ogni mossa del cavallo è a L; due caselle in una direzione orizz./vert. e
         * una perpendicolare alla direttrice precedente. Gli spostamenti orizzontale
         * SX e verticale in alto avranno valore negativo in quanto sulla matrice
         * spostarsi in queste direzioni significa sottrarre unità dal valore della
         * riga/colonna in cui ci si trova. Da un punto centrale della scacchiera
         * (3,4) è possibile effettuare 8 mosse a L (da 0 a 7). Quindi la costante
         * LATOMATRICE va bene anche per esprimere il numero di mosse del cavallo.
         */
        int x = 0;
        int y = 0;
        /* coordinate di partenza del cavallo */
        int mossaIniziale = 0;
        int esito;
        
    
        inizializzaMatrice ( scacchiera );
        /* inizializzo tutta la scacchiera/matrice a -1, così da capire
         * facilmente quando una  casella è  già  stata  visitata  dal
         * cavallo.
         */  
    
        esito = percorriScacchiera ( scacchiera, spostamentoOrizzontale, spostamentoVerticale, x, y, mossaIniziale );
        printf ( "esito = %d\n", esito );
        stampaMatrice ( scacchiera );
        printf ( "\n" );
        
        inizializzaMatrice ( scacchiera );
        
        x = 3;
        y = 4;
    
        esito = percorriScacchiera ( scacchiera, spostamentoOrizzontale, spostamentoVerticale, x, y, mossaIniziale );
        printf ( "esito = %d\n", esito );
        stampaMatrice ( scacchiera );
        printf ( "\n" );
    
        inizializzaMatrice ( scacchiera );
    
        x = 6;
        y = 3;
    
        esito = percorriScacchiera ( scacchiera, spostamentoOrizzontale, spostamentoVerticale, x, y, mossaIniziale );
        printf ( "esito = %d\n", esito );
        stampaMatrice ( scacchiera );
        printf ( "\n" );
    
        return 0;
    
    }
    
    void inizializzaMatrice ( int a [][ LATOMATRICE ] ) {
    
       int x, y;
    
       for ( x = 0; x < LATOMATRICE; x++ ) {
    
          for ( y = 0; y < LATOMATRICE; y++ ) {
    
             a [ x ][ y ] = -1;
    
          }
    
       }
    
    }
    
    
    void stampaMatrice ( int a [][ LATOMATRICE ] ) {
    
       int x, y;
    
       for ( x = 0; x < LATOMATRICE; x++ ) {
    
          for ( y = 0; y < LATOMATRICE; y++ ) {
    
               printf ( "%3d", a [ x ][ y ] );
    
            }
    
          printf ( "\n" );
    
       }
    
    }
    
    int percorriScacchiera ( int a [][ LATOMATRICE ], int b [ ], int c [ ], int d, int e, int f ) {
       /* a è la matrice scacchiera.
        * b è il vettore spostamentoOrizzontale.
        * c è il vettore spostamentoVerticale.
        * d è il contatore x.
        * e è il contatore y.
        * f è la variabile mossaIniziale, indicante le mossa che il cavallo deve tentare
        */
    
       static int contatore = 0;
       /* necessario per tenere traccia dei movimenti del cavallo: aumenta
        * di una unità ad ogni casella visitata e deve sopravvivere alle ricorsioni
        */
    
       d += c [ f ];
       e += b [ f ];
       /* calcolo la posizione del cavallo alla luce della mossa rappresentata da f.
        * d ed e rappresentano le coordinate del cavallo, riga e colonna.
        */
    
       if (( d > LATOMATRICE - 1 || e > LATOMATRICE - 1 ) || ( d < 0 || e < 0 ) ||
       /*                      se sono fuori dalla scacchiera                     */
          ( a [ d ] [ e ] > -1 )) {
       /* OPPURE la prossima casella in cui si muoverà il cavallo è già stata visitata,
        * tento un'altra strada
        */
    
          d -= c [ f ];
          e -= b [ f ];
    
          /* in questo caso annullo gli effetti del precedente calcolo (d += c [ f ] etc.) per "riportare" il cavallo alle
           * precedenti coordinate. Fatto ciò, posso tentare un'altra mossa, cioè la mossa f+1 (cioè se ad es. ho tentato in una data
           * posizione della scacchiera la mossa 3 e quest'ultima mi ha portato fuori scacchiera, torno alla precedente posizione e
           * ritento la mossa 4). In questo if avviene il passo ricorsivo.
           */
          f++;
          /* aumento d di una unità per tentare la mossa successiva
           */
    
          if ( f > 7 ) {
          /* in questo caso, significa che sono completamente bloccato. Cioè il cavallo non ha più mosse possibili da tentare, perchè tutte le
           * precedenti o lo portavano fuori scacchiera o su caselle già visitate.
           */
    
             contatore = 0;
             /* se sono dentro a questo if significa che il cavallo ha fallito, quindi devo ripartire da nuove coordinate. Ovviamente il contatore
              * va azzerato perchè altrimenti la prima mossa valida del successivo giro del cavallo non partirebbe da 0, ma da un valore più alto,
              * rendendo il contatore stesso privo di senso, anche perchè non si riuscirebbe più a capire se il cavallo ha avuto successo o meno.
              * L'unico modo per capirlo è quello di verificare che il contatore sia uguale a 64, cioè il numero di caselle della scacchiera. Se il
              * contatore parte da un valore > 0, magari arriva a 64 prima che tutte le caselle siano state realm. visitate, e quindi il programma
              * riscontrerebbe un falso successo.
              */
    
             return 1;
    
    
          }
    
          percorriScacchiera ( a, b, c, d, e, f );
          /* a è la matrice scacchiera.
           * b è il vettore spostamentoOrizzontale.
           * c è il vettore spostamentoVerticale.
           * d è il contatore x.
           * e è il contatore y.
           * f è la variabile mossaIniziale, indicante le mosse che il cavallo deve tentare,
           * in questo caso f varrà un'unità in più rispetto al precedente tentativo, stiamo
           * cioè tentando una nuova mossa.
           */
    
       }
    
       else {
    
          a [ d ][ e ] = contatore;
          /* se le nuove coordinate della posizione del cavallo sono accettabili
           * (cioè se risultano dentro la scacchiera e se la casella non è mai stata
           * visitata), andrò a printare nella casella un intero compreso fra 1 e 64
           */
    
          if ( contatore == 63 ) {
          /* se contatore vale 63 significa che il cavallo ha visitato con successo
           * tutte le 64 caselle della scacchiera. Il contatore parte infatti da 0
           * quindi da 0 a 63 sono 64 caselle.
           */
    
              return 0;
    
          }
    
          contatore++;
          f = 0;
          /* se ho avuto successo, la mossa successiva sarà di nuovo la 0
           */
          percorriScacchiera ( a, b, c, d, e, f );
          /* in questo caso f (cioè la mossa compiuta) non viene aumentato, perchè la
           * precedente mossa ha avuto successo (cioè cavallo dentro scacchiera e casella
           * mai visitata). Di conseguenza, provo a fare la mossa successiva ripartendo da
           * 0 e, in caso di insuccesso, proverò con la mossa 1.
           */
    
       }
    
    }
    Output di esempio (3 scacchiere in cui il cavallo parte da posizioni differenti della scacchiera e dove non completa il giro; le caselle contrassegnate dal valore -1 non sono mai state visitate dal cavallo):

    codice:
    esito = 8
      7 -1 -1 -1  5  2 -1 -1
     -1 -1  6  1 -1 -1  4 -1
     -1  0 -1 -1  3 -1 -1 -1
     -1 -1 -1 -1 -1 -1 -1 -1
     -1 -1 -1 -1 -1 -1 -1 -1
     -1 -1 -1 -1 -1 -1 -1 -1
     -1 -1 -1 -1 -1 -1 -1 -1
     -1 -1 -1 -1 -1 -1 -1 -1
    
    esito = 8
     32 13 18  3 16 11  8  1
     19  4 15 12  7  2 27 10
     14 31  6 17 28  9  0 -1
      5 20 29 -1 23 -1 -1 26
     30 -1 22 -1 -1 25 -1 -1
     21 -1 -1 24 -1 -1 -1 -1
     -1 -1 -1 -1 -1 -1 -1 -1
     -1 -1 -1 -1 -1 -1 -1 -1
    
    esito = 8
     34 15 20  5 18 13 10  3
     21  6 17 14  9  4 29 12
     16 33  8 19 30 11  2 -1
      7 22 31 -1 25 -1 -1 28
     32 -1 24 -1 -1 27 -1  1
     23 -1 -1 26 -1  0 -1 -1
     -1 -1 -1 -1 -1 -1 -1 -1
     -1 -1 -1 -1 -1 -1 -1 -1
    Il mio sospetto è che la variabile esito assuma sempre lo stesso valore a causa della ricorsione, ma non saprei dire perchè. Esito dovrebbe valere 1 in caso di insuccesso (cioè quasi sempre) e 0 in caso di successo.

    Come posso risolvere?

    Grazie in anticipo per l'aiuto.

    Daniele

  2. #2
    Il tuo codice è un po lungo da analizzare, però ad un occhiata fugace mi pare che nella funzione percorriScacchiera manca più di una return.

    Ad esempio il compilatore c# non ti permetterebbe di compilare una cosa del genere, ti darebbe errore del tipo NOT ALL CODE PATHS RETURN A VALUE. Penso anche che il tuo compilatore C, benchè i compilatori C siano molto permissivi ti dia almeno un warning del genere.


    Una funzione che dichiara di restituire int deve avere una istruzione return VALORE_INT in ogni punto di uscita del codice. Pur non conoscendo il tuo algoritmo penso che forse dovresti sostituire le varie chiamate ricorsive a percorriScacchiera(...) con return percorriScacchiera.

  3. #3
    Grazie per la risposta, Andrea.

    Nella funzione percorriScacchiera (cuore del programma) sono presenti due istruzioni return, contenute rispettivamente in 2 blocchi if. La prima è return 1 (=insuccesso), la seconda è return 0 (= successo).

    Debuggando il programma posso affermare con certezza che l'istruzione return 1 viene sicuramente raggiunta dal flusso del programma. In qualche modo però viene ignorata, perchè l'intero ritornato è sempre 8 (oppure viene interpretata sui generis).

    Hai idea del perchè un'istruzione "return 1" possa venire così palesemente ignorata?

    Grazie

  4. #4
    Non viene certamente raggiunta...

    codice:
     else {
    
          a [ d ][ e ] = contatore;
          /* se le nuove coordinate della posizione del cavallo sono accettabili
           * (cioè se risultano dentro la scacchiera e se la casella non è mai stata
           * visitata), andrò a printare nella casella un intero compreso fra 1 e 64
           */
    
          if ( contatore == 63 ) {
          /* se contatore vale 63 significa che il cavallo ha visitato con successo
           * tutte le 64 caselle della scacchiera. Il contatore parte infatti da 0
           * quindi da 0 a 63 sono 64 caselle.
           */
    
              return 0;
    
          }
    DA QUA IN POI NON C'E NESSUNA RETURN 
          contatore++;
          f = 0;
          /* se ho avuto successo, la mossa successiva sarà di nuovo la 0
           */
          percorriScacchiera ( a, b, c, d, e, f );
          /* in questo caso f (cioè la mossa compiuta) non viene aumentato, perchè la
           * precedente mossa ha avuto successo (cioè cavallo dentro scacchiera e casella
           * mai visitata). Di conseguenza, provo a fare la mossa successiva ripartendo da
           * 0 e, in caso di insuccesso, proverò con la mossa 1.
           */
    
       }

  5. #5
    Parlavo della return 1, non della return 0.

    Cmq. debuggando e seguendo il flusso linea per linea, ti assicuro che la return 1 viene raggiunta. Ho provato anche a commentarla per verificare l'effetto e il risultato è un loop infinito, segno del fatto che l'istruzione return viene raggiunta ma eseguita "male", per così dire.

    Il blocco di codice che mi hai gentilmente indicato non contiene di proposito un'istruzione return perchè in quel punto non mi pare serva. In quel blocco avviene semplicemente il passo ricorsivo quindi non vedo cosa dovrei ritornare.

    Forse sbaglio il modo in cui utilizzo return. Io lo uso come il punto di uscita del codice e ne ho inseriti due (return 1 e return 0) perchè due sono le possibilità (successo o fallimento del giro della scacchiera).

    In che modo potrebbe aiutarmi secondo te un'istruzione come "return percorriScacchiera" al posto della semplice chiamata ricorsiva? qual è la reale differenza?

    Scusa per le domande ma sto imparando

  6. #6
    lo schema di una funzione ricorsiva è di questo tipo

    int Ricorsiva(param)
    {
    return Ricorsiva(param);
    }

    tu non metti il return davanti alla chiamata ricorsiva...

    Ciao

  7. #7
    Ovvero questo:
    codice:
    return percorriScacchiera ( a, b, c, d, e, f );
    anzichè:
    codice:
    percorriScacchiera ( a, b, c, d, e, f );

    Ciao

  8. #8

    [C] - funzione ricorsiva ritorna sempre lo stesso valore [RISOLTO]

    Grazie, ragazzi.

    E' stato sufficiente mettere "return percorriScacchiera" anzichè solo "percorriScacchiera" perchè l'anomalia sparisse immediatamente!

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2024 vBulletin Solutions, Inc. All rights reserved.