무슨 일로 C 하셨습니까?

[C] 문자열 다루기::문자열 파싱 본문

C - 이걸 굳이?/유틸리티

[C] 문자열 다루기::문자열 파싱

OJJJ 2020. 9. 4. 03:08

솔직히 문자열 다루는 항목에선 가장 고급기술이 아닐까 싶다.

 

물론 이 기능을 하는 함수도 <String.h>에 존재하지만

 

직접 구현해 볼것이다.


간단히 구상해보면

 

기본 문자열(Source)에서 특정 구분자[문자열/문자](separator)를 기준으로 나누어서 각 문자열들을 배열의 형태로 반환시키도록 할 것이다.

 

 

함수 기능      문자열을 특정 문자열(문자)을 기준으로 나눔
입력값      source(문자열), separator(문자열)
반환값      문자열(Token) 배열
     나누어진 여러 문자열들

 


char** g(char* Src, char* Sep) {
	return NULL;
}

( Src : Source / Sep : Separator -> 구분자 )

함수를 선언해준다.

 

 

이전에 만든 함수를 통해 Source에서 Separator가 얼마나 일치하고 어디에서 일치하는지 파악한 후 반환될 배열을 만들어 주는데

char** g(char* Src, char* Sep) {

    int* Finder = StringFinder(Src, Sep);
    int ResultSize = 0;
    
    char** result = (char**)malloc(sizeof(char*) * ResultSize);

    for (int i = 1; i <= Finder[0]; i++) {
    // Separator 사이의 문자열이 존재할 때
    if (1) { 
        result = (char**)realloc(result, sizeof(char*) * ++ResultSize);
        result[ResultSize - 1] = "";
        }
    }

    free(Finder);
    return result;
}

문자열 검색 때와 마찬가지로 할당해야 할 배열의 메모리 크기를 모르기 때문에 realloc()을 사용하도록 한다.

(동적 할당을 사용한 배열을 꼼꼼히 반환시켜주자)

 

Token 배열에 문자열을 추가할 때 고려해야할 사항은

  • 구분자가 맨 앞에 있는 경우 → 첫 번째 Token은 널(NULL)값
  • 구분자가 맨 뒤에 있는 경우 → 마지막 Token은 널(NULL)값
  • 두 구분자 사이의 거리가 없는 경우 → 두 구분자 사이의 Token은 널(NULL)값

으로 각각의 경우를 한번에 처리하기 위해서 Token의 길이가 0일때는 무시해주면 될 것 같다.

 

 

    int SrcLength = StringLength(Src);
    int SepLegnth = StringLength(Sep);
    int* Finder = StringFinder(Src, Sep);

    int ResultSize = 0;
    int StringLength = 0;
    char** result = (char**)malloc(sizeof(char*) * ResultSize);

    int StartIdx = 0;
    int EndIdx = 0;
    
    for (int i = 1; i <= Finder[0]; i++) {
        EndIdx = Finder[i];

        StringLength = EndIdx - StartIdx;
        
        // Seperator 사이에 문자열이 존재할 때
        if (StringLength > 0) {
            result = (char**)realloc(result, sizeof(char*) * ++ResultSize);
            result[ResultSize - 1] = StringAdd3("", Src + StartIdx, StringLength);
        }
        
        StartIdx = EndIdx + SepLegnth;
    }

(저 가독성 떨어지는 문자열 합성 함수를 어서 처리해 주어야 할 것 같다.)

 

 

몇 가지 변수를 더 선언해 주고 로직을 추가했다.

이때 주의할 점은 구분자는 Token에 포함되지 않기 떄문에 구분자의 길이도 고려해 주어야한다.

 

두 구분자 사이의 거리 (구분자의 문자열 길이를 제외한) 가 존재한다면 그 길이 만큼이 Token이 되는 방식이다.

(문자열의 시작부터 구분자로 인해 구분되었다고 생각하고 고려해 주어야한다.)

 

그리고 그 다음 구분자로 하나씩 넘어가며 Token을 만들어서 배열에 추가해준다.

 

    if (StartIdx + SepLegnth < SrcLength) {
        result = (char**)realloc(result, sizeof(char*) * ++ResultSize);
        result[ResultSize - 1] = StringAdd3("", Src + StartIdx + SepLegnth, SrcLength - (StartIdx + SepLegnth));
    }

    result = (char**)realloc(result, sizeof(char*) * ++ResultSize);
    result[ResultSize - 1] = NULL;

마지막으로 마지막 구분자 뒤의 Token을 추가해 주고, 배열의 끝을 알리는 NULL까지 처리해주면 되겠다.

 

 

이름을 붙혀준 함수의 최종 코드는 다음과 같다.

char** StringParser(char* Src, char* Sep) {

    int SrcLength = StringLength(Src);
    int SepLegnth = StringLength(Sep);
    int* Finder = StringFinder(Src, Sep);

    int ResultSize = 0;
    int StringLength = 0;
    char** result = (char**)malloc(sizeof(char*) * ResultSize);

    int StartIdx = 0;
    int EndIdx = 0;

    for (int i = 1; i <= Finder[0]; i++) {
        EndIdx = Finder[i];

        StringLength = EndIdx - StartIdx;

        if (StringLength == 0) {
            result = (char**)realloc(result, sizeof(char*) * ++ResultSize);
            result[ResultSize - 1] = StringAdd1("", "");
        }

        // 시작점이 다음점과 일치하는 경우
        if (StringLength > 0) {
            result = (char**)realloc(result, sizeof(char*) * ++ResultSize);
            result[ResultSize - 1] = StringAdd3("", Src + StartIdx, StringLength);
        }

        StartIdx = EndIdx + SepLegnth;
    }

    if (StartIdx == SrcLength) {
        result = (char**)realloc(result, sizeof(char*) * ++ResultSize);
        result[ResultSize - 1] = StringAdd1("", "");
    }
    else if (StartIdx < SrcLength) {
        result = (char**)realloc(result, sizeof(char*) * ++ResultSize);
        result[ResultSize - 1] = StringAdd1("", Src + StartIdx);
    }

    result = (char**)realloc(result, sizeof(char*) * ++ResultSize);
    result[ResultSize - 1] = NULL;

    free(Finder);
    return result;
}

이 함수가 정상 작동하는지는 직접 테스트해보길 바란다.

 

 

함수의 사용은 포인터가 NULL을 가리킬 때까지 반복해보면 되겠다.

    char** ptr=StringParser("asdvasvasdfasdfsdfasdvzxcv","x");
    for (int i = 0; ptr[i] != NULL; i++) {
        printf("%d is :  %s\n", i, ptr[i]);
        free(ptr[i]);
    }
    free(ptr);

메모리 반환은 필수

Comments