Analiza ciągów znaków w C

To ma być ścisły kod pedantyczny ANSI C89. Powinien wyodrębnić word1, word2 i word3 z ciągu sformatowanego [słowo1] słowo2 [ słowo3] i zwraca błąd w jakimkolwiek innym formacie.

Wydaje się, że działa, ale wydaje się brzydki. Nie ma potrzeby komentowania faktu, że GetTokenBetweenSquareBraces i GetTokenBtweenOpositeSquareBraces są duplikatami.

Chciałbym uzyskać kilka wskazówek, jak posprzątaj 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; } 

Komentarze

  • Najpierw popraw wcięcie. Silnik Markdown nie ' jak tabulatory – zastąp je spacjami.
  • @LokiAstari: wygląda na to, że zastąpiłeś jego oryginalny kod.
  • Ups. Przepraszam. Mam nadzieję, że naprawiłem mój schrzanek. Kod dostałem z artykułu o meta. naprawiono problem z zakładką i odłóż go z powrotem. Jeśli to nie jest poprawne, przepraszam, ale mogę ' nie przywracać wersji.
  • @LokiAstari: Tak, wygląda dużo lepiej.

Odpowiedź

#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*/ 

Mam przynajmniej treść z komentarz w tym miejscu. Łatwo jest przeoczyć ten średnik. Nie sądzę, że potrzebujesz testu i < len. 0 na końcu łańcucha powinno nie przejść testu isspace, więc nie musisz też sprawdzać długości. Śledzenie też nie ma sensu z i. Zamiast tego użyj out.

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

Tak naprawdę nie jest konieczne ustawianie wszystkich tych spacji na 0. Ogólnie wykonujesz za dużo pracy w tej jednej linii. Powinieneś przynajmniej ustawić 0 wewnątrz treści pętli, ponieważ nie ma to nic wspólnego z kontrolą pętli.

 return out; 

Generalnie najlepiej zmodyfikować parametry lub zwrócić nowe. Nie rób obu tych rzeczy. Tutaj zwracasz nowy wskaźnik ciągu i modyfikujesz oryginalny ciąg.

} 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])); 

] nie jest cyfrą ani literą. Nie potrzebujesz obu tych testów.

 } 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! Jest to prawie dokładnie to samo, co poprzednia funkcja. Odwrócono tylko kierunki wsporników. Wygląda na to, że powinieneś być w stanie udostępnić ten kod.

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); 

Generalnie odradzam wykonywanie przez funkcje działające żadnych wyników. Również dziwny wybór miejsca wstawiania nowych linii.

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

Dlaczego przycinasz tutaj białe znaki? Czy już tego nie zrobiłeś. Wykonujesz dużo pracy, aby skopiować tekst. Może to coś, co powinno zrobić GetTokenBetweenSquareBraces?

 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]; 

W Twoim kodzie nie należy uważać, aby nie przepełniać tych zmiennych. Możesz coś z tym zrobić

 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); 

Na potrzeby testów automatycznych lepiej będzie, jeśli podasz poprawną odpowiedź i sprawdzisz ją w kodzie. W ten sposób program powie ci, kiedy jest źle.

 getchar(); return 1; 

0 jest używane do wskazania pomyślnego uruchomienia programu.

} 

Ogólnie twój program jest brzydki, ponieważ używasz złego słownictwa. Zamiast zdefiniować słownictwo, które ułatwiło opisanie zadania, przyjęłeś podane słownictwo. Oto moje podejście do Twojego problemu

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 ja ” już zrobiłeś (lub próbowałem zrobić), to napisanie funkcji znakowych, białych znaków i słów tak, że są one naprawdę bardzo proste. Jeśli rozumiesz char *, nie powinieneś mieć z nimi żadnych problemów. Ale te proste narzędzia łączą się bardzo dobrze, aby umożliwić prostą implementację parsera.

Komentarze

  • +1 dla ” Generalnie odradzam wykonywanie przez funkcje robocze jakichkolwiek danych wyjściowych „. Również bardzo ładne i przejrzyste rozwiązanie.

Odpowiedź

To jest być może trochę mniej brzydkie, ale obsługa ciągów nigdy nie będzie ładna w C.

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; } 

Odpowiedź

Możesz użyć automatu stanowego do wykonania tego zadania,

#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); } 

Komentarze

  • Cześć, i witamy w Code Review. Ten kod nie jest tak naprawdę recenzją. Jest to raczej alternatywny sposób robienia rzeczy bez wyjaśnienia, co robi, dlaczego działa i dlaczego jest lepszy od oryginału. Ponadto wyglądam na hrough it i martwić się brakującymi nawiasami klamrowymi, błędnymi instrukcjami wielkości liter i niejasnymi manipulacjami bitowymi, które nie są udokumentowane. Proszę rozważyć dodanie szczegółów, dlaczego to jest lepsze i co rozwiązuje inaczej niż OP i dlaczego te wybory wpływają na lepszy kod.
  • Czy utworzyłeś to ręcznie, czy jest wymagane jakieś narzędzie? Podoba mi się ta koncepcja, ale ' boję się jej wspierać. Jest tak wiele magicznych liczb.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *