PDA

Visualizza la versione completa : [C] Lettura e scrittura di parole codificate da file


mbistato
06-09-2014, 17:00
Ciao ragazzi,

sto scrivendo un programma in C che legga delle stringhe da file. Il codice che ho scritto è quello presente qui sotto. In pratica, tale programma legge il il file "input.txt" (passato come parametro in argv[1]) contenente le seguenti stringhe:

9AJ4D
456F
FFD34

e memorizza ogni stringa nel vettore di caratteri "parola_codificata[31]"

Il problema è che non appena viene eseguito il for(i=0; i<MAX_R; i++), indipendentemente quale sia la stringa (la 1° la 2° o la 3°) letta, gli elementi della posizione 2 e 3 del vettore parola_codificata si trasformano magicamente in N ovvero ottengo sempre:

parola_codificata[1] = N e parola_codificata[2] = N


Sapete dirmi perchè questo capita??




#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>


#define MAX_R 30




int main(int argc, char *argv[])
{
FILE *ptrcode;
FILE *ptrdec;
char alf_cod32 [35] = {'0','1','2','3','4','5','6','7','8','9','A','B',' C','D','E','F','G','H','J','K','L','M','N','P','Q' ,'R','S','T','U','V','W','X','Y','Z'};
char carattere, parola_codificata[31], chiave[31];
int i, j, p;






if(argc!=3)
{
printf("Errore nell'immissione degli argomenti!!\n\n");
exit(EXIT_FAILURE);
}
else if((ptrcode=fopen(argv[1], "r"))==NULL)
{
printf("impossibile aprire il file %s\n\n", argv[1]);
exit(EXIT_FAILURE);
}
else if((ptrdec=fopen(argv[2], "w"))==NULL)
{
printf("impossibile scrivere sul file %s\n\n", argv[2]);
exit(EXIT_FAILURE);
}


else{


/* inizializza tutta la matrice col carattere di fine stringa */


for (i = 0; i < MAX_R; i++)
parola_codificata[i] = '\0';


while (!feof (ptrcode))
{
fscanf (ptrcode, "%s", parola_codificata);


/*printf("\n%s", parola_codificata);


printf("\n carattere 2: %c", parola_codificata[1]);
printf("\n carattere 3: %c", parola_codificata[2]);*/


for(i=0; i<MAX_R; i++)
{
//printf("\n%c", parola_codificata[i]);
for(j=0; j<strlen(alf_cod32); j++)
{
printf("\n carattere 2: %c", parola_codificata[2]);
printf("\n carattere 3: %c", parola_codificata[3]);

if(parola_codificata[i] == alf_cod32[j])
{
printf("\n carattere: %c indice %d indice alfabeto: %d", parola_codificata[i], i, j);
fprintf(ptrdec, "%d", j);


}
p = parola_codificata[i] - '0';
//controllo se il carattere corrente è un numero
if (isalpha(p)==0){
chiave[j] = 'N';
}
else{
chiave[j] = 'L';
}
}
}
fprintf(ptrdec, "\n");


}
fclose(ptrcode);
fclose(ptrdec);
}
}

Xaratroom
07-09-2014, 13:01
Scusami ma non riesco proprio a capire quale sia il tuo problema: La lettura da file va quasi bene (feof non si usa in quel modo perché risulta diversa da zero solo dopo una lettura fallita), ma non è chiaro cosa dovrebbe fare il tuo algoritmo.

Per la lettura da file, linea per linea, in questo caso è meglio utilizzare la fgets:

char parola_codificata[N];
//....
while (fgets(parola_codificata, N, fp) != NULL) {
//....
}

Ricorda che N deve contenere la parola, lo \n e lo \000. Non serve inizializzare a \000 il vettore parola_codificata.

Per rimuovere il carattere di \n finale puoi usare la strchr (oppure eliminarlo manualmente, oppure non eliminarlo proprio):


char *nlp;
nlp = strchr(parola_codificata, '\n');
if (nlp != NULL) *nlp = '\000';


Se il tuo codice deve "interpretare" la parola, andando a riconoscere se ogni singolo carattere è una lettera od un numero, puoi semplicemente fare così:


//ch è il carattere corrente
if((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
//è una lettera
} else if (ch >= '0' && ch <= '9') {
//è un numero
} else {
//non è una lettera e non è un numero
}

O, in alternativa, usare le funzioni della libreria standard.

Per scorrere una stringa ti conviene utilizzare un while, e fermarti quando raggiungi uno dei caratteri '\n' o '\000', in questo modo:


char* ch = parola_codificata;
while(*ch != '\n' && *ch != '\000') {
//...
ch++;
}

mbistato
07-09-2014, 17:39
Ciao e grazie per la risposta.
Visto che non avevo alternativa, ho seguito il tuo consiglio ma il programma che viene fuori va in crash quando tento di eseguirlo:



#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>


#define MAX_R 30




int main(int argc, char *argv[])
{
FILE *ptrcode;
FILE *ptrdec;
char alf_cod32 [35] = {'0','1','2','3','4','5','6','7','8','9','A','B',' C','D','E','F','G','H','J','K','L','M','N','P','Q' ,'R','S','T','U','V','W','X','Y','Z'};
char parola_codificata[31], chiave[31];
char* carattere;
char* alf;
int i, j, pos = 0;






if(argc!=3)
{
printf("Errore nell'immissione degli argomenti!!\n\n");
exit(EXIT_FAILURE);
}
else if((ptrcode=fopen(argv[1], "r"))==NULL)
{
printf("impossibile aprire il file %s\n\n", argv[1]);
exit(EXIT_FAILURE);
}
else if((ptrdec=fopen(argv[2], "w"))==NULL)
{
printf("impossibile scrivere sul file %s\n\n", argv[2]);
exit(EXIT_FAILURE);
}


else{


while (fgets(parola_codificata, MAX_R, ptrcode) != NULL)
{
carattere = parola_codificata;
alf = alf_cod32;
while(*carattere != '\n' && *carattere != '\000')
{
while(*alf != '\n' && *alf != '\000')
{


if(*carattere == *alf)
{
//memorizzo la posizione della lettera nel vettore alf_cod32
fprintf(ptrdec, "%d", pos);


}
//controllo se il carattere corrente è un numero


//ch è il carattere corrente
if((*carattere >= 'a' && *carattere <= 'z') || (*carattere >= 'A' && *carattere <= 'Z'))
{
//è una lettera
chiave[j] = 'L';
}
else if (*carattere >= '0' && *carattere <= '9')
{
//è un numero
chiave[j] = 'N';
}
else{
printf("Errore lettura nel file");
}
pos++;
alf++;
}
carattere++;
}
fprintf(ptrdec, "\n");


}
fclose(ptrcode);
fclose(ptrdec);
}
}

M.A.W. 1968
07-09-2014, 17:58
Appare piuttosto ovvio che il programma vada in crash, in quanto nel loop più interno stai cercando di scansire (tramite un puntatore ausiliario alf) un banale array di char come se fosse una stringa ASCII-Z ossia null-terminated, cercando un terminatore nullo che non è incluso nell'array. Inoltre, già che ci siamo, i dieci simboli numerici associati alle ventisei lettere dell'alfabeto anglosassone danno un totale di 36 elementi, non già 35 (e un compilatore minimamente decente dovrebbe emettere almeno un warning "too many initializers").

Più grave, temo che codesto programmino soffra di un enorme errore progettuale: perché stai facendo un farraginoso reverse lookup, ossia la scansione della lista dei simboli per ogni carattere della stringa di input? Vai a scivolare su una complessità addirittura quadratica, quando l'ordine di grandezza della stringa è comparabile con la dimensione dell'array dei simboli... in casi del genere si usano LUT, LookUp Tables, che consentono un'indirizzamento immediato in tempo costante applicando una banalissima trasformazione algebrica ad ogni singolo carattere di input (tipicamente una sottrazione...).

mbistato
07-09-2014, 22:01
Ciao M.A.W. 1968 il programma che devo fare è abbastanza semplice. QUesto è il testo del problema:

Scrivere un programma in C che esegua la decodifica di una serie di parole codificate memorizzate in un file di testo in formato ASCII. La codifica in questione si chiama COD32 e utilizza come simboli le cifre da 0 a 9 e le lettere dell’alfabeto da A a Z (escluse I e O): 0123456789ABCDEFGHJKLMNPQRSTUVWXYZ. La decodifica avviene trasformando ciascuno dei simboli preso singolarmente da COD32 a decimale. In particolare ogni simbolo viene tradotto nel numero decimale associato alla sua posizione nella stringa riportata in precedenza di simboli usati dalla codifica COD32 (il primo simbolo ha posizione pari a 0). Ad esempio 2 viene tradotto nel numero 2, A viene tradotto nel numero 10, e Z nel numero 33. Un esempio di decodifica completa è: (9AJ4D)COD32 -> (91018413)DEC

Le stringhe da tradurre sono contenuto nel file il cui nome viene passato ad argv[1] e che contiene le seguenti stringhe da codificare:

9AJ4D
456F
FFD34

Ogni stringa deve essere codificata e scritta su un file il cui nome è passato ad argv[2].
Cosa mi consiglieresti?

Il terminatore di fine stringa l'ho inserito nel vettore alf_cod32 ma il numero di elementi sono 35. Perchè dici che sono 36??

M.A.W. 1968
07-09-2014, 22:37
Non era chiaro che mancassero i due simboli 'I' e 'O', questo porta da 36 a 34 il totale dei simboli. In ogni caso, l'array non deve essere null-terminated, e a rigore non deve essere minimamente usato!

Come già suggerito sopra, per ogni carattere della stringa di input, devi distinguere tre casi:
1) E' una lettera? Dall'esempio pare di capire che sono ammesse solo lettere maiuscole, in ogni caso si può garantire tale risultato con toupper(). Comunque sia, la sua codifica si può ottenere in modo algebrico, con una formuletta non lineare (occorre escludere i simboli 'I' ed 'O'), oppure tramite il seguente array di lookup diretto, non inverso come hai fatto tu:
posn[] = {10, 11, 12, 13, 14, 15, 16, 17, 99, 18, 19, 20, 21, 22, 99, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33};
Si notino i valori sentinella, inaccettabili per la codifica, nelle locazioni corrispondenti ad 'I' ed 'O'.
Tale array, posto che sia ch il carattere attualmente in scansione, verrà banalmente indirizzato come
posn[ch - 'A'] per ottenere la codifica desiderata.

2) E' un numero? In tal caso, la codifica si ottiene immediatamente sottraendo la costante '0' al codice ASCII del carattere stesso, come in ch - '0'.

3) In caso diverso, c'è un errore nel file di input e si deve emettere un messaggio di errore, terminando eventualmente il programma.

Tutto molto semplice e lineare.

mbistato
08-09-2014, 09:01
ok ora sei stato + chiaro ma c'è sempre qualcosa che non va perchè il programma mi va in loop infinito. Non capisco perchè cicla all'infinito nonostante la condizione "while(*carattere != '\n' && *carattere != '\0')":dhò:




#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#define MAX_R 30




int main(int argc, char *argv[])
{
FILE *ptrcode;
FILE *ptrdec;
int posn[26] = {10, 11, 12, 13, 14, 15, 16, 17, 99, 18, 19, 20, 21, 22, 99, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33};
char parola_codificata[31], chiave[31];
char* carattere;
int i, j;


if(argc!=3)
{
printf("Errore nell'immissione degli argomenti!!\n\n");
exit(EXIT_FAILURE);
}
else if((ptrcode=fopen(argv[1], "r"))==NULL)
{
printf("impossibile aprire il file %s\n\n", argv[1]);
exit(EXIT_FAILURE);
}
else if((ptrdec=fopen(argv[2], "w"))==NULL)
{
printf("impossibile scrivere sul file %s\n\n", argv[2]);
exit(EXIT_FAILURE);
}


else{


while (fgets(parola_codificata, MAX_R, ptrcode) != NULL)
{
carattere = parola_codificata;
while(*carattere != '\n' && *carattere != '\0')
{
//controllo se il carattere corrente è un numero o una lettera




if((*carattere >= 'a' && *carattere <= 'z') || (*carattere >= 'A' && *carattere <= 'Z'))
{
//è una lettera
fprintf(ptrdec, "%d", posn[*carattere-'A']);




//chiave[j] = 'L';
}
else if (*carattere >= '0' && *carattere <= '9')
{
//è un numero
fprintf(ptrdec, "%d", posn[*carattere-'0']);
//chiave[j] = 'N';
}
else{
printf("Errore lettura nel file");
EXIT_FAILURE;
}
}
carattere++;
}
//fprintf(ptrdec, "\n");


}
fclose(ptrcode);
fclose(ptrdec);
}

M.A.W. 1968
08-09-2014, 14:05
Nel programma sussistono ancora numerosi errori logici e strutturali. Vediamoli analiticamente, senza troppe distinzioni tra errori gravi, lacune progettuali e errate scelte stilistiche.

1) Non c'è alcun bisogno che l'array di decodifica sia dichiarato come int. Si sprecano (almeno) 4 byte per ogni locazione, che invece contiene un valore sicuramente inferiore a 256. Dunque è opportuno farne un array (const) di char e, per chiarezza, collocarne anche la dichiarazione al di fuori del main().

