Analýza řetězců v C

Toto má být přísný pedantský kód ANSI C89. Mělo by extrahovat word1, word2 a word3 z řetězce naformátovaného [word1] word2 [ word3] a selhání návratu v jakémkoli jiném formátu.

Zdá se, že to funguje, ale vypadá to tak ošklivě. Není třeba komentovat skutečnost, že GetTokenBetweenSquareBraces a GetTokenBtweenOpositeSquareBraces jsou duplikáty.

Rád bych získal několik tipů, jak vyčistěte to.

#include <stdio.h> #include <string.h> #include <ctype.h> char * TrimWhiteSpaces(char *str) { char *out = str; int i; int len = strlen(str); for (i=0; i<len && isspace(str[i]); i++, out++); /*scan forward*/ for (i=len-1; i>=0 && isspace(str[i]); str[i]=0, i--);/*scan backward*/ return out; } char * GetTokenBetweenSquareBraces(char * input, char **output, int * output_size) { char *p = TrimWhiteSpaces(input); *output_size=0; if (p[0] == "[") { *output = TrimWhiteSpaces(p + 1); do { (*output_size)++; }while((*output)[*output_size] != "]" && isalnum((*output)[*output_size])); } else { return NULL; } return (*output) + *output_size; } char * GetTokenBtweenOpositeSquareBraces(char * input, char **output, int * output_size) { char *p = TrimWhiteSpaces(input); *output_size=0; if (p[0] == "]") { *output = TrimWhiteSpaces(p + 1); do { (*output_size)++; }while((*output)[*output_size] != "[" && isalnum((*output)[*output_size])); } else { return NULL; } return (*output) + *output_size; } int GetWords(char * str,char * word1,char * word2,char * word3) { char * next=NULL,*output=NULL; int outputsize; printf ("\nSplitting string \"%s\" into tokens:\n",str); next = GetTokenBetweenSquareBraces (str,&output,&outputsize); strncpy(word1,output,outputsize); word1[outputsize] = "\0"; strcpy(word1,TrimWhiteSpaces(word1)); if(!next) return 0; next = GetTokenBtweenOpositeSquareBraces (next,&output,&outputsize); strncpy(word2,output,outputsize); word2[outputsize] = "\0"; strcpy(word2,TrimWhiteSpaces(word2)); if(!next) return 0; next = GetTokenBetweenSquareBraces (next,&output,&outputsize); strncpy(word3,output,outputsize); word3[outputsize] = "\0"; strcpy(word3,TrimWhiteSpaces(word3)); if(!next) return 0; return 1; } void TestGetWords(char * str ) { char word1[20],word2[20],word3[20]; if (GetWords(str,word1,word2,word3)) { printf("|%s|%s|%s|\n",word1,word2,word3); } else { printf("3ViLLLL\n"); } } int main (void) { char str[] ="[ hello ] gfd [ hello2 ] "; char str2[] ="[ hello [ gfd [ hello2 ] "; char str3[] ="the wie321vg42g42g!@#"; char str4[] ="][123[]23][231["; TestGetWords(str); TestGetWords(str2); TestGetWords(str3); TestGetWords(str4); getchar(); return 1; } 

Komentáře

  • Nejprve opravte odsazení. Markdown engine ‚ nemá rád karty – nahraďte je mezerami.
  • @LokiAstari: vypadá to, že jste nahradili jeho původní kód.
  • Opps. Promiňte. Doufám, že jsem to napravil. Dostal jsem kód z článku o meta. opravil problém se záložkou a vrátil jej zpět. Pokud to není správné, omlouvám se, ale zdá se mi, že ‚ nelze vrátit verzi zpět.
  • @LokiAstari: Ano, vypadá mnohem lépe.

Odpověď

#include <stdio.h> #include <string.h> #include <ctype.h> char * TrimWhiteSpaces(char *str) { char *out = str; int i; int len = strlen(str); for (i=0; i<len && isspace(str[i]); i++, out++); /*scan forward*/ 

Měl bych alespoň tělo s komentář zde. Je snadné tento středník vynechat. Nemyslím si, že potřebujete i < len test. Nula na konci řetězce by neměla selhat v testu isspace, takže nemusíte také kontrolovat délku. Také ve skutečnosti nemá smysl sledovat z i. Místo toho použijte out.

 for (i=len-1; i>=0 && isspace(str[i]); str[i]=0, i--);/*scan backward*/ 

Není opravdu nutné nastavovat všechny tyto mezery na 0. Celkově v tomto jednom řádku děláte hodně práce. Měli byste alespoň provést nastavení 0 uvnitř těla smyčky, protože to nemá nic společného s ovládáním smyčky.

 return out; 

Obecně je nejlepší buď upravit parametry, nebo vrátit nové. Nedělejte obojí. Zde vrátíte nový ukazatel řetězce a upravíte původní řetězec.

} char * GetTokenBetweenSquareBraces(char * input, char **output, int * output_size) { char *p = TrimWhiteSpaces(input); *output_size=0; if (p[0] == "[") { *output = TrimWhiteSpaces(p + 1); do { (*output_size)++; }while((*output)[*output_size] != "]" && isalnum((*output)[*output_size])); 

] není číslo ani písmeno. Oba tyto testy nepotřebujete.

 } else { return NULL; } return (*output) + *output_size; } char * GetTokenBtweenOpositeSquareBraces(char * input, char **output, int * output_size) { char *p = TrimWhiteSpaces(input); *output_size=0; if (p[0] == "]") { *output = TrimWhiteSpaces(p + 1); do { (*output_size)++; }while((*output)[*output_size] != "[" && isalnum((*output)[*output_size])); } else { return NULL; } return (*output) + *output_size; } 

Deja Vu! To je téměř přesně stejné jako u předchozí funkce. Pouze směr držáku byl obrácen. Zdá se, že byste měli být schopni tento kód sdílet.

int GetWords(char * str,char * word1,char * word2,char * word3) { char * next=NULL,*output=NULL; int outputsize; printf ("\nSplitting string \"%s\" into tokens:\n",str); 

Obecně doporučuji, aby vaše pracovní funkce neprováděly žádný výstup. Také zvláštní volba, kam umístit nové řádky.

 next = GetTokenBetweenSquareBraces (str,&output,&outputsize); strncpy(word1,output,outputsize); word1[outputsize] = "\0"; strcpy(word1,TrimWhiteSpaces(word1)); 

Proč zde ořezáváte mezery? Už jste to neudělali. Děláte hodně práce s kopírováním textu. Možná je to něco, co by měla GetTokenB BetweenSquareBraces udělat?

 if(!next) return 0; next = GetTokenBtweenOpositeSquareBraces (next,&output,&outputsize); strncpy(word2,output,outputsize); word2[outputsize] = "\0"; strcpy(word2,TrimWhiteSpaces(word2)); if(!next) return 0; 

Deja Vu!

 next = GetTokenBetweenSquareBraces (next,&output,&outputsize); strncpy(word3,output,outputsize); word3[outputsize] = "\0"; strcpy(word3,TrimWhiteSpaces(word3)); if(!next) return 0; 

Deja Vu!

 return 1; } void TestGetWords(char * str ) { char word1[20],word2[20],word3[20]; 

Váš kód není opatrný, aby nedošlo k přetečení těchto proměnných. Možná budete chtít s tím něco udělat.

 if (GetWords(str,word1,word2,word3)) { printf("|%s|%s|%s|\n",word1,word2,word3); } else { printf("3ViLLLL\n"); } } int main (void) { char str[] ="[ hello ] gfd [ hello2 ] "; char str2[] ="[ hello [ gfd [ hello2 ] "; char str3[] ="the wie321vg42g42g!@#"; char str4[] ="][123[]23][231["; TestGetWords(str); TestGetWords(str2); TestGetWords(str3); TestGetWords(str4); 

Pro účely automatického testování je vlastně lepší, když zadáte správnou odpověď a porovnáte ji v kódu. Tímto způsobem vám program sdělí, kdy je špatně.

 getchar(); return 1; 

0 se používá k označení úspěšného spuštění programu.

} 

Váš program je celkově ošklivý, protože používáte nesprávnou slovní zásobu. Vzali jste slovní zásobu tak, jak je dána, místo toho, abyste definovali slovní zásobu, která usnadnila popis úkolu. Zde je můj přístup k vašemu problému

char * Whitespace(char * str) /* This function return the `str` pointer incremented past any whitespace. */ { /* when an error occurs, we return NULL. If an error has already occurred, just pass it on */ if(!str) return str; while(isspace(*str)) { str++; } return str; } char * Character(char * str, char c) /* This function tries to match a specific character. It returns `str` incremented past the character or NULL if the character was not found */ { if(!str) return str; /* Eat any whitespace before the character */ str = Whitespace(str); if(c != *str) { return NULL; } else { return str + 1; } } char * Word(char * str, char * word) /* This function reads a sequence of numbers and letter into word and then returns a pointer to the position after the word */ { /* Handle errors and whitespace */ if(!str) return str; str = Whitespace(str); /* copy characters */ while(isalnum(*str)) { *word++ = *str++; } *word = 0; /* don"t forget null!*/ return str; } int GetWords(char * str,char * word1,char * word2,char * word3) { str = Character(str, "["); str = Word(str, word1); str = Character(str, "]"); str = Word(str, word2); str = Character(str, "["); str = Word(str, word3); str = Character(str, "]"); str = Character(str, "\0"); return str != NULL; } 

Co já “ Dokončili jsme (nebo jsme se o to pokusili) napsat funkce Character, Whitespace a Word tak, aby byly opravdu velmi jednoduché. Pokud rozumíte char *, neměli byste s nimi mít žádné potíže. Tyto jednoduché nástroje se ale velmi pěkně kombinují a umožňují přímou implementaci vašeho analyzátoru.

Komentáře

  • +1 pro “ Obecně doporučuji, aby vaše pracovní funkce neprováděly žádný výstup „. Také velmi pěkné a čisté řešení.

Odpověď

To je možná trochu méně ošklivé, ale manipulace s řetězci v C nikdy nebude hezká.

static const char * skip_space(const char *s) { return s + strspn(s, " "); } static const char * skip_bracket(const char * s, int bracket) { s = skip_space(s); if (*s != bracket) return NULL; return skip_space(++s); } static const char * skip_word(const char * s) { return s + strcspn(s, " []"); } static const char * copy_word(char *w, const char *s, size_t size) { const char * end = skip_word(s); size_t len = end - s; if (len >= size) /* silently truncate word to buffer size */ len = size - 1; memcpy(w, s, len); w[len] = "\0"; return skip_space(end); } static int get_words(const char *s, char *w1, char *w2, char *w3, size_t size) { if ((s = skip_bracket(s, "[")) == NULL) return 0; s = copy_word(w1, s, size); if ((s = skip_bracket(s, "]")) == NULL) return 0; s = copy_word(w2, s, size); if ((s = skip_bracket(s, "[")) == NULL) return 0; s = copy_word(w3, s, size); if ((s = skip_bracket(s, "]")) == NULL) return 0; return 1; } 

Odpověď

K dokončení tohoto úkolu můžete použít stavový stroj,

#include <stdio.h> #include <string.h> void Tokenize(char* s) { // the following array return new state based on current state and current scanned char // Input: * [ ] space Print Tokenize Current State Expression /*Next state:*/char StateArray[12][3][4] = {{{11,1,11,0} ,{0,0,0,0},{0,0,0,0} }, //0 {space}*{[} {{2,11,11,1} ,{1,0,0,0},{0,0,0,0}}, //1 {space}*{char} {{2,11,4,3} ,{1,0,0,0},{0,0,1,0}}, //2 {char}*{space}?{]} {{11,11,4,3} ,{0,0,0,0},{0,0,1,0}}, //3 {space}*{]} {{5,11,11,4} ,{1,0,0,0},{0,0,0,0}}, //4 {space)*{char} {{5,7,11,6} ,{1,0,0,0},{0,1,0,0}}, //5 {char}*{space}?{[} {{11,7,11,6} ,{0,0,0,0},{0,1,0,0}}, //6 {space}*{[} {{8,11,11,7} ,{1,0,0,0},{0,0,0,0}}, //7 {space}*{char} {{8,11,10,9} ,{1,0,0,0},{0,0,1,0}}, //8 {char}*{space}?{]} {{11,11,10,9} ,{0,0,0,0},{0,0,1,0}}, //9 {space}*{]} {{11,11,11,10} ,{0,0,0,0},{0,0,0,0}}, //10 {space}* {{11,11,11,11} ,{0,0,0,0},{0,0,0,0}} }; char state=0; int len = strlen(s); for(int i =0;i<len;i++) { if(StateArray[state][1][(s[i]^91)? ((s[i]^93)?((s[i]^32)? 0:3):2):1]) printf("%c",s[i]); if(StateArray[state][2][(s[i]^91)? ((s[i]^93)?((s[i]^32)? 0:3):2):1]) printf("\n"); state=StateArray[state][0][(s[i]^91)? ((s[i]^93)?((s[i]^32)? 0:3):2):1]; switch(state) { case 11: printf("Error at column %d",i); case 10: if(i==len-1) { printf("\nParsing completed"); } } } } int main(void) { char* s= " [ word1 ] word2word [ 3 ] "; // test string Tokenize(s); } 

Komentáře

  • Ahoj, a vítejte v Code Review. Tento kód ve skutečnosti není kontrolou. Jedná se spíše o alternativní způsob, jak dělat věci s malým vysvětlením, co dělá, proč funguje a proč je lepší než originál. Navíc vypadám Proveďte to a obávejte se, že vám chybí složené závorky, případné výpisy případů a nejasné bitové manipulace, které nejsou dokumentovány. Zvažte přidání podrobností o tom, proč je to lepší a co řeší odlišně od OP, a proč tyto možnosti přispívají k lepšímu kódu.
  • Vytvořili jste to ručně nebo je v tom nějaký nástroj? Koncept se mi líbí, ale ‚ bych se bál, že to podpořím. Existuje tolik magických čísel.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *