C에서 문자열 구문 분석

이것은 엄격한 ANSI C89 현명한 코드로 간주됩니다. [word1] word2 [형식의 문자열에서 word1, word2word3를 추출해야합니다. word3] 및 다른 형식으로 실패를 반환합니다.

작동하는 것 같지만 너무 추한 것 같습니다. GetTokenBetweenSquareBracesGetTokenBtweenOpositeSquareBraces가 중복된다는 사실에 대해 언급 할 필요가 없습니다.

방법에 대한 몇 가지 팁이 마음에 듭니다. 정리하세요.

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

댓글

  • 먼저 들여 쓰기를 수정하세요. Markdown 엔진은 ‘ 탭을 좋아하지 않습니다. 탭을 공백으로 바꿉니다.
  • @LokiAstari : 원래 코드를 바꾼 것 같습니다.
  • Opps. 죄송합니다. 내 나사를 고쳤 으면 좋겠다. 메타에 대한 기사에서 코드를 얻었습니다. 탭 문제를 수정하고 다시 넣습니다. 이것이 정확하지 않다면 죄송 합니다만 ‘ 버전을 롤백 할 수없는 것 같습니다.
  • @LokiAstari : 네, 훨씬 좋아 보입니다.

답변

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

최소한 여기에 주석이 있습니다. 세미콜론을 놓치기 쉽습니다. i < len 테스트는 필요하지 않습니다. 문자열 끝에있는 0은 isspace 테스트에 실패해야하므로 길이도 확인할 필요가 없습니다. 또한 추적하는 것도 의미가 없습니다. i. 대신 out를 사용하세요.

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

모든 공백을 0으로 설정할 필요는 없습니다. 전반적으로 한 줄에서 많은 작업을 수행하고 있습니다. 루프 제어와 관련이 없으므로 적어도 루프 본문 내에서 0 설정 만 수행해야합니다.

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

Deja Vu! 이것은 이전 기능과 거의 동일합니다. 브래킷 방향 만 반전되었습니다. 해당 코드를 공유 할 수있을 것 같습니다.

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

여기서 공백을 잘라내는 이유는 무엇입니까? 이미 그렇게하지 않았습니까? 텍스트를 복사하기 위해 많은 작업을하고 있습니다. 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; 

데자뷰!

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

귀하의 코드는 이러한 변수를 오버플로하지 않도록주의하지 않습니다. 이에 대해 뭔가를 할 수 있습니다.

 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; 

0은 성공적인 프로그램 실행을 나타내는 데 사용됩니다.

} 

전체적으로 잘못된 어휘를 사용하고 있기 때문에 프로그램이 추합니다. 작업을 설명하기 쉽게 만든 어휘를 정의하는 대신 주어진 어휘를 사용했습니다. 문제에 대한 나의 접근 방식은 다음과 같습니다.

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

What I ” 문자, 공백, 단어 기능을 매우 간단하게 작성하는 것입니다. char *를 이해한다면 문제가 없어야합니다. 그러나 이러한 간단한 도구는 매우 훌륭하게 결합되어 파서를 간단하게 구현할 수 있습니다.

댓글

  • +1 for ” 일반적으로 작업 함수가 출력을 수행하지 않도록 권장합니다. “. 또한 매우 훌륭하고 깨끗한 솔루션입니다.

답변

이것은 아마도 조금 덜 못 생겼을 것입니다. 그러나 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; } 

Answer

상태 머신을 사용하여이 작업을 완료 할 수 있습니다.

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

댓글

  • 안녕하세요. Code Review에 오신 것을 환영합니다.이 코드는 실제로 리뷰가 아닙니다. 오히려 코드가 수행하는 작업, 작동하는 이유, 원본보다 나은 이유에 대한 설명이 거의없이 작업을 수행하는 대체 방법입니다. 그것을 살펴보고 문서화되지 않은 중괄호 누락, 대 / 소문자 구분 및 모호한 비트 조작에 대해 걱정하십시오. 이것이 더 나은 이유, OP에 대해 다르게 해결되는 사항 및 이러한 선택이 더 나은 코드를 만드는 이유에 대한 세부 정보를 추가하는 것을 고려하십시오.
  • 수작업으로 만들었습니까? 아니면 도구가 필요합니까? 이 개념은 마음에 들지만 ‘이를 지원하는 것이 무섭습니다. 매직 넘버가 너무 많습니다.

답글 남기기

이메일 주소를 발행하지 않을 것입니다. 필수 항목은 *(으)로 표시합니다