반응형

 

 

"[C#] 초성을 포함한 StartWith 함수 만들기" (링크: https://pichen.tistory.com/56 ) 글에서 ㄱ부터 ㅎ까지를 모두 직접 적어서 switch 돌리는 것에 계산식이 있지 않겠냐고 하는 반응을 보았습니다.

 

[C#] 초성을 포함한 StartWith 함수 만들기

개발자 중 누군가 초성을 포함하여 검색하는 방법을 물었는데, 시간이 남아서 개발을 했다가 초안이 맘에 안들어서 퇴근 후 공부를 마친 시간에 보완작업을 해보았습니다. 현재 다니는 회사에

pichen.tistory.com

 

결론적으로 말하면 1편에서 쓰는 방법을 쓰는 것이 성능면에서 월등해보입니다만, char에 대한 이해도를 높이기 위해 계산을 하는 과정을 보여드리려고 합니다. 먼저 각 글자들의 유니코드를 외우고 다니고 있지 않기 때문에 지난 글의 소스인

아래 이미지 부분을 복사하여 엑셀을 열어 붙여넣기합니다. 엑셀을 활용하는 방법은 다른 글 또는 검색으로 찾아보세요!

(이 글을 작성하는 24년 5월 9일까지는 엑셀 관련글은 블로그에 없긴 합니다.)

 

 

 

엑셀에 붙여 넣기 후 [텍스트 나누기] 기능으로 텍스트 중 유니코드가 궁금한 '' 들을 분리해냅니다.

 

그리고 나서, 컬럼만 복사 후 값으로 붙여넣기한 다음에 로그를 찍을 모양을 만들어 줍니다.

아래 이미지 처럼 만들때 당연하게 손으로 쓰는 부분은 L 컬럼 뿐이어야 엑셀을 쓰는 보람이 있습니다.

M열 중 첫행 하나의 값을 만들고 + 눌러서 누르면 됩니다.

참고로 M1의 셀에 입력한 값은 =$L$1&I1&$L$2&$L$3&I1&$L$4&$L$1&J1&$L$2&$L$3&J1&$L$4&$L$1&K1&$L$2&$L$3&K1&$L$2

이건데요, 실제로는 셀 클릭 -> & -> 셀클릭 방식으로 만든거예요. 열과행에 $가 붙는 것은 F4를 누르면 만들 수 있고 자동완성시 고정될 셀에 지정하면 됩니다.(L컬럼의 값들)

로그를 찍어보려고 하는거라 결국은 복사하여 IDE에 붙여넣기 합니다!

Console.Write($@"          요기에 붙여넣기 하세요!       ");

 

$@를 둘다 붙이는 이유는 $는 보간문자열을 이용하기 위해서고 (그래서 엑셀에서 굳이 {와 }를 붙여서 가져왔어요.

@를 안쓰면 문장 띄어쓰기할때마다

$" ~~~~ " +

$" ~~~~ " +

$" ~~~~ " 이렇게 붙여줘야하는데요, @를 쓰면 이 과정을 생략해도 일관된 값으로 처리됩니다! @의 다른 모든 기능들은 검색해보세요!

 

출력값은 이렇게 나옵니다.

 

이제 초성 검색을 위해 필요한 유니코드들을 알았죠!

유니코드간의 규칙을 찾기 위해 다시 엑셀로 붙여넣기 하고 앞서 설명드렸던 텍스트 나누기 - 기준 "공백"을 적용하여

숫자만 분리해낸 뒤, 제가 ㄲㄸㅃㅆㅉ를 제일 뒤에 적었어서, 자음의 유니코드 순서에 맞게 정렬합니다. 엑셀의 필터기능을 이용하면 쉬운데, 방법을 모르신다면 수동으로 정렬하셔도 좋습니다.

 

 

유니코드가 비어있는 부분을 혹시 캐치하셨나요? ㄲ과 ㄴ 사이 12595가 없고, ㄹ과 ㅁ 사이에 12602~12608이 없죠,

위에 로그를 찍던 방식으로 찍어보시면 알겠지만 ㄳ, ㄺ,ㄻ,ㄼ,ㄽ,ㄾ,ㄿ,ㅀ 로 초성으로는 올 수 없는 애들이 껴있어요.

가-힣에는 해당 초성이 받침 이외에서 쓰이는 글자는 없죠!

그래서 이 부분들만 남기면 약간의 규칙이 생긴답니다.

 

 

ㄱ 과 가 간에는 31439 차이가 있는데, 가 -> 까, 까 -> 나 등등 은 유니코드 차이가 정확히 588이라는 얘기입니다.

또한 가와 깋, 그러니까 ㄱ으로 시작하는 가장 첫 글자와 ㄱ으로 시작하는 가장마지막 글자의 차이가 587,

어떤 초성을 고르더라도 그 차이는 동일하게 587이라는 결과가 위 엑셀 캡처에서 드러났습니다.

 

따라서, char 'ㄱ'의 int 값 12593에 31439를 더하면 '가'의 int값이 되고, 여기서 587을 더하면 '깋'의 int 값이 됩니다.

char'ㄴ'의 int 값은, 12593 ('ㄱ') + 31439 + 588, 여기서 582을 더하면 '닣'의 int값이 됩니다.

 

이해가 되시나요?

 

이 내용을 c#에서 적어보면, 아래와 같습니다. 규칙이 보이시나요?

char gi_eok = 'ㄱ';
char ga = (char)((int)gi_eok + 31439);
char gih = (char)((int)ga + 587);


char ni_en = 'ㄴ';
char na = (char)((int)gi_eok + 31439 + 588); 
char nih = (char)((int)na + 587);

char di_geut = 'ㄷ';
char da = (char)((int)gi_eok + 31439 + 588*2); 
char dih = (char)((int)da + 587);

 

이 규칙을 토대로 초성일때는 초성이 들어가는 글자에서 모두 true를 리턴할 수 있도록 함수를 작성하였습니다.

bool IsChosung2(char OriginWordChar, char keywordChar)
{
    List<char> chosungs = new() { 'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ' };
    if (chosungs.Contains(keywordChar))
    {
        int startCharacter = (int)'ㄱ' + 31439 + 588 * chosungs.IndexOf(keywordChar);
        int endCharacter = startCharacter + 587;
        return startCharacter <= OriginWordChar && OriginWordChar <= endCharacter;
    }
    else
    {
        return OriginWordChar == keywordChar;
    }
}


///////////////////////////////////////////////////
// 참고로 지난 글에서 이부분의 함수는 아래와 같습니다.
bool IsChosung1(char OriginWordChar, char keywordChar) => keywordChar switch
{
    'ㄱ' => '가' <= OriginWordChar && OriginWordChar <= '깋',
    'ㄴ' => '나' <= OriginWordChar && OriginWordChar <= '닣',
    'ㄷ' => '다' <= OriginWordChar && OriginWordChar <= '딯',
    'ㄹ' => '라' <= OriginWordChar && OriginWordChar <= '맇',
    'ㅁ' => '마' <= OriginWordChar && OriginWordChar <= '밓',
    'ㅂ' => '바' <= OriginWordChar && OriginWordChar <= '빟',
    'ㅅ' => '사' <= OriginWordChar && OriginWordChar <= '싷',
    'ㅇ' => '아' <= OriginWordChar && OriginWordChar <= '잏',
    'ㅈ' => '자' <= OriginWordChar && OriginWordChar <= '짛',
    'ㅊ' => '차' <= OriginWordChar && OriginWordChar <= '칳',
    'ㅋ' => '카' <= OriginWordChar && OriginWordChar <= '킿',
    'ㅌ' => '타' <= OriginWordChar && OriginWordChar <= '팋',
    'ㅍ' => '파' <= OriginWordChar && OriginWordChar <= '핗',
    'ㅎ' => '하' <= OriginWordChar && OriginWordChar <= '힣',
    'ㄲ' => '까' <= OriginWordChar && OriginWordChar <= '낗',
    'ㄸ' => '따' <= OriginWordChar && OriginWordChar <= '띻',
    'ㅃ' => '빠' <= OriginWordChar && OriginWordChar <= '삫',
    'ㅆ' => '싸' <= OriginWordChar && OriginWordChar <= '앃',
    'ㅉ' => '짜' <= OriginWordChar && OriginWordChar <= '찧',
    _ => keywordChar == OriginWordChar
};

 

 

지난 글에서 작성한 부분 중 뒷부분에 대한 설명이라 앞부분은 그냥 가져왔습니다.

방금 작성한 IsChosung2()가 아닌 다른 부분이 궁금하시다면

지난글인 "[C#] 초성을 포함한 StartWith 함수 만들기" (링크: https://pichen.tistory.com/56 ) 를 참고해주세요!

//초성 검색 테스트
string keyword1 = "ㄱ나ㄷ";
string keyword2 = "aㄱ나";
List<string> words = new List<string> { "가나다", "가노", "가난", "가나두", "가나라", "고리", "기체", "깋체", "나다라", "다라마", "가나a", "a가나", "a가나다" };
List<string> list1 = words.FindAll(w => StartWithKeyword(keyword1, w));
List<string> list2 = words.FindAll(w => StartWithKeyword(keyword2, w));
Print(keyword1, list1);
Print(keyword2, list2);



void Print(string keyword, List<string> papers) => papers.ForEach(p => Console.WriteLine($"검색어: {keyword} - {p}"));

bool StartWithKeyword(string keyword, string word)
{
    if (keyword.Length > word.Length)
    {
        return false;
    }

    for (var i = 0; i < word.Length && i < keyword.Length; i++)
    {
        //bool charResult = IsChosung1(word[i], keyword[i]);
        bool charResult = IsChosung2(word[i], keyword[i]);
        if (!charResult)
        {
            return false;
        }
    }
    return true;
}



bool IsChosung2(char OriginWordChar, char keywordChar)
{
    List<char> chosungs = new() { 'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ' };
    if (chosungs.Contains(keywordChar))
    {
        int startCharacter = (int)'ㄱ' + 31439 + 588 * chosungs.IndexOf(keywordChar);
        int endCharacter = startCharacter + 587;
        return startCharacter <= OriginWordChar && OriginWordChar <= endCharacter;
    }
    else
    {
        return OriginWordChar == keywordChar;
    }
}

 

IsChosung함수의 길이는 짧아졌는데, 성능도 과연 그럴까?

정답은 아닙니다.

먼저 방금 작성된 함수는 숫자를 매번계산해야 합니다. => 따라서 성능을 개선하기 위해서는 변화하는 값이 아니기 때문에 미리 계산하거나 하드코딩으로 클래스로 초성, 시작글자, 마지막글자의 int나 char값을 작성 후 컬렉션에 넣어서 관리해야 첫번째 함수와 유사한 퍼포먼스를 낼 수 있을 것입니다.

 

그러나 이러한 개발과정과 검토가 개발실력에 많은 도움이 될 것이라 믿습니다. 감사합니다.

반응형

+ Recent posts