2) Il programma proposto usa troppe variabili non necessarie. In particolare non è minimamente necessario l'uso di un puntatore ausiliario, poco accortamente denominato "character" (due lettere sarebbero già troppe per una variabile del genere, che comunque andrebbe dichiarata entro il loop più interno perché è solo quello il suo scope). Già che ci siamo, la scelta dei nomi delle variabili in generale andrebbe rivista, in particolare il buffer per la riga/parola in input dovrebbe chiamarsi encoding[] o qualcosa del genere, mentre per l'output vanno benissimo plaintext[] o decoded[]. Riguardo alle dimensioni, si può largheggiare e dichiarare ambedue gli array di dimensione 30 o superiore, dal momento che trattasi di un esercizio e non sono richieste particolari protezioni contro l'overflow.
Attenzione, perché proprio all'uso del puntatore superfluo è legata poi la difficoltà di scrittura della condizione di permanenza nel loop più interno. La scansione della linea di input può avvenire entro un costrutto for() con terminazione predeterminata usando la funzione strlen() sul buffer di input stesso, oppure a rottura di codice - esattamente come fatto attualmente con il puntatore ausiliario.

3) La struttura logica delle if() può essere razionalizzata ampiamente. Ripeto, trattasi di un semplice esercizio, e nessun obbligo è dato in ordine alla regola del "single exit point". Approfittiamone per ridurre l'indentazione, aumentare la leggibilità e linearizzare la struttura, diminuendo la complessità ciclomatica.


FILE *infile;
FILE *outfile;
char encoding[LINE_MAX];
char plaintext[LINE_MAX];

if(argc != 3)
{
fprintf(stderr, "Errore nell'immissione degli argomenti!\n\n");
return(EXIT_FAILURE);
}

infile = fopen(argv[1], "r");
if (NULL == infile)
{
fprintf(stderr, "impossibile aprire il file %s\n\n", argv[1]);
return(EXIT_FAILURE);
}

outfile = fopen(argv[2], "w");
if (NULL == outfile)
{
fprintf(stderr, "impossibile scrivere sul file %s\n\n", argv[2]);
return(EXIT_FAILURE);
}



4) Quel diluvio di fprintf() è impresentabile. Anche volendo ignorare le tematiche profonde connesse ai vari livelli di buffering, si tenga comunque a mente che ognuna di quelle chiamate ha un costo computazionale enorme rispetto a qualsiasi altra alternativa. Sono idiomi da evitare totalmente.

5) Nel mio post precedente ho illustrato con estrema chiarezza il caso della codifica diretta di un valore ASCII compreso tra '0' e '9', estremi inclusi. Tale codifica non richiede il ricorso all'array!
A rigore, ciò si applica anche all'intervallo ['A', 'H'], ovviamente con una diversa costante. Tuttavia, ai fini di una migliore efficienza, è preferibile evitare di introdurre ulteriori if() (e quindi salti nel codice macchina corrispondente) ed uniformare il trattamento del caso isalpha() in O(1) tramite array di lookup.

In ultima analisi, l'inner loop ripulito e razionalizzato, pur mantenendo alcune scelte stilistiche scolasticamente giustificabili, si riduce banalmente a quanto segue:


size_t i = 0;
/* per comodità illustrativa, de funge da puntatore ausiliario per la stringa di output */
char *de = plaintext;
printf("> input......: %s", encoding);

while((encoding[i] != '\n') && (encoding[i] != '\0'))
{
/* isalpha() */
if((encoding[i] >= 'a' && encoding[i] <= 'z') ||
(encoding[i] >= 'A' && encoding[i] <= 'Z'))
{
sprintf(de, "%d", posn[toupper(encoding[i]) - 'A']);
de += 2;
}
/* isdigit() */
else if (encoding[i] >= '0' && encoding[i] <= '9')
{
sprintf(de, "%d", encoding[i] - '0');
++de;
}
else
{
printf("Errore di sintassi nel file di input!\n\n");
fclose(outfile);
fclose(infile);
return(EXIT_FAILURE);
}
i++;
}
printf("> output.....: %s\n\n", plaintext);
fprintf(outfile, "%s\n", plaintext);

mbistato
08-09-2014, 17:19
Chiaro tutto tranne l'istruzione


de += 2;


perchè si incrementa di 2 nel caso in cui ho un carattere?



Grazie di tutto! ho apprezzato molto la tua precisione e il tuo rigore!

M.A.W. 1968
08-09-2014, 17:24
Chiaro tutto tranne l'istruzione


de +=2;


perchè si incrementa di 2 nel caso in cui ho un carattere?


Elementare, caro Watson... i valori di decodifica dei simboli alfabetici hanno tutti esattamente due cifre, partendo dal 10 che corrisponde ad 'A'.

In casi più articolati, sarebbe necessario tenere conto del logaritmo decimale approssimato per eccesso di ogni numero stampato, il che in genere si risolve comunque banalmente applicando la strlen() ad un qualche buffer intermedio usato con itoa() o, al limite, con una piccola penalità prestazionale usando la solita sprintf().

Loading