[C] Progetto di programmazione.

Yuyu_13
Buongiorno, sto svolgendo un esercizio dove si richiede l'uso delle stringhe per determinare la parola più "grande" e "piccola" tra quelle inserite e si arresti quando la parola inserita abbia una lunghezza di quattro caratteri.
#include<stdio.h>
#include<string.h>

#define MAX_WORD 20
#define NUM_WORD 20
int read_line(char str[], int n);

int main(void){
  int i, j, len, count=0; 
  char smallest_word[MAX_WORD], largest_word[MAX_WORD];
  char str[MAX_WORD];
  
   
  
     for(; ;){
        if(count==NUM_WORD){
          printf("--Spazio insufficiente--\n"); 
          goto done;
        }
        
        printf("Inserire parola: "); 
        len=read_line(str, MAX_WORD); 						// Chiamata a funzione read_line
        if(len==4){
          goto done;
        }
        strcpy(smallest_word, strcpy(largest_word, &str[0]));			// Sia la stringa smallest_word che largest_word contengono la stringa str[0]
        
        
        if(count>=1){									// Quando count è maggiore o uguale a uno posso effettuare il confronto
          for(i=0; i<=count-1; i++){
                if(strcmp(smallest_word, &str[i+1])<0){
                   strcpy(smallest_word, &str[i+1]);
                  }
             }
   
        
          for(j=0; j<=count-1; j++){
                if(strcmp(largest_word, &str[j+1])>0){
                   strcpy(largest_word, &str[j+1]); 
                  }
             }
         }

        count++;
     }
     done:
          printf("small word: %s\n", smallest_word); 
          printf("large word: %s\n", largest_word);
return 0; 
}

                                                                                  	// Funzione per lettura stringa
int read_line(char str[], int n){
   int ch, i=0; 
   
   while((ch=getchar())!='\n'){
      if(i<n){
        str[i]=ch;
        i++; 
      }
   }
   str[i]='\0'; 
   
return i; 
}


Output




Ho fatto vari tentativi ma nulla, dove sbaglio. Sono in crisi !

Risposte
apatriarca
Che cosa contiene la stringa [tt]str[/tt]? E le stringhe [tt]smallest_word[/tt] e [tt]largest_word[/tt]? Prova a spiegare il tuo procedimento a parole e confrontarlo con quello che hai scritto. In particolare ti invito a porre particolare attenzione a [tt]&str[j + 1][/tt], non credo sia quello che ti aspetti.

In seguito possiamo parlare di come migliorare il tuo algoritmo, che fa molte più operazioni del necessario.

Quinzio
Tante cose da dire...
l'uso del "goto" nel 2021 fa davvero inorridire. Posso sapere dove stai imparando, in quale corso da quale libro ?
Cerca in tutti i modi di evitare i "goto", non e' difficile.

Ad ogni ciclo che esegui copi sempre l'ultima parola scritta nella smallest e nella largest.
Ovviamente cio' non ha senso.

La funzione strcmp non va bene per confrontare la lunghezza delle stringhe.
Cerca di capire bene cosa fa la strcmp e quindi cerca un'altra funzione che misura la lunghezza delle stringhe.

Yuyu_13
Vi ringrazio avermi risposto, premetto che da poco che sto programmando.

@apatriarca
Rispondo in ordine.
Contiene la parola k-esima al passo k,
Contiene la parola più corta,
Contiene la parola più grande.
I cicli dovrebbero servire per confrontare le parole.

@ quinzio
Sono uno studente di matematica, invece, il libro è Programmazione in C, Kim N. King.
Per quanto riguarda l'uso del goto, hai usato le stesse parole del mio prof., perché non va bene usarlo ?
Il strcmp non confronta le lunghezze delle parole? Sul mio libro ci sta scritto che serve per confrontare le lunghezze delle parole mediante gli operatori di $<, >....$?

