C++ 예제 (넥슨 프로그래밍 대회 문제풀이 2)

넥슨 청소년 프로그래밍 대회(NYPC) 2018년 예선 1~2번 문제에 이어 다음 문제도 살펴보겠습니다.

자세한 내용은 NYPC 홈페이지를 참조바랍니다.

줄임말 문제입니다.

줄임말

좀 놀 줄 알려면 줄임말을 잘 써야 한다.
요즘은 문장에서 단어들의 첫 글자만을 따와서 줄임말을 만들곤 한다.
예를 들어, '애교 빼면 시체'를 '애빼시'라고 하고 '혼자 코인 노래방'을 '혼코노'로 부른다.
문제에서 주어지는 문장들은 다음을 만족한다.
1) 한글과 영어로 이루어진 문장이다.
2) 문장은 단어들로 이루어지고 단어들 사이에는 공백이 하나 존재한다. 단어는 한글과 영어가 혼용될 수 있다.
3) 문장은 그 뜻이 명확하지 않아도 된다. 우리가 읽었을 때 그 의미를 이해할 수 없을 수도 있다.
문장이 주어지면 단어들의 첫 글자를 딴 줄임말을 출력하는 프로그램을 작성하시오.
  • UTF-8 한글 문자열을 해독하는 방법은 인터넷을 검색하여 알아내어 보자.
  • 언어의 표준 라이브러리 이외에 다른 사람의 코드를 복사해서 사용해서는 안된다.

입력 형식

첫째 줄에 문장의 개수를 나타내는 정수 N(1 ≤ N ≤ 1,024)이 주어진다.
다음 이어지는 N개의 줄 각각에 하나의 문장이 주어진다. 하나의 문장은 16개 이하의 단어로 이루어져 있으며, 각 단어는 64자 이하이다. 단어는 한글 글자와 영어 대문자, 소문자로 이루어져 있다. 한글 글자 중에서 완전하지 않은 낱자(ㄱ,ㄴ,...,ㅎ,ㅏ,ㅑ,...,ㅣ 등)는 주어지지 않는다.
주어지는 입력은 UTF-8(BOM 없음)으로 인코딩 된 입력이다.

출력 형식

N개의 줄에 걸쳐 정답을 출력한다. 출력의 i번째 줄에 입력에 주어진 i번째 문장으로 만든 줄임말을 출력한다.
출력 내용을 붙여넣기로 직접 제출하는 것을 권장하며, 업로드로 파일을 제출하는 경우 파일은 UTF-8(BOM 여부 상관 없음)로 인코딩되어야 한다.

입력 예제 1

3
상하이 스파이스 치킨버거 콤보
NEXON Youth Programming Challenge
별걸 다 줄인다

출력 예제 1

상스치콤
NYPC
별다줄

입력 예제 2

3
Away from Keyboard
안녕 안녕 안녕 안녕 안녕 안녕 안녕 안녕 안녕 안녕 안녕 안녕 안녕 안녕 안녕 안녕
zl존전사 zl존법사 zl존도적 zl존궁수 zl존코더

출력 예제 2

AfK
안안안안안안안안안안안안안안안안
zzzzz

채점 방식

이 문제는 풀이 소스코드를 제출하지 않고, 각 테스트케이스의 입력데이터를 다운받아 알맞은 출력 파일을 만들어 출력 파일만을 제출하는 문제다. 또한 입력 케이스들 각각에 대해 동일한 점수가 배분된다.

여기까지가 문제에 대한 설명입니다.

문제의 내용이 이해될 때까지 반복해 읽어 보기 바랍니다.

요약하자면, 여러줄에 걸친 문장을 파일(UTF-8 인코딩) 에서 읽어들여, 각 줄의 문장에서 공백을 분리해 첫 글자만 딴 줄임말을 파일(UTF-8 인코딩)로 만들어 제출하는 방식입니다.

먼저 코드를 살펴보기에 앞서, 컴퓨터가 문자를 저장하고 표시하는 방법에 대한 사전 지식이 필요합니다.

컴퓨터는 0과 1 즉, 이진수만 아는 바보라고 이야기합니다.

우리는 두 가지 상태로 표현되는 값을 바로 디지털이라고 표현합니다.

컴퓨터가 수를 저장하는 방식은, 인간이 사용하는 10진수(Decimal)를 2진수(Binary)로 변경해 저장한다는 사실을 학원에서 수업을 들어본 사람이라면 다 이해할 것입니다.

컴퓨터가 문자를 저장, 표현하는 방식은, 우리 인간의 약속에 의해 만들어져 있습니다.

예를 들면, 'A'라는 문자를 숫자 65로 저장하기로 미국이 약속하고 표준화 시켰습니다.  ('B' 숫자 66 등)

이를 아스키코드(ASCII Code) 라고 부릅니다.

아스키 코드는 7비트 방식의 인코딩입니다. 7비트로 표현할 수 있는 경우의 수는 2의 7승이므로 128개의 문자만 표현이 가능합니다.

컴퓨터가 보급화 되면서 다양한 국가의 언어 문자를 처리할 필요성이 생겼고, 유니코드가 등장합니다.

개념은 매우 간단한데, 7비트로 표현하는 문자의 수를, 확장해 8~32비트(1~4바이트)로 표현하자는 것입니다.

2의 32승은 거의 무한대에 가까운 수의 범위이므로 유니코드는 전 세게 모든 문자를 지정하고, 표현 가능하도록 설계되었습니다.

그럼, UTF-8, UTF-16 등은 또 무엇인가?

유니코드 문자집합의 문자들을 어떻게 저장하고 표시할지 약속한 규칙입니다.

요즘은 ASCII와 호환이 가능한 UTF-8 이라는 가변 길이 인코딩 방법을 가장 많이 사용합니다.

사실 문자집합 규격과 인코딩에 대한 내용은 책 한권으로 다루어도 될 정도로 그 배경, 역사, 규정 등에 대한 내용이 복잡하므로 여기까지만 적고, 아래 글을 참조해 보시기 바랍니다.

제가 본 국내 자료 중에서 문자집합과 인코딩에 대해 가장 잘 표현한 글이라고 생각합니다.

네이버 개발자 가 쓴 글 링크 : 한글 인코딩의 역사와 유니코드


그럼 이제 본론으로 들어가 코드를 한번 살펴보겠습니다.


#include <iostream>
#include <sstream>
#include <fstream>
#include <codecvt>

using namespace std;

wstring readFile(const char* filename)
{
    // read in UTF-8
    wifstream wif(filename);
    //wif.imbue(locale(locale::empty(), new codecvt_utf8<wchar_t>));
    wif.imbue(locale(locale::empty(), new codecvt_utf8<wchar_t, 0x10ffff, std::consume_header>));
    wstringstream wss;
    wss << wif.rdbuf();
    return wss.str();
}

void writeFile(const wstring& s)
{
    // Write file in UTF-8
    std::wofstream wof;
    wof.imbue(locale(locale::empty(), new codecvt_utf8<wchar_t, 0x10ffff, std::generate_header>));
    wof.open(L"ouput.txt");    
    wof << s;        
    wof.close();
}

int main()
{
    // 한글 출력 로케일설정
    wcout.imbue(locale("kor"));    

    // 원본 파일 열기 (UTF-8 -> widechar)
    wstring str = readFile("input.txt");
    wcout << L"[원본 문자열]" << endl << str << endl;

    // 첫줄 얻기('\n'문자로 자르기)
    wchar_t *p = nullptr;
    wchar_t *pToken = wcstok_s(const_cast<wchar_t*> (str.c_str()) , L"\n", &p);

    // 몇줄인지
    int n = stoi(pToken);    

    wstring short_str;
    
    while (pToken != nullptr)
    {
        // 다음 행 가져오기
        pToken = wcstok_s(nullptr, L"\n", &p);        

        if (pToken)
        {
            // 한 행 복사
            wstring s = wstring(pToken);

            // 공백으로 자르기
            wchar_t *p2 = nullptr;
            wchar_t *pToken2 = wcstok_s(const_cast<wchar_t*> (s.c_str()), L" ", &p2);

            while (pToken2 != nullptr)
            {
                short_str += pToken2[0];
                pToken2 = wcstok_s(nullptr, L" ", &p2);
            }

            // 마지막줄은 줄바꿈 X
            if (--n)
                short_str += L"\n";        
        }    
    }

    // 줄임말 출력
    wcout << endl << L"[문장의 첫 문자]" << endl;
    wcout << short_str << endl;    

    // 줄임말 파일 쓰기
    writeFile(short_str);
    return 0;
}

코드를 보고있으니 파이썬으로 하면 쉬울텐데 라는 생각이 들지만, C++로 만들어진 코드가 구글 검색을 해봐도 찾기 힘들어 한번 만들어 보았습니다.

먼저 29번 라인의 메인함수부터 살펴보겠습니다.

소스코드를 분석하는 요령은 메인함수부터 살펴보는 것이 좋습니다. 다른 함수들은 호출되어야지만 수행되므로, 먼저 살펴볼 필요없이 메인부터 흐름에 따라 자연스럽게 코드를 분석해가는 것이 편리합니다.

1. 먼저 원본 파일(input.txt)을 열어서 wstring 타입의 문자열 변수에 저장합니다.

코드 실행전 앞 문제 설명부분에 나온 파일을 복사에 input.txt(UTF-8)로 만들어 프로그램의 실행파일 경로에 저장해 두어야 합니다.

2. input.txt 파일의 첫 줄에 문장의 라인수가 기록되어 있으므로 wcstok_s() 함수를 이용해 몇 줄인지 알아냅니다.

wsctok_s()함수는 분리 문자를 이용해 문자열을 잘라 토큰화(잘라내는)시키는함수입니다.

이 함수는 3개의 전달인자를 갖는데, 첫번째는 wchar_t * 타입 원본문자열 입니다.

다만 읽어낸 파일의 문자열을 저장한 str 변수는 const wchar_t* 타입이므로, const 속성을 제거하기 위해 C++의 형변환 연산자인 const_cast<>를 통해 const 속성을 제거합니다.

두번째 전달인자는 잘라내고자 하는 기준 문자열(줄바꿈 문자 "\n") 입니다.

세번째 전달인자는 잘라낸 다음 문자열 위치를 저장할 wchar_t ** 타입 2중 포인터 입니다.

3
상하이 스파이스 치킨버거 콤보
NEXON Youth Programming Challenge
별걸 다 줄인다
wchar_t *p = nullptr;
wchar_t *pToken = wcstok_s(const_cast<wchar_t*> (str.c_str()) , L"\n", &p);

예를 들면, 위와 같은 input.txt 가 있고, "\n"기준으로 wcstok_s() 함수를 호출하면,
pToken은 3 을 가르키고, 세번째 전달인자인 p는 "상하이 스파이스...이하" 를 지시하게 됩니다.

이제 stoi() 함수를 통해 "3"이라는 문자를 정수로 바꾸어 몇줄의 문장인지 알아냅니다.

위 내용이 이해가 된다면 그 다음은 쉽습니다.

문장의 줄 수만 큼 반복하며, 공백을 기준으로 wcstok_s()를 이용해 첫 문자를 잘라와 저장하면 끝입니다.

3. 47번 라인의 while 반복문을 통해 첫 줄 "상하이 스파이스 치킨버거 콤보" 를 가져온 후 wcstok_s()함수를 공백문자 기준으로 잘라냅니다.

pToken2에는 "상하이"가 저장되고, pToken2[0]은 첫 문자인 "상"을 의미합니다.

이제 이 행동을 계속해서 반복하면, short_str변수에는 첫줄의 줄임말 "상스치콤" 이 저장됩니다.

다음줄로 이동해 공백으로 자르면, "NYPC"가, 그 다음은 "별다줄" 이 저장됩니다.

이젠 반복문을 탈출해 해당 문자열 변수에 쓰여진 줄임말을 UTF-8 파일(output.txt) 로 쓰면 끝입니다.

코드를 실행 후 해당 경로를 가서 찾아보면 UTF-8로 인코딩된 output.txt 파일이 만들어지고 줄임말이 쓰여져 있음을 확인할 수 있습니다.

코드의 수행 결과는 아래와 같습니다.

[콘솔 출력 결과]

[ouput.txt 파일출력 결과]

이상으로 줄임말 문제분석을 마칩니다.

청소년이 참가하는 대회에 이 정도 수준의 문제가 나올지는 몰랐습니다. 유니코드와 인코딩문제는 문자열 처리가 Native한 C++ 프로그래머에게 쉽지 않은 주제입니다.

C++을 이용해 이 문제를 풀 수 있는 학생이 과연 있을까 하는 의문도 듭니다.

그에 비해, 파이썬은 3버전 부터 유니코드 문자집합을 기본으로 사용하므로 비교적 쉽게 구현이 가능합니다.

코드의 내용이 잘 이해되지 않는다면 코드에 Break Point(중단점)을 넣고 디버깅해 가며 변수의 변화를 관찰하기 바랍니다.

감사합니다.

댓글

이 블로그의 인기 게시물

Qt Designer 설치하기

C++ 예제 (소켓 서버, 이미지, 파일전송)