[C] Programma per contare occorrenze di parole.
Ciao a tutti, sto incontrando non poche difficoltà a scrivere un programma in C che letto un file, crei un istogramma per le frequenze delle 10 parole che si ripetono più spesso nel testo.
So che il file di input non contiene più di 1000 caratteri per riga e ogni parola ( intesa come sottosequenza massimale di caratteri alfabetici (cioè per i quali isalpha() restituisce un valore vero)) è lunga al più venti. Inoltre nel testo non compaiono più di 10000 parole distinte.
Io ho pensato di leggere il file riga per riga in questo modo:
#include
#include
int main() {
FILE *fp;
char buf[1000];
char *riga;
/* apro il file */
fp=fopen("input.txt", "r");
if( fp==NULL ) {
printf("Errore in apertura del file");
}
/* leggo e stampo righe del file */
while(1) {
riga=fgets(buf, 1000, fp);
if( riga==NULL )
break;
printf("%s", buf);
}
/* chiudo il file */
fclose(fp);
system("pause");
return 0;
}
Non riesco però a organizzare le idee per individuare le parole e contare la loro frequenza. Fatto questo passaggio che è il più corposo del programma, pensavo di riempire una struttura con le dieci parole più frequenti e le rispettive frequenze e a quel punto disegnare su un file di output l'istogramma.
C'è qualcuno in grado di darmi una mano ?
So che il file di input non contiene più di 1000 caratteri per riga e ogni parola ( intesa come sottosequenza massimale di caratteri alfabetici (cioè per i quali isalpha() restituisce un valore vero)) è lunga al più venti. Inoltre nel testo non compaiono più di 10000 parole distinte.
Io ho pensato di leggere il file riga per riga in questo modo:
#include
#include
int main() {
FILE *fp;
char buf[1000];
char *riga;
/* apro il file */
fp=fopen("input.txt", "r");
if( fp==NULL ) {
printf("Errore in apertura del file");
}
/* leggo e stampo righe del file */
while(1) {
riga=fgets(buf, 1000, fp);
if( riga==NULL )
break;
printf("%s", buf);
}
/* chiudo il file */
fclose(fp);
system("pause");
return 0;
}
Non riesco però a organizzare le idee per individuare le parole e contare la loro frequenza. Fatto questo passaggio che è il più corposo del programma, pensavo di riempire una struttura con le dieci parole più frequenti e le rispettive frequenze e a quel punto disegnare su un file di output l'istogramma.
C'è qualcuno in grado di darmi una mano ?
Risposte
Dovresti prima definire quali sono i separatori di parole. Puo' essere lo spazio, ma anche la virgola o il punto o altri caratteri.
Considerato che il testo ti dice che ci sono al massimo 10.000 parole distinte, memorizzerei su una matrice ogni singola parola nuova che incontri e la relativa frequenza. Puoi anche usare due vettori che chiamerei "Parola" e "Freq".
Alla fine della elaborazione ordinerai il vettore delle frequenze, in modo tale da avere le frequenze maggiori ordinate.
Considerato che il testo ti dice che ci sono al massimo 10.000 parole distinte, memorizzerei su una matrice ogni singola parola nuova che incontri e la relativa frequenza. Puoi anche usare due vettori che chiamerei "Parola" e "Freq".
Alla fine della elaborazione ordinerai il vettore delle frequenze, in modo tale da avere le frequenze maggiori ordinate.
Siccome l'esercizio definisce una parola come "una sottosequenza massimale di caratteri alfabetici (cioè per i quali isalpha() restituisce un valore vero)", qualsiasi carattere che non rispetti la condizione è un separatore.
Per individuare le parole è sufficiente leggere una riga per volta ed estrarre tutte le sottosequenze della riga di caratteri alfabetici precedute e seguite da caratteri non alfabetici. Per ogni parola devi quindi verificare se è già stata trovata precedentemente (facendo una ricerca nella struttura dati che usi per memorizzare parole e relative frequenze). Se la parola è già stata trovata dovrai quindi incrementare un contatore. In caso contrario dovrai inserire la nuova parola nella struttura dati. La struttura dati più semplice è un array di parole e frequenze come già consigliato da Umby.
Per individuare le parole è sufficiente leggere una riga per volta ed estrarre tutte le sottosequenze della riga di caratteri alfabetici precedute e seguite da caratteri non alfabetici. Per ogni parola devi quindi verificare se è già stata trovata precedentemente (facendo una ricerca nella struttura dati che usi per memorizzare parole e relative frequenze). Se la parola è già stata trovata dovrai quindi incrementare un contatore. In caso contrario dovrai inserire la nuova parola nella struttura dati. La struttura dati più semplice è un array di parole e frequenze come già consigliato da Umby.
Vi ringrazio per le risopste.
Dunque io avrei definito una struttura per il conteggio in questo modo:
strct frequenza{char par[20], int num};
Pensavo poi di utilizzare un array di questo tipo
struct frequenza frequenzatot[10000];
da riempire con le parole che a mano a mano incontro, anche se forse può essere utile prima leggere le parole di una riga e salvarle in un vettore di stringhe e solamente in seguito a ciò riempire la struttura frequenza utilizzando il vettore.
Inoltre, per leggere le parole di una riga, immagino debba impostare un ciclo; avrei pensato di inserirlo nel ciclo while che legge il testo riga per riga che ho riportato nel codice scritto del primo post.
Con la funzione strlen posso trovare la lunghezza di una singola riga, ma poi non ho idea di come impostare il ciclo che legga la riga parola per parola e che riempia.
In sostanza . . . non so come fare questo passaggio:
Dunque io avrei definito una struttura per il conteggio in questo modo:
strct frequenza{char par[20], int num};
Pensavo poi di utilizzare un array di questo tipo
struct frequenza frequenzatot[10000];
da riempire con le parole che a mano a mano incontro, anche se forse può essere utile prima leggere le parole di una riga e salvarle in un vettore di stringhe e solamente in seguito a ciò riempire la struttura frequenza utilizzando il vettore.
Inoltre, per leggere le parole di una riga, immagino debba impostare un ciclo; avrei pensato di inserirlo nel ciclo while che legge il testo riga per riga che ho riportato nel codice scritto del primo post.
Con la funzione strlen posso trovare la lunghezza di una singola riga, ma poi non ho idea di come impostare il ciclo che legga la riga parola per parola e che riempia.
In sostanza . . . non so come fare questo passaggio:

