이제 형태소 분석기 라이브러리를 만듭시다. 예광탄으로 만든 다음에 정상적으로 동작함을 확인한 후에 라이브러리를 만듭니다. 여기에서는 결과적으로 예광탄에서 만든 내용과 라이브러리가 같으므로 예광탄에서 만드는 과정은 생략할게요.
1 |
public static class MorphemeParser |
형태소 분석기는 자연어 연구를 토대로 의미있는 최소 단위의 형태소를 분리하면 높은 수준의 형태소 분석기를 만들 수 있습니다.
여기에서는 공백이나 마침표 등의 기호와 자주 사용하는 접두사와 접미사를 기준으로 내용을 분리하는 수준의 형태소 분석기를 만들게요.
원본 내용을 분리할 접두사와 접미사 컬렉션을 선언합시다. 이 부분을 동적으로 추가할 수 있게 정의하면 보다 높은 수준의 형태소 분석기를 만들 수 있습니다.
1 2 3 4 |
static string[] filters = {" ",",",".","?","!","\r","\n","!","?", "[","]","<",">","{","}","\"","'"}; static string[] pf_filters = {"는","은","을","를","가","에","게", "에게","입니다","합니다"}; |
이 책에서는 웹 검색 엔진을 만드는 전체 공정과 각 요소를 만드는 원리를 소개하는 것이며 높은 수준을 지향하는 부분은 여러분의 몫으로 남길게요.
형태소를 분석하여 결과를 반환하는 메서드를 제공합시다.
1 |
public static List<Morpheme> Parse(string source) |
먼저 결과를 보관할 컬렉션을 생성합니다.
1 |
List<Morpheme> mlist = new List<Morpheme>(); |
원본 내용을 접두사 필터 컬렉션을 기준으로 분할합니다.
1 2 |
string[] elems = source.Split(filters, StringSplitOptions.RemoveEmptyEntries); |
분할한 문자열 컬렉션의 각 항목을 다시 분석합니다. 그리고 결과를 반환합니다. 각 항목을 분석하는 부분은 별도의 메서드로 작성합시다.
1 2 3 4 5 |
foreach (string elem in elems) { ParseElem(elem, mlist); } return mlist; |
분할한 항목을 분석하는 메서드를 작성합시다.
1 |
private static void ParseElem(string elem, List<Morpheme> mlist) |
입력 인자로 받은 문자열의 끝이 접미사 컬렉션에 있는 요소와 일치하는지 확인하여 일치하면 접미사를 뺀 요소를 만듭니다. 일치하지 않으면 결과에 추가합니다. 이를 위해 세부적인 작업을 수행하는 부분은 별도의 메서드로 정의합시다.
1 2 3 4 5 6 7 8 9 |
foreach (string pf_filter in pf_filters) { if (CheckPostfix(pf_filter, elem)) { MakeElem(pf_filter, elem, mlist); return; } } RecordElem(elem, mlist); |
먼저 분석할 요소의 끝에 접미사가 있는지 확인하는 메서드를 작성합시다.
1 |
private static bool CheckPostfix(string pf_filter, string elem) |
먼저 요소에 접미사 문자열이 포함한다면 두 개의 문자열의 길이의 차를 이용하여 부분 문자열을 얻어옵니다. 만약 얻어온 부분 문자열이 접미사와 같으면 참을 반환합니다. 그렇지 않으면 거짓을 반환합니다.
1 2 3 4 5 6 7 8 9 10 |
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; |
요소의 접미사를 뺀 나머지 요소를 결과에 추가하는 메서드를 작성합시다.
1 |
private static void MakeElem(string pf_filter, string elem, List<Morpheme> mlist) |
요소의 길이에서 접미사의 길이를 뺀 값을 이용하여 부분 문자열을 얻어옵니다.
1 2 |
int pos = elem.Length - pf_filter.Length; string sub = elem.Substring(0, pos); |
얻어온 부분 문자열을 결과에 추가합니다.
1 |
RecordElem(sub, mlist); |
결과에 추가하는 메서드를 작성합시다.
1 |
private static void RecordElem(string elem, List<Morpheme> mlist) |
결과 목록에 있는 각 항목의 이름이 추가할 내용과 일치하면 참조 개수를 1 증가하고 반환합니다.
1 2 3 4 5 6 7 8 |
foreach (Morpheme mo in mlist) { if (mo.Name == elem) { mo.Count++; return; } } |
일치하는 형태소가 없다면 새로운 형태소를 생성하여 결과 컬렉션에 추가합니다.
1 2 |
Morpheme mor = new Morpheme(elem, 1); mlist.Add(mor); |
▷ MorphemeParser.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
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; } } } |