apatriarca
Prova a mettere dei [tt]printf[/tt] per verificare che la tua ipotesi sia corretta (non lo è). In particolare [tt]str[/tt] è una stringa, ma nella tua descrizione dovrebbe essere un array di stringhe (e quindi un array bidimensionale).

Yuyu_13
@apatriarca ho provato....hai ragione :-D
Dovrei cambiare strategia, potrei memorizzare le parole all'interno delle riga di un array bidimensionale e poi leggere tra queste e selezionare quella più grande e più piccola.
Può andare bene cosi, oppure diversamente ?

Saluti

apatriarca
La funzione standard [tt]strcmp[/tt] è implementata più o meno come segue:
int strcmp(const char *s1, const char *s2) {
    while (*s1 == *s2 && *s1 != '\0') { ++s1; ++s2; }
    return (int)*(unsigned char *)s1 - (int)*(unsigned char *)s2;
}

Confronta due stringhe in ordine lessicografico, restituisce cioè la differenza del primo carattere diverso. Non considera quindi la lunghezza delle stringhe: [tt]"b" > "abcdef"[/tt] per questa funzione per esempio. Non è tuttavia chiaro dal testo dell'esercizio che cosa si intenda come stringa più grande o più piccola e la funzione [tt]strcmp[/tt] fornisce un tipo di ordine che si può usare.

Una soluzione al tuo problema può certamente essere quella di memorizzare tutte le stringhe inserite in un array bidimensionale, (quindi salvi ogni nuova stringa in una riga diversa). L'esercizio può tuttavia essere risolto senza memorizzare tutte le stringhe. Nota infatti che [tt]smallest_word[/tt] e [tt]largest_word[/tt] già contengono la parola più piccola e più grande fino all'iterazione precedente a quella che stai considerando. È quindi davvero necessario confrontare di nuovo queste parole con tutte quelle precedenti? (No...) Hai qualche idea di come si potrebbero aggiornare queste due variabili considerando solo il loro valore e quello della nuova parola inserita?

Il [tt]goto[/tt] è mal visto perché tende a rendere il codice più difficile da seguire perché ci sono più modi di arrivare a una specifica riga del codice. Nel tuo caso sarebbe stato tuttavia sufficiente fare uso della parola chiave [tt]break[/tt] che permette di uscire da un ciclo.

Quinzio
"Yuyu_13":

@ quinzio
Sono uno studente di matematica, invece, il libro è Programmazione in C, Kim N. King.
Per quanto riguarda l'uso del goto, hai usato le stesse parole del mio prof., perché non va bene usarlo ?
Il strcmp non confronta le lunghezze delle parole? Sul mio libro ci sta scritto che serve per confrontare le lunghezze delle parole mediante gli operatori di $<, >....$?


Il libro che hai non lo conosco ma non ho dubbi che sia di alto livello.
Veniamo al GOTO.
La questione e' una di quelle capaci di far litigare anche programmatori che per 8 ore stanno mansueti davanti al monitor senza dire una parola.
Non e' facile spiegare il perche' usare il GOTO e' male e su internet ci sono decine di discussioni al merito. Basta cercare su google "why goto is evil in C".
Ti do la mia spiegazione: evitare di usare il GOTO fa si che i programmi assumano una struttura gerarchica. Ossia ci sono funzioni che chiamano altre funzioni, che chiamano altre funzioni, ecc. E in ogni funzione ci possono essere cicli di esecuzione che contengono annidati altri cicli, e cosi' via.
Per ciclo di esecuzione intendo il "for loop", ovvero un gruppo di istruzioni eseguito diverse volte.
Il punto chiave e' che non si esce a un ciclo se il ciclo non e' terminato.
Ovvero non si salta da un ciclo all'altro.
Il GOTO rende possibile il salto da un punto all'altro in modo indiscriminato, anche se il ciclo non e' terminato si puo' saltare fuori anche da diversi cicli annidati. Questo rompe la struttura gerarchica di un programma e rende molto piu' difficile la comprensione di un programma da parte dello sviluppatore.
E' anche vero che ogni linguaggio di programmazione contiene dei GOTO camuffati e dissimulati sotto altro nome.
Anche la "if else" in realta' non e' altro che un GOTO, per non parlare poi dell'istruzione "break" e continue.
Pero' queste istruzioni non rompono la gerarchia del codice e non effettuano dei salti fuori dal blocco di istruzioni dove sono collocati.

Per te che sei agli inizi, la morale della storia e' questa: non usare MAI il GOTO, MAI e poi MAI. E' fuorilegge.

La strcmp.
La strcmp significa STRing CoMPare, ossia confronta due stringhe.
Ossia la funzione prende due stringhe, dove ogni stringa e' una sequenza di bytes.
La stringa finisce al primo 0x00 che si incontra. Attenzione, non il numero '0' (0x30 in ASCII), ma 0x00 scritto nel byte.
Se le due stringhe sono uguali (fino al primo 0x00 compreso) allora la strcmp restituisce 0, altrimenti restituisce un numero diverso da zero.
Ora, vedi subito che la strcmp non e' utile per avere informazioni sulla lunghezza di una stringa.
La funzione che ti serve e' la strlen, ovvero string length. :smt023

apatriarca
@Quinzio: In realtà non ha bisogno di calcolare la lunghezza della stringa perché è il valore restituito dalla funzione [tt]read_line[/tt]. Continuo comunque a trovare il testo ambiguo sul significato di stringa più grande e più piccola. L'ordine fornito dalla lunghezza della parola è comunque solo parziale e sarebbe a quel punto necessario decidere quale tra le parole della stessa lunghezza la funzione dovrebbe restituire (la prima? una qualsiasi? La più piccola lessicograficamente?).

Sia io che @Yuyu_13 abbiamo usato [tt]'\0'[/tt] e non [tt]'0'[/tt]. Il primo è effettivamente un carattere con valore 0, mentre il secondo è effettivamente il valore corrispondente al carattere zero. L'uso di [tt]'\0'[/tt] rispetto a [tt]0x00[/tt] o [tt]0[/tt] è consigliato perché aiuta il lettore e alcuni tool a comprendere che stai cercando il terminatore della stringa. Per il resto è uguale.

Per quanto riguarda il GOTO vorrei solo aggiungere che rende a volte difficile comprendere il valore di alcune variabili locali al blocco all'interno del quale stai saltando. Non mi risulta ci siano regole specifiche a riguardo nello standard e quindi il comportamento potrebbe essere diverso tra un compilatore e un altro o anche tra diverse opzioni dello stesso compilatore. In effetti più il linguaggio è complicato, più l'uso di qualcosa come il goto diventa problematico. Ma a livello di assembly quasi tutto è implementato usando salti tipo il goto...

Quinzio
@apatriarca

Non ho tenuto conto del valore di ritorno della read_line. Ok

Non sono sicuro che un 'beginner' abbia chiaro al 100% la differenza tra 0x00, 0, "0", '0', "\0", '\0' e si potrebbe continuare.
Il mio era solo un primo tentativo di far capire che quando uno crede di aver capito in realta' non ha capito. :-)

L'assembly e' complicato da leggere, ma almeno il comportamento e' determinato.
Tant'e' vero che in assembly tutto e' un goto. Se poi a un certo punto qualcuno si e' preoccupato di aver salvato l'indirizzo di ritorno sullo stack, allora lo si va a leggere e si fa un altro goto (leggasi: un return).
Invece il sorgente C e' relativamente piu' facile da leggere, ma poi deve passare dentro a quella macina che il compilatore e allora addio alla certezza di essere sempre sicuri del risultato che si vuole.

vict85
[strike]C'è un problema nella tua [inline]read_line[/inline]: quando scrivi una riga più lunga di 20 caratteri, tu stoppi la lettura, quindi il resto della parola viene considerata come un parola nuova anche se non è quello che ci si aspetta. Inoltre parli di parola, ma nel tuo codice una riga come:
una riga con più parole

viene letta come due parole: [inline]una riga con più p[/inline] e [inline]arole[/inline] (o qualcosa del genere, ho calcolato a mano il punto in cui si spezza).[/strike]

Inoltre, e questo è un errore grave, passi alla funzione read_line il valore [inline]MAX_WORD[/inline], anche se puoi al massimo scrivere [inline]MAX_WORD-1[/inline] caratteri. Insomma [inline]str[MAX_WORD][/inline] è fuori dalla stringa.

P.S.: errore mio: ho aggiunto mentalmente un break dove non c'era. Ma il secondo errore c'è ed è qualcosa su cui devi fare attenzione.

Yuyu_13
Ringrazio a tutti per i consigli che mi avete dato !!
Sono tornato indietro, sto rileggendo dai puntatori, ho un pò di confusione nella testa, voglio assimilare bene i concetti e poi proseguire.

Per il momento grazie a tutti.

Saluti

apatriarca
Sinceramente come primo tentativo non era così male. Se fossi in te cercherei di fare degli esercizi piuttosto che soffermarmi troppo sulla teoria e i concetti. Lo dico sia perché ci sono un sacco di risorse di dubbia qualità in giro (e non so di che qualità sia il tuo libro o il materiale del corso), sia perché alcuni concetti li si impara meglio vedendoli usati in pratica.

Per esempio puoi fare degli esercizi su array e stringhe per ripassare alcune delle tue carenze in questo esercizio. Alcuni esercizi potrebbero essere i seguenti:
1. Scrivi un programma che legge una parola da standard input e stampa la parola al contrario. Una parola è definita come una sequenza di caratteri alfabetici senza spazi o altri simboli all'interno. Devi insomma ignorare gli spazi e ogni carattere che non sia alfabetico prima della parola e ti devi fermare al primo carattere non alfabetico. Per esempio se uno inserisce " ciao mondo" devi stampare "oaic" e ignorare il resto.
2. Scrivi un programma che legge una sequenza di numeri positivi (zero compreso) da standard input e si ferma al primo numero negativo. Deve stampare il massimo, il minimo e la media. Il numero negativo non va considerato. In una prima versione puoi assumere che esiste un numero massimo di valori che puoi inserire (per esempio 1000), ma poi cerca di modificare il codice in modo da eliminare questo limite e quindi senza memorizzare tutti i numeri.
3. Scrivi un programma che riceve in input due numeri naturali con un numero di cifre inferiore a 100. Il numero deve stampare la loro somma. Nota che non puoi fare uso di variabili intere perché nessuna di quelle disponibili può rappresentare numeri così grandi...
4. Scrivi un programma che legge due stringhe da standard input su due righe separate e stampa "SI" se la seconda è una sottostringa della prima (cioè compare all'interno della prima) oppure "NO" in caso contrario. Per esempio se hai "abracadabra" e "ra" allora devi restituire "SI" se invece la seconda stringa fosse stata "raz" allora avresti dovuto stampare "NO".

E se hai bisogno di una mano, per esempio vuoi dei suggerimenti o vuoi controllare la tua risposta, chiedi pure.

vict85
Secondo me stai cercando di ottimizzare un po' troppo. Lo si può immaginare dall'uso del goto invece del break e dal fatto che non usi [inline]fgets[/inline] o [inline]gets_s[/inline](se non addirittura [inline]getline[/inline]) per la lettura delle stringhe[nota]Seppur sia positivo che non usi [inline]gets[/inline] dato che è una funzione molto pericolosa e deprecata.[/nota]. Anche l'uso di [inline]for(;;)[/inline] può essere visto in questo modo. In realtà non sono affatto sicuro che i tuoi tentativi abbiano davvero un effetto sulle performance generali, e comunque sono trascurabili rispetto ad una scelta appropriata dell'algoritmo.

Quindi prima pensa all'algoritmo e poi eventualmente ottimizza. Per ora ti direi comunque di ignorare le performance (se non quando scegli l'algoritmo).

Rispondi
Per rispondere a questa discussione devi prima effettuare il login.