Per individuare le parole è sufficiente leggere una riga per volta ed estrarre tutte le sottosequenze della riga di caratteri alfabetici precedute e seguite da caratteri non alfabetici.
Dovresti fare qualcosa come
Il codice non l'ho testato ma credo che la logica sia abbastanza chiara.
char parola[21]; int i = 0; int j; while (riga[i] != '\0') { /* ignora caratteri non alfabetici. */ while (riga[i] != '\0' && !isalpha(riga[i])) { ++i; } /* leggi parola */ for (j = 0; riga[i] != '\0' && isalpha(riga[i]); ++i, ++j) { parola[j] = riga[i]; } parola[j] = '\0'; /* se è stata letta una parola inseriscila nella struttura dati... */ if (j != 0) { /* ... */ } }
Il codice non l'ho testato ma credo che la logica sia abbastanza chiara.
Ti ringrazio davvero molto per l'aiuto che mi stai dando: Preziosissimo !
Ho lavorato sul codice che mi hai gentilmente indicato e ho aggiunto un passaggio per azzarare il contatore "i" perchè altrimenti dopo la prima riga riprende a cercare parola dal carattere i-esimo della riga successiva e non dall'inizio. testando il programma, in questo momento me lo suddivide esattamente in parole.
Ora sto cercando di riempire la struttura delle frequenze. In particolare sto pensando ad un modo per scorrere le parole e aggiornarne l'occorrenza.
Spero di farcela . . in caso contrario cerco di strapparti ancora qualche consiglio
Intanto grazie ancora per i suggerimenti !
Ho lavorato sul codice che mi hai gentilmente indicato e ho aggiunto un passaggio per azzarare il contatore "i" perchè altrimenti dopo la prima riga riprende a cercare parola dal carattere i-esimo della riga successiva e non dall'inizio. testando il programma, in questo momento me lo suddivide esattamente in parole.
Ora sto cercando di riempire la struttura delle frequenze. In particolare sto pensando ad un modo per scorrere le parole e aggiornarne l'occorrenza.
Spero di farcela . . in caso contrario cerco di strapparti ancora qualche consiglio

Intanto grazie ancora per i suggerimenti !
Ahimè non riesco a riempire decentemente la struttura dati . . provo a richiederti un consiglio:
dopo aver creato questa struttura:
strct frequenza{char par[20], int num};
struct frequenza frequenzatot[10000];
e aver posto frequenzatot[j].num=0 e frequenzatot[j].par =" "
sto cercando di riempirla con le parole.
Giunto al passaggio che mi hai indicato in fondo al tuo ultimo post
/* se è stata letta una parola inseriscila nella struttura dati... */
if (j != 0) {
/* ... */
}
Avevo pensato di agire in questo modo:
1) scorro frequenzatot con un ciclo for
2) Se la parola trovata è uguale ad una già presente nella struttura, incremento il corrispondente contatore.
3) Altrimenti scorro nuovamente la struttura e riempio con la parola il primo spazio vuoto che incontro.
Detto così a parole mi sembra poter funzionare, ma il codice che ho scritto non fa quello che deve.
Mi sapresti dare qualche consiglio a riguardo ? Se necessario ti trascrivo il mio codice.
dopo aver creato questa struttura:
strct frequenza{char par[20], int num};
struct frequenza frequenzatot[10000];
e aver posto frequenzatot[j].num=0 e frequenzatot[j].par =" "
sto cercando di riempirla con le parole.
Giunto al passaggio che mi hai indicato in fondo al tuo ultimo post
/* se è stata letta una parola inseriscila nella struttura dati... */
if (j != 0) {
/* ... */
}
Avevo pensato di agire in questo modo:
1) scorro frequenzatot con un ciclo for
2) Se la parola trovata è uguale ad una già presente nella struttura, incremento il corrispondente contatore.
3) Altrimenti scorro nuovamente la struttura e riempio con la parola il primo spazio vuoto che incontro.
Detto così a parole mi sembra poter funzionare, ma il codice che ho scritto non fa quello che deve.
Mi sapresti dare qualche consiglio a riguardo ? Se necessario ti trascrivo il mio codice.
"Relegal":
Spero di farcela . . in caso contrario cerco di strapparti ancora qualche consiglio![]()
Direi che è la parte piu semplice da fare.
Scruti la tabella sequenzialmente, fin quando non trovi una posizione libera. In questo caso aggiungi la nuova parola in tabella posizionando l'ennesimo elemento della frequenza a 1.
Se invece durante la ricerca, trovi già la tua parola, non devi far altro che incrementare di una unita la frequenza.
OOPS
ci siamo accavallati....
ci siamo accavallati....
Direi che è la parte piu semplice da fare.
Pensa che pochi secondi prima ho scritto che ho difficoltà!


Per fortuna che non devo fare il programmatore !

"Relegal":
Direi che è la parte piu semplice da fare.
Pensa che pochi secondi prima ho scritto che ho difficoltà!![]()
![]()
Per fortuna che non devo fare il programmatore !
vabbè, pero' il tuo ragionamento è valido,
quindi penso che ci sia qualche errorino
se metti il codice magari ....
"Umby":
se metti il codice magari ....
Eccolo: (Oddio, vedo che è abbastanza scomodo da leggere ! Non so bene come fare a renderlo più leggibile.)
...
if(j!=0){
for(k=0;k<10000;k++){
if(strcmp(parola,frequenzatot[j].par)==0){
frequenzatot[j].num++
} // if
else{
for(k=0;k<10000;k++){
if(strcmp(frequenzatot[k].par," ")==0) {
strcpy(frequenzatot[k].par,parola);
} // if
break;
} // for
} // else
} // if
Prima di tutto per inserire il codice si usa il tag [ code ] (senza spazi tra la parola e le parentesi quadre). Non è prima di tutto necessario leggere due volte il tuo array e neanche inizializzarlo con {"", 0}. Conviene definire una variabile num_parole che contiene il numero corrente delle parole nel tuo array. Una volta trovata una parola non è inoltre necessario verificare le parole successive.
Ma prima di mostrare il codice ti mostro che cosa hai sbagliato:
Nel tuo codice verifichi che la parola corrente sia uguale a quella cercata e se non è uguale trova il primo spazio libero e ci scrive la nuova parola, per poi passare alla parola che segue la parola appena inserita (che sarà quindi vuota e causerà nuovamente l'inserimento della parola nella tua struttura dati...). Il ciclo andava inserito solo dopo che tutte le parole erano state visitate e non nell'else. Ci sono poi alcuni problemi sugli indici (non sempre hai usato gli indici in modo corretto). Il codice corretto, usando i miglioramenti di cui ho parlato all'inizio è
Ma prima di mostrare il codice ti mostro che cosa hai sbagliato:
if(j!=0) { for(k=0;k<10000;k++) { if(strcmp(parola,frequenzatot[j].par)==0) { frequenzatot[j].num++ } else{ for(k=0;k<10000;k++) { if(strcmp(frequenzatot[k].par," ")==0) { strcpy(frequenzatot[k].par,parola); } break; } } } }
Nel tuo codice verifichi che la parola corrente sia uguale a quella cercata e se non è uguale trova il primo spazio libero e ci scrive la nuova parola, per poi passare alla parola che segue la parola appena inserita (che sarà quindi vuota e causerà nuovamente l'inserimento della parola nella tua struttura dati...). Il ciclo andava inserito solo dopo che tutte le parole erano state visitate e non nell'else. Ci sono poi alcuni problemi sugli indici (non sempre hai usato gli indici in modo corretto). Il codice corretto, usando i miglioramenti di cui ho parlato all'inizio è
if(j!=0) { int trovata = 0; /* serve per determinare se la parola è stata trovata nell'array */ for(k = 0; k < num_parole; ++k) { if (strcmp(parola, frequenzatot[k].par) == 0) { ++frequenzatot[k].num; trovata = 1; break; } } if (!trovata) { strcpy(frequenzatot[num_parole].par, parola); frequenzatot[num_parole].num = 1; ++num_parole; } }
Che dire . . chiarissimo !
Ho capito dove sbagliavo e ho seguito il codice che mi hai mostrato.
Ho sistemato l'intero programma e ne ho verificato il funzionamento. Ora esco per una visita, in serata mi metto sotto per creare la struttura contenente le dieci parole più frequenti e per disegnare l'istogramma.
Ti ringrazio nuovamente per l'aiuto, davvero gentilissimo. Buona serata.
Ho capito dove sbagliavo e ho seguito il codice che mi hai mostrato.
Ho sistemato l'intero programma e ne ho verificato il funzionamento. Ora esco per una visita, in serata mi metto sotto per creare la struttura contenente le dieci parole più frequenti e per disegnare l'istogramma.
Ti ringrazio nuovamente per l'aiuto, davvero gentilissimo. Buona serata.
Mi sento di consigliarti di leggere "Il linguaggio C" di K&R (i creatori del C). Non mi ricordo in quale capitolo c'è un esempio su un programma molto simile al tuo. Credo che una lettura attenta possa colmare molte tue lacune.
Ok, ti ringrazio per la referenza. Cercherò di procurarmi il libro e gli darò una letta !
Ciao a tutti, sono ancora io: il "programmatore"
Ho quasi terminato il programma nel senso che una volta organizzata la struttura contenente le parole presenti nel testo e le relative frequenze, ho scritto il codice per trovare all'interno della struttura le dieci parole più frequenti e per salvarle in una struttura. Dopodichè ho scritto la parte di codice per creare un file di output che rappresentasse l'istogramma e su questo passaggio non dovrei aver riscontrato problemi.
Al contrario non riesco ad organizzare al meglio la struttura delle dieci parole più frequenti. In particolare, nei dieci slot a disposizione riesco sì a porre le dieci parole con frequenza maggiore ma non in ordine decrescente bensì nell'ordine con cui si presentano nel testo di input.
Riporto qui di seguito il codice :
Dopo averlo testato ed aver notato il malfunzionamento, ho riletto il codice e penso di aver individuato il problema: le istruzioni per riempire la struttura isto sono poste all'interno del ciclo che scorre la struttura frequenze la quale contiene le parole presenti nel testo; di conseguenza al primo passo del ciclo, ad esempio, la parola con frequenza massima sarà ovviamente la prima incontrata e da qui l'errore nel riempimento.
Ho provato quindi a spostare le istruzioni ma non sono riuscito a ottenere un funzionamento corretto. Mi sono perso qualche altro errore ?

Ho quasi terminato il programma nel senso che una volta organizzata la struttura contenente le parole presenti nel testo e le relative frequenze, ho scritto il codice per trovare all'interno della struttura le dieci parole più frequenti e per salvarle in una struttura. Dopodichè ho scritto la parte di codice per creare un file di output che rappresentasse l'istogramma e su questo passaggio non dovrei aver riscontrato problemi.
Al contrario non riesco ad organizzare al meglio la struttura delle dieci parole più frequenti. In particolare, nei dieci slot a disposizione riesco sì a porre le dieci parole con frequenza maggiore ma non in ordine decrescente bensì nell'ordine con cui si presentano nel testo di input.
Riporto qui di seguito il codice :
int j=0; int max=0; int indicemax=0; for(k=0;k<10;k++){ // trova la frequenza massima if(frequenze[k].num>=max){ max=frequenze[k].num; indicemax=k; } // if isto[j].num=max; // riempio la struttura isto strcpy(isto[j].par,frequenze[indicemax].par); frequenze[indicemax].num=0; max=0; // azzero il contatore per la ricerca del massimo j++; // incremento l'indicatore di posizione nella struttura isto } // for
Dopo averlo testato ed aver notato il malfunzionamento, ho riletto il codice e penso di aver individuato il problema: le istruzioni per riempire la struttura isto sono poste all'interno del ciclo che scorre la struttura frequenze la quale contiene le parole presenti nel testo; di conseguenza al primo passo del ciclo, ad esempio, la parola con frequenza massima sarà ovviamente la prima incontrata e da qui l'errore nel riempimento.
Ho provato quindi a spostare le istruzioni ma non sono riuscito a ottenere un funzionamento corretto. Mi sono perso qualche altro errore ?
Ritengo che hai scelto una strada un po tortuosa.
Al tuo posto avrei ordinato l'intero vettore (tanto lavora il pc... ), ovviamente poi prendevo i 10 valori iniziali.
Al tuo posto avrei ordinato l'intero vettore (tanto lavora il pc... ), ovviamente poi prendevo i 10 valori iniziali.
"Umby":
Ritengo che hai scelto una strada un po tortuosa.
Al tuo posto avrei ordinato l'intero vettore (tanto lavora il pc... ), ovviamente poi prendevo i 10 valori iniziali.
Ok, ora provo questa strada... vediamo cosa riesco a far uscire !
"Relegal":
le istruzioni per riempire la struttura isto sono poste all'interno del ciclo che scorre la struttura frequenze la quale contiene le parole presenti nel testo
questo è un errore logico.
Non puoi riempire il grafico "durante", ma solo DOPO che hai stabilito quali sono i 10 valori più alti.
Di questo errore me ne sono accorto ma non sono riuscito a risolverlo. Io pensavo di trovare la parola con la frequenza massima, salvarla insieme alla sua frequenza nel primo dei dieci slot di isto e azzerare infine la sua frequenza nella struttura originale così da poter il tutto le dieci volte necessarie e riempire la struttura isto.
Con il mio codice mi esce un risultato giusto a metà . . perlomeno è già qualcosa !
Con il mio codice mi esce un risultato giusto a metà . . perlomeno è già qualcosa !