7. 2 형태소 분석기 라이브러리 만들기

이제 형태소 분석기 라이브러리를 만듭시다. 예광탄으로 만든 다음에 정상적으로 동작함을 확인한 후에 라이브러리를 만듭니다. 여기에서는 결과적으로 예광탄에서 만든 내용과 라이브러리가 같으므로 예광탄에서 만드는 과정은 생략할게요.

public static class MorphemeParser

형태소 분석기는 자연어 연구를 토대로 의미있는 최소 단위의 형태소를 분리하면 높은 수준의 형태소 분석기를 만들 수 있습니다.

여기에서는 공백이나 마침표 등의 기호와 자주 사용하는 접두사와 접미사를 기준으로 내용을 분리하는 수준의 형태소 분석기를 만들게요.

원본 내용을 분리할 접두사와 접미사 컬렉션을 선언합시다. 이 부분을 동적으로 추가할 수 있게 정의하면 보다 높은 수준의 형태소 분석기를 만들 수 있습니다.

static string[] filters = {" ",",",".","?","!","\r","\n","!","?",
                              "[","]","<",">","{","}","\"","'"};
static string[] pf_filters = {"는","은","을","를","가","에","게",
                                  "에게","입니다","합니다"};

이 책에서는 웹 검색 엔진을 만드는 전체 공정과 각 요소를 만드는 원리를 소개하는 것이며 높은 수준을 지향하는 부분은 여러분의 몫으로 남길게요.

형태소를 분석하여 결과를 반환하는 메서드를 제공합시다.

public static List<Morpheme> Parse(string source)

먼저 결과를 보관할 컬렉션을 생성합니다.

List<Morpheme> mlist = new List<Morpheme>();

원본 내용을 접두사 필터 컬렉션을 기준으로 분할합니다.

string[] elems = source.Split(filters,
                     StringSplitOptions.RemoveEmptyEntries);

분할한 문자열 컬렉션의 각 항목을 다시 분석합니다. 그리고 결과를 반환합니다. 각 항목을 분석하는 부분은 별도의 메서드로 작성합시다.

foreach (string elem in elems)
{
    ParseElem(elem, mlist);
}
return mlist; 

분할한 항목을 분석하는 메서드를 작성합시다.

private static void ParseElem(string elem, List<Morpheme> mlist)

입력 인자로 받은 문자열의 끝이 접미사 컬렉션에 있는 요소와 일치하는지 확인하여 일치하면 접미사를 뺀 요소를 만듭니다. 일치하지 않으면 결과에 추가합니다. 이를 위해 세부적인 작업을 수행하는 부분은 별도의 메서드로 정의합시다.

foreach (string pf_filter in pf_filters)
{
    if (CheckPostfix(pf_filter, elem))
    {
        MakeElem(pf_filter, elem, mlist);
        return;
    }
}
RecordElem(elem, mlist);

먼저 분석할 요소의 끝에 접미사가 있는지 확인하는 메서드를 작성합시다.

private static bool CheckPostfix(string pf_filter, string elem)

먼저 요소에 접미사 문자열이 포함한다면 두 개의 문자열의 길이의 차를 이용하여 부분 문자열을 얻어옵니다. 만약 얻어온 부분 문자열이 접미사와 같으면 참을 반환합니다. 그렇지 않으면 거짓을 반환합니다.

if (elem.Contains(pf_filter))
{
    int pos = elem.Length - pf_filter.Length;
    string sub = elem.Substring(pos);
    if (sub == pf_filter)
    {
        return true;
    }
}
return false;

요소의 접미사를 뺀 나머지 요소를 결과에 추가하는 메서드를 작성합시다.

private static void MakeElem(string pf_filter, string elem, List<Morpheme> mlist)

요소의 길이에서 접미사의 길이를 뺀 값을 이용하여 부분 문자열을 얻어옵니다.

int pos = elem.Length - pf_filter.Length;
string sub = elem.Substring(0, pos);

얻어온 부분 문자열을 결과에 추가합니다.

RecordElem(sub, mlist);

결과에 추가하는 메서드를 작성합시다.

private static void RecordElem(string elem, List<Morpheme> mlist)

결과 목록에 있는 각 항목의 이름이 추가할 내용과 일치하면 참조 개수를 1 증가하고 반환합니다.

foreach (Morpheme mo in mlist)
{
    if (mo.Name == elem)
    {
        mo.Count++;
        return;
    }
}

일치하는 형태소가 없다면 새로운 형태소를 생성하여 결과 컬렉션에 추가합니다.

Morpheme mor = new Morpheme(elem, 1);
mlist.Add(mor);

▷ MorphemeParser.cs

using System;
using System.Collections.Generic;
using WSE_Core; 
namespace MorphemeParserLib
{
    /// <summary>
    /// 형태소 분석기 - 정적 클래스
    /// </summary>
    public static class MorphemeParser
    {
        static string[] filters = {" ",",",".","?","!","\r","\n","!","?",
                                      "[","]","<",">","{","}","\"","'"};
        static string[] pf_filters = {"는","은","을","를","가","에","게",
                                     "에게","입니다","합니다"}; 
        /// <summary>
        /// 형태소 분석 정적 메서드
        /// </summary>
        /// <param name="source">원본</param>
        /// <returns>형태소 컬렉션</returns>
        public static List<Morpheme> Parse(string source)
        {
            List<Morpheme> mlist = new List<Morpheme>();
            string[] elems = source.Split(filters,
                StringSplitOptions.RemoveEmptyEntries); 
            foreach (string elem in elems)
            {
                ParseElem(elem, mlist);
            }
            return mlist;
        }
         private static void ParseElem(string elem, List<Morpheme> mlist)
        {
            foreach (string pf_filter in pf_filters)
            {
                if (CheckPostfix(pf_filter, elem))
                {
                    MakeElem(pf_filter, elem, mlist);
                    return;
                }
            }

            RecordElem(elem, mlist);
        }
 
        private static void RecordElem(string elem, List<Morpheme> mlist)
        {
            foreach (Morpheme mo in mlist)
            {
                if (mo.Name == elem)
                {
                    mo.Count++;
                    return;
                }
            }

            Morpheme mor = new Morpheme(elem, 1);
            mlist.Add(mor);
        }
 


        private static void MakeElem(string pf_filter, string elem, List<Morpheme> mlist)
        {
            int pos = elem.Length - pf_filter.Length;
            string sub = elem.Substring(0, pos);
            RecordElem(sub, mlist);
        }
 
        private static bool CheckPostfix(string pf_filter, string elem)
        {
            if (elem.Contains(pf_filter))
            {
                int pos = elem.Length - pf_filter.Length;
                string sub = elem.Substring(pos);
                if (sub == pf_filter)
                {
                    return true;
                }
            }
            return false;
        }
    }
}