Skip to content

40. 회문 판별과 N-gram


1. 키워드

  • 회문(Palindrome)
  • N-gram


2. 회문 판별과 N-gram 만들기

  • 문자열을 이용하여 회문을 판별하는 방법과 N-gram을 만드는 방법을 알아보자.


3. 회문 판별

  • 회문은 다음과 같이 순서를 거꾸로 읽어도 제대로 읽은 것과 같은 단어와 문장을 말한다.


001


  • 이제 문자열을 배열에 넣은 뒤 반복문으로 각 글자를 검사해 보자.


#define _CRT_SECURE_NO_WARNINGS // scanf 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

int main()
{
    char word[30];            // 단어를 저장할 배열
    int length;               // 문자열 길이
    bool isPalindrome = true; // 회문 판별값을 저장할 변수, 초깃값은 true

    printf("단어를 입력하세요: ");
    scanf("%s", word);

    length = strlen(word); // 문자열의 길이를 구함

    // 0부터 문자열 길이의 절반만큼 반복
    for (int i = 0; i < length / 2; i++)
    {
        // 왼쪽 문자와 오른쪽 문자를 비교하여 문자가 다르면
        if (word[i] != word[length - 1 - i])
        {
            // 회문이 아님
            isPalindrome = false;
            break;
        }
    }

    printf("%d\n", isPalindrome); // 회문 판별값 출력

    return 0;
}

// 단어를 입력하세요: level (입력)
// 1
// 단어를 입력하세요: hello (입력)
// 0


  • 회문 판별에서 가장 중요한 부분은 문자열(단어)의 길이이다.
  • 왜냐하면 회문 판별은 문자열의 길이를 기준으로 하기 때문이다.


length = strlen(word); // 문자열의 길이를 구함


  • 이제 문자열 길이의 절반만큼만 반복하면서 왼쪽 문자와 오른쪽 문자들을 검사한다.
  • 만약 문자열의 길이가 5라면 5 / 2 = 2(정수 나눗셈)이므로 가운데 글자 바로 앞까지만 검사하게 된다.
  • 반복문 안에서 왼쪽 문자 word[i]와 오른쪽 문자 word[length - 1 - i]를 비교하여 문자가 다르면 회문이 아니므로 isPalindromefalse를 넣어주고 반복문을 끝낸다.
  • 어차피 회문이 아니기 때문에 더 검사할 필요가 없다.


// 0부터 문자열 길이의 절반만큼 반복
for (int i = 0; i < length / 2; i++)
{
    // 왼쪽 문자와 오른쪽 문자를 비교하여 문자가 다르면
    if (word[i] != word[length - 1 - i])
    {
        // 회문이 아님
        isPalindrome = false;
        break;
    }
}


  • for 반복문의 i0부터 1씩 증가하므로 word[i]는 왼쪽에서 오른쪽으로 진행하고, word[length - 1 - i]는 오른쪽에서 왼쪽으로 진행한다.
  • 즉, 문자열의 마지막 문자는 word[length - 1]이므로 여기서 인덱스를 i만큼 계속 빼주면 오른쪽에서 왼쪽으로 진행하게 된다.


002


4. N-gram 만들기

  • N-gram은 문자열에서 N개의 연속된 요소를 추출하는 방법이다.
  • 만약 "Hello"라는 문자열을 문자(글자) 단위 2-gram으로 추출하면 다음과 같이 된다.


He
el
ll
lo


  • 즉, 문자열의 처음부터 문자열 끝까지 한 글자씩 이동하면서 두 글자를 추출한다.
  • 3-gram은 세 글자, 4-gram은 네 글자를 추출한다.


  • 이제 C에서 문자 단위 2-gram을 출력해 보자.


#include <stdio.h>
#include <string.h>

int main()
{
    char text[30] = "Hello";
    int length;

    length = strlen(text); // 문자열의 길이를 구함

    // 2-gram이므로 문자열의 끝에서 한글자 앞까지만 반복함
    for (int i = 0; i < length - 1; i++)
    {
        printf("%c%c\n", text[i], text[i + 1]); // 현재 문자와 그다음 문자 출력
    }

    return 0;
}

// He
// el
// ll
// lo


  • 2-gram이므로 문자열의 끝에서 한 글자 앞까지만 반복하면서 현재 문자와 바로 그다음 문자 두 글자씩 출력한다.


003


  • 만약 3-gram이라면 조건식은 i < length - 2와 같이 되고, 문자열 끝에서 두 글자 앞까지 반복하면 된다.
  • 문자열을 출력할 때는 printf("%c%c%c\n", text[i], text[i + 1], text[i + 2]);가 된다.
  • 여기서 문자열의 끝까지 반복하면 text[i + 1], text[i + 2]는 문자열의 범위를 벗어난 접근을 하게 되므로 주의해야 한다.


  • 글자 단위 N-gram이 아닌 단어 단위 N-gram도 있다.
  • 다음은 문자열을 공백으로 구분하여 단어 단위 2-gram을 출력한다.
  • 예를 들어 "this is c language""this is", "is c", "c language"가 된다.


#define _CRT_SECURE_NO_WARNINGS // strtok 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h>

int main()
{
    char text[100] = "this is c language";
    char *tokens[30] = {
        NULL,
    };             // 자른 문자열의 포인터를 보관할 배열, NULL로 초기화
    int count = 0; // 자른 문자열 개수

    char *ptr = strtok(text, " "); // " " 공백 문자를 기준으로 문자열을 자름, 포인터 반환

    while (ptr != NULL) // 자른 문자열이 나오지 않을 때까지 반복
    {
        tokens[count] = ptr; // 문자열을 자른 뒤 메모리 주소를 문자열 포인터 배열에 저장
        count++;             // 인덱스 증가

        ptr = strtok(NULL, " "); // 다음 문자열을 잘라서 포인터를 반환
    }

    // 2-gram이므로 배열의 마지막에서 요소 한 개 앞까지만 반복함
    for (int i = 0; i < count - 1; i++)
    {
        printf("%s %s\n", tokens[i], tokens[i + 1]); // 현재 문자열과 그다음 문자열 출력
    }

    return 0;
}

// this is
// is c
// c language


  • 단어 단위 2-gram도 간단한데, strtok 함수로 text를 자른 뒤 각 단어들을 tokens 배열에 넣는다.


char *ptr = strtok(text, " "); // " " 공백 문자를 기준으로 문자열을 자름, 포인터 반환

while (ptr != NULL) // 자른 문자열이 나오지 않을 때까지 반복
{
    tokens[count] = ptr; // 문자열을 자른 뒤 메모리 주소를 문자열 포인터 배열에 저장
    count++;             // 인덱스 증가

    ptr = strtok(NULL, " "); // 다음 문자열을 잘라서 포인터를 반환
}


  • 2-gram이므로 tokens 배열의 마지막에서 요소 한 개 앞까지만 반복하면서 현재 문자열과 그다음 문자열을 출력하면 된다.


// 2-gram이므로 배열의 마지막에서 요소 한 개 앞까지만 반복함
for (int i = 0; i < count - 1; i++)
{
    printf("%s %s\n", tokens[i], tokens[i + 1]); // 현재 문자열과 그다음 문자열 출력
}


N-gram의 활용

  • 예를 들어 4-gram을 쓰면 "picked", "picks", "picking"에서 "pick"만 추출하여 단어의 빈도를 세는데 이용된다.
  • 이런 특성 때문에 검색엔진, 빅데이터, 법언어학 분야에서 주로 활용된다.

References