본문 바로가기
C Programming/연습 문제

[C언어 연습문제]강좌 20. Querying the Document(문자열 분해 - 문단, 문장, 단어 조작)

by 희품 2020. 4. 23.
반응형

학습(Study) & 목표(Objective) & 과제(Task)

문서는 문단이 모여서 표현되고, 문단은 문장의 모음으로, 문장은 단어의 모음으로 표현됩니다. 단어는 문자(영어는 소문자와 대문자 알파벳으로 표현이 되지요.

 

원본 텍스트 문서를 구성요소로 문단, 문장, 단어로 변환하는 과제입니다. 결과를 테스트하기 위한 쿼리는 아래 설명되어 있는 대로, 특정 문단, 문장, 단어를 반환하도록 요청하는 쿼리가 있습니다.

 

엘리시아는 덩케르크 대학교에서 C 프로그래밍 언어를 공부하고 있는데, 포인터를 사용하여 단어, 문장, 단락 및 문서를 나타내려고 합니다.

 

표현 방법은 다음과 같이 정의합니다.

단어 : char*

문장 : char** (문장의 단어는 하나의 공백(" ")으로 구분합니다. 마지막 단어는 공백(" ")으로 끝나지 않습니다.

문단 : char*** (문단의 문장은 마침표(".") 하나로 구분합니다. 

문서 : char**** (문서의 문단은 하나의 줄 바꿈("\n")으로 구분합니다. 마지막 문단은 줄 바꿈으로 끝나지 않습니다.

 

예시

Learning C is fun.
Learning pointers is more fun.It is good to have pointers.

첫 번째 문단의 유일한 문장은 다음과 같이 나타낼 수 있습니다.

char ** first_sentence_in_first_paragraph = { "Learning", "C", "is", "fun" };

첫 번째 문단 자체는 다음과 같이 나타낼 수 있습니다.

char *** first_paragraph = {{ "Learning", "C", "is", "fun"}};

두 번째 문단의 첫 번째 문장은 다음과 같이 나타낼 수 있습니다.

char** first_sentence_in_second_paragraph = {"Learning", "pointers", "is", "more", "fun"};

두 번째 문단의 두 번째 문장은 다음과 같이 나타낼 수 있습니다.

char** second_sentence_in_second_paragraph = {"It", "is", "good", "to", "have", "pointers"};

두 번째 문단 자체는 다음과 같이 나타낼 수 있습니다.

char*** second_paragraph = {{"Learning", "pointers", "is", "more", "fun"}, {"It", "is", "good", "to", "have", "pointers"}};

마지막으로 이 문서는 다음과 같이 나타낼 수 있습니다.

char**** document = {{{"Learning", "C", "is", "fun"}}, {{"Learning", "pointers", "is", "more", "fun"}, {"It", "is", "good", "to", "have", "pointers"}}};

앨리시아는 친구 테오도라에게 문서를 문자열로 보냈습니다. (char**** 형식의 문서가 아닌 char* 형식의 문자열로 전송)

다음 기능을 완료하여 문서를 char**** 형식으로 변환할 수 있도록 도와줘야 합니다.

char**** get_document(char* text) : char****로 표시된 문서를 반환
char*** kth_paragraph(char**** document, int k) : k번째 문단을 반환
char** kth_sentence_in_mth_paragraph(char**** document, int k, int m) : m번째 문단에서 k번째 문장 반환
char* kth_word_in_mth_sentence_of_nth_paragraph(char**** document, int k, int m, int n) : n번째 문단에서 m번째 문장의 k번째 단어를 반환.

 

입력 형식(Input Format)

첫 입력으로 문단의 수(paragraph_count)를 입력 받습니다. 다음 입력으로 입력된 문단의 수만큼 문자열을 입력합니다. 줄(\n)을 기준으로 문단이 됩니다. 문장의 구분은 "."으로 합니다. 그다음 입력은 쿼리 수 q를 입력하는 정수를 입력하고, 다음 라인에 아래의 형식에 맞도록 쿼리 조건을 입력합니다.

 

q의 값을 1 k 형식으로 입력

- 다음 라인에 정수 k번째 문단의 처음 문장에서 단어를 몇개 출력할지의 정수 x를 입력합니다.

- 다음 정수 x의 입력은 다음 문장의 출력할 단어 수 만큼 입력합니다.

- 이 쿼리는 kth_paragraph 함수를 호출합니다.

 

q의 값을 2 k m 형식으로 입력

- 다음 라인에 k번째 문단의 m번째 문장에서 표현할 단어 수 x를 입력합니다.

- 이 쿼리는 kth_sentence_in_mth_paragraph 함수를 호출합니다.

 

q의 값을 3 k m n 형식으로 입력

- 이 쿼리는 kth_word_in_mth_sentence_of_nth_paragraph 함수를 호출합니다.

 

제약 조건(Constraints)

 

- get_document에 전달된 텍스트에는 공백으로 구분된 단어(' '), 마침표('.'), 문장 및 줄 바꿈('\n')으로 구분된 문단이 저장됩니다.

- 문장의 마지막 단어는 공백으로 끝나지 않고, 마지막 문단은 줄 바꿈('\n')을 하지 않습니다.

- 단어는 대문자 영어 알파벳과 소문자 영어 알파벳만 사용합니다.

- 1 <= (문서에 포함된 글자 수) <= 1000

- 1 <= (문서에 포함된 문단의 갯수) <= 5

출력 형식(Output Format)

검색어에 해당하는 문단, 문장 또는 단어를 표출하여 코드의 논리를 확인할 수 있습니다.

 

입력 예제(Sample Input)

2
Learning C is fun.
Learning pointers is more fun.It is good to have pointers.
3
1 2
2
5
6
2 1 1
4
3 1 1 1 

출력 예제(Sample Output)

Learning pointers is more fun.It is good to have pointers.
Learning C is fun
Learning

출력 예제의 설명

첫 번째 쿼리는 두 번째 문단의 첫 번째 문장의 5개의 단어, 두 번째 문장의 6개의 단어에 해당합니다.

두 번째 쿼리는 첫 번째 문단의 첫 번째 문장의 4개의 단어에 해당합니다.

세 번째 쿼리는 첫 번째 문단의 첫 번째 문장의 1개의 단어에 해당합니다.

주어진 코드

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<assert.h>
#define MAX_CHARACTERS 1005
#define MAX_PARAGRAPHS 5

char* kth_word_in_mth_sentence_of_nth_paragraph(char**** document, int k, int m, int n){
}

char** kth_sentence_in_mth_paragraph(char**** document, int k, int m){ 
}

char*** kth_paragraph(char**** document, int k){
}

char**** get_document(char* text){
}

char* get_input_text() {	
    int paragraph_count;
    scanf("%d", &paragraph_count);

    char p[MAX_PARAGRAPHS][MAX_CHARACTERS], doc[MAX_CHARACTERS];
    memset(doc, 0, sizeof(doc));
    getchar();
    for (int i = 0; i < paragraph_count; i++) {
        scanf("%[^\n]%*c", p[i]);
        strcat(doc, p[i]);
        if (i != paragraph_count - 1)
            strcat(doc, "\n");
    }

    char* returnDoc = (char*)malloc((strlen (doc)+1) * (sizeof(char)));
    strcpy(returnDoc, doc);
    return returnDoc;
}

void print_word(char* word) {
    printf("%s", word);
}

void print_sentence(char** sentence) {
    int word_count;
    scanf("%d", &word_count);
    for(int i = 0; i < word_count; i++){
        printf("%s", sentence[i]);
        if( i != word_count - 1)
            printf(" ");
    }
} 

void print_paragraph(char*** paragraph) {
    int sentence_count;
    scanf("%d", &sentence_count);
    for (int i = 0; i < sentence_count; i++) {
        print_sentence(*(paragraph + i));
        printf(".");
    }
}

int main() 
{
    char* text = get_input_text();
    char**** document = get_document(text);

    int q;
    scanf("%d", &q);

    while (q--) {
        int type;
        scanf("%d", &type);

        if (type == 3){
            int k, m, n;
            scanf("%d %d %d", &k, &m, &n);
            char* word = kth_word_in_mth_sentence_of_nth_paragraph(document, k, m, n);
            print_word(word);
        }

        else if (type == 2){
            int k, m;
            scanf("%d %d", &k, &m);
            char** sentence = kth_sentence_in_mth_paragraph(document, k, m);
            print_sentence(sentence);
        }

        else{
            int k;
            scanf("%d", &k);
            char*** paragraph = kth_paragraph(document, k);
            print_paragraph(paragraph);
        }
        printf("\n");
    }     
}

 

답안 코드

쿼리에 필요한 k번째 문단, m번쨰 문장, n번째 단어를 표출하는 함수는 답안 코드와 같이 document의 4중 포인터를 배열로 활용하면 쉽게 구현할 수 있습니다.

char**** document는 곧 document[n][m][k]처럼 표현할 수 있습니다. 답안에서 Index를 -1씩 해주는 이유는 배열의 처음 index는 0이기 때문입니다.

 

답안에서는 단어를 반환하는 함수, 문장을 반환하는 함수, 문단을 반환하는 함수를 만들어, 문단, 문장, 단어 순으로 호출하면서 문서를 구하고 있습니다. 그러나 이렇게 구현했을 때, 로직을 알고 보지 않는 이상 가독성이 떨어지는 것 같네요.

추가 답안 코드 쪽으로 가셔서 다른 풀이 방법을 확인해보시기 바랍니다.

char* kth_word_in_mth_sentence_of_nth_paragraph(char**** document, int k, int m, int n) {
    return document[n-1][m-1][k-1];
}

char** kth_sentence_in_mth_paragraph(char**** document, int k, int m) { 
    return document[m-1][k-1];
}

char*** kth_paragraph(char**** document, int k) {
    return document[k-1];
}

char* get_word(char* text, int beg, int end) {
    char* answer;
    answer = calloc(end - beg + 2, sizeof(char));
    int index = 0;
    int i;
    for (i = beg; i <= end; i++)
        answer[index++] = text[i];
    answer[index] = 0;
    return answer;
}

char** get_sentence(char* text, int beg, int end) {
    char** answer;
    int word_count = 1;
    int i;
    for (i = beg; i <= end; i++)
        if (text[i] == ' ')
            ++word_count;
    answer = calloc(word_count, sizeof(char*));
    int start = beg;
    int index = 0;
    for (i = beg; i <= end; i++)
        if (text[i] == ' ')
        {
            answer[index++] = get_word(text, start, i - 1);
            start = i + 1;
        }
    answer[index] = get_word(text, start, i - 1);
    return answer;
}

char*** get_paragraph(char* text, int beg, int end) {
    char*** answer;
    int sentence_count = 0;
    int i;
    for (i = beg; i <= end; i++)
        if (text[i] == '.')
            ++sentence_count;
    answer = calloc(sentence_count, sizeof(char**));
    int start = beg;
    int index = 0;
    for (i = beg; i <= end; i++)
        if (text[i] == '.')
        {
            answer[index++] = get_sentence(text, start, i - 1);
            start = i + 1;
        }
    return answer;
}

char**** get_document(char* text) {
    char**** answer;
    int paragraph_count = 1;
    int i;
    for (i = 0; text[i]; i++)
        if (text[i] == '\n')
            ++paragraph_count;
    answer = calloc(paragraph_count, sizeof(char***));
    int start = 0;
    int index = 0;
    for (i = 0; text[i]; i++)
        if (text[i] == '\n')
        {
            answer[index++] = get_paragraph(text, start, i - 1);
            start = i + 1;
        }
    answer[index] = get_paragraph(text, start, i - 1);
    return answer;
}

 

추가 답안 코드

문서를 각 포인터마다 동적으로 메모리를 할당하여 배열로 만들어 버렸습니다. 줄 바꿈, 스페이스 바, 마침표가 나올 때마다 메모리를 다시 할당하고, index를 조절하면서 char**** doc에 데이터를 짜 맞추는 방식으로 구현한 코드입니다.

C언어 문자열 특성으로 줄 바꿈 자리, 마침표 자리, 공백 자리를 NULL로 바꿔주면서(text[i] = 0;) 단어가 저장되도록 하는 방식입니다. 이해는 마찬가지로 어려워 보일 수 있지만, 코드 자체는 더 보기가 편하지 않나요?

char**** get_document(char* text)
{
    int nIdxP = 0, nIdxS = 0, nIdxW = 0;
    int i = 0;
    char**** doc = NULL;

    doc = (char ****) malloc(sizeof(char***));
    doc[nIdxP] = (char ***) malloc(sizeof(char**));
    doc[nIdxP][nIdxS] = (char**) malloc(sizeof(char*));
    doc[nIdxP][nIdxS][nIdxW] = text; // start Word Save

    for(i = 0; text[i+1] != 0; i++)
    {
        switch(text[i])
        {
        case '\n':
            nIdxP++;
            doc = (char****)realloc(doc, (nIdxP+1)*sizeof(char***));
            nIdxS = 0;
            nIdxW = 0;
            doc[nIdxP] = (char***)realloc(doc[nIdxP], (nIdxS+1)*sizeof(char**));
            doc[nIdxP][nIdxS] = (char**)realloc(doc[nIdxP][nIdxS], (nIdxW+1)*sizeof(char*));
            doc[nIdxP][nIdxS][nIdxW] = &text[i+1];
            text[i] = 0; 
            break;

        case '.':
            nIdxS++;
            nIdxW = 0;
            doc[nIdxP] = (char***)realloc(doc[nIdxP], (nIdxS+1)*sizeof(char**));
            doc[nIdxP][nIdxS] = (char**)realloc(doc[nIdxP][nIdxS], (nIdxW+1)*sizeof(char*));
            doc[nIdxP][nIdxS][nIdxW] = &text[i+1];
            text[i] = 0; 
            break;

        case ' ':
            nIdxW++;
            doc[nIdxP][nIdxS] = (char**)realloc(doc[nIdxP][nIdxS], (nIdxW+1)*sizeof(char*));
            doc[nIdxP][nIdxS][nIdxW] = &text[i+1];
            text[i] = 0;
            break;

        default :
            break;
        }
    }
    text[i] = 0;
    return doc;
}

 

 

반응형