반응형

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

 

현재 다니는 회사에서 사용하는 언어가 C#인데, 회사에 적용해도 좋을 것 같아서 C#을 사용했습니다.

 

일단 전체 검색대상 목록을 List<string>에 담았다고 가정하여, 

다음과 같은 List를 선언합니다.

List<string> words = new List<string> { "가나다", "가노", "가난", "가나두", "가나라", "고리", "기체", "깋체", "나다라", "다라마", "가나a", "a가나", "a가나다" };

 

그리고 검색어를 전체 구현이라면 앞단에서 받아야 겠지만, 콘솔을 통한 입력도 받지 않고, 선언을 하겠습니다.

 

검색어는 

string keyword1 = "ㄱ나ㄷ";
string keyword2 = "aㄱ나";

이렇게 두개로 하겠습니다.

 

컬렉션에서 어떤 조건을 만족하는 모든 값을 컬렉션으로 반환하는 함수인 FindAll()을 사용하도록 할텐데요,

words.FindAll(w => 어떤함수(w))로 사용할 어떤함수를 작성해야겠죠.

 

words.FindAll(word => 어떤함수( word )) 는 아래와 같은 기능을 한다고 이해하지면 됩니다. 실제로는 변수에 델리게이트를 사용하는 것이지만, 이해를 돕기 위해 작성한 것입니다.

List<string> 가짜FindAll(List<string> words){
    List<string> a = new();
    foreach(string word in words) 
    {
         if(어떤함수(word))
             a.Add(word)
    }
    return a;
}

 

그러면 어떻게 해야 초성에 맞는 한글들을 표현해낼 것인가를 고민해야겠죠.

 

우리는 char라는 자료형을 알고있죠. 유니코드로 숫자 범위로서 한글에 대한 범위를 지정해볼 수 있습니다.

따라서 검색어 중 어떤 글자가 'ㄱ', 'ㄴ' 등의 자음 초성일때, 가~깋, 나 ~닣가 포함되어있는지를 범위로 지정해볼 생각을 할 수 있습니다.

 

간단하게 검색어와 실제단어를 비교한다고 생각하고, n번째 글자가 ㄱ이면 원래 글자가 '가'와 '깋' 사이에 있는지 여부를 반환합니다.

if(검색어 중 n번째 글자 == 'ㄱ') {

  	원래 글자 중 n번째 글자 >= '가' && 원래 글자 중 n번째 글자 <= '깋'

}

 

받침 등이 자음마다 달라서 아주 일정한 규칙이 있는 것이 아니라서 이 작업을 ㄱ~ㅎ까지 모두 해주어야 합니다.

엑셀을 이용해도 됩니다.

 

그런데 우리는 switch 변수구문을 작성한 적이 있기 때문에 아래와 같이 작성합니다.

저 같은 경우는 ㄱ~ㅎ, 가~하, 깋~힣은 gpt에게 한글을 작성해달라고 하고, 엑셀에서 목록을 만들어서 다시 vs에 붙여넣기 했답니다. 초성이 아닌 경우에는 검색어와 원래 단어의 n번째 글자가 같으면 되기 때문에 _ => unicode == OriginChar로 작성하였습니다. OriginChar는 원래 목록에 있던 단어의 n번째 글자이고 unicode는 검색어의 n번째 글자입니다. 지금보니 변수명을 잘못지었지만, 이미 개발했으니 그대로 쓰겠습니다.

 

bool IsChosung(char OriginChar, char unicode) => unicode switch 
{
    'ㄱ' => '가' <= OriginChar && OriginChar <= '깋',
    'ㄴ' => '나' <= OriginChar && OriginChar <= '닣',
    'ㄷ' => '다' <= OriginChar && OriginChar <= '딯',
    'ㄹ' => '라' <= OriginChar && OriginChar <= '맇',
    'ㅁ' => '마' <= OriginChar && OriginChar <= '밓',
    'ㅂ' => '바' <= OriginChar && OriginChar <= '빟',
    'ㅅ' => '사' <= OriginChar && OriginChar <= '싷',
    'ㅇ' => '아' <= OriginChar && OriginChar <= '잏',
    'ㅈ' => '자' <= OriginChar && OriginChar <= '짛',
    'ㅊ' => '차' <= OriginChar && OriginChar <= '칳',
    'ㅋ' => '카' <= OriginChar && OriginChar <= '킿',
    'ㅌ' => '타' <= OriginChar && OriginChar <= '팋',
    'ㅍ' => '파' <= OriginChar && OriginChar <= '핗',
    'ㅎ' => '하' <= OriginChar && OriginChar <= '힣',
    'ㄲ' => '까' <= OriginChar && OriginChar <= '낗',
    'ㄸ' => '따' <= OriginChar && OriginChar <= '띻',
    'ㅃ' => '빠' <= OriginChar && OriginChar <= '삫',
    'ㅆ' => '싸' <= OriginChar && OriginChar <= '앃',
    'ㅉ' => '짜' <= OriginChar && OriginChar <= '찧',
    _ => unicode == OriginChar
};

 

이 작업을 검색어와 단어목록의 단어중 하나에서 글자단위로 진행해야하므로, foreach(char a in string값) 또는 for문에 대입하겠습니다.

 

여기서는 검색어와 단어를 index단위로 비교해야하므로, for문이 적절하고, 둘 중 짧은 곳에 맞추어 돌려야 하므로 조건에 둘다 작성합니다.

 

IsChosung의 값이 false이면 더이상 뒷부분을 확인하지 않아도 되므로, false를 반환하고 루프를 끝냅니다.

bool 어떤함수?(string keyword, string word){
    for (var i = 0; i < word.Length && i < keyword.Length; i++)
    {
        bool charResult = IsChosung(word[i], keyword[i]);
        if (!charResult)
        {
            return false;
        }
    }
    return true;
}

여기까지만 하면 문제가 발생하는데 찾으셨나요?

 

바로 검색어가 단어보다 길어도 앞부분까지만 매칭이 되면 true를 반환하는 문제가 생기죠.

 

그래서 앞에 길이를 비교하는 조건이 필요합니다.

bool 어떤함수?(string keyword, string word){
	if (keyword.Length > word.Length)
    {
        return false;
    }
    
    for (var i = 0; i < word.Length && i < keyword.Length; i++)
    {
        bool charResult = IsChosung(word[i], keyword[i]);
        if (!charResult)
        {
            return false;
        }
    }
    return true;
}

 

이제 출력의 시간입니다.

독자분들의 편의를 위해서 앞서 작성한 부분들도 기입하겠습니다.

 

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);


// foreach console.writeline 두번안쓰려고 선언한 함수
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 = IsChosung(word[i], keyword[i]);
        if (!charResult)
        {
            return false;
        }
    }
    return true;
}

bool IsChosung(char OriginChar, char unicode) => unicode switch 
{
    'ㄱ' => '가' <= OriginChar && OriginChar <= '깋',
    'ㄴ' => '나' <= OriginChar && OriginChar <= '닣',
    'ㄷ' => '다' <= OriginChar && OriginChar <= '딯',
    'ㄹ' => '라' <= OriginChar && OriginChar <= '맇',
    'ㅁ' => '마' <= OriginChar && OriginChar <= '밓',
    'ㅂ' => '바' <= OriginChar && OriginChar <= '빟',
    'ㅅ' => '사' <= OriginChar && OriginChar <= '싷',
    'ㅇ' => '아' <= OriginChar && OriginChar <= '잏',
    'ㅈ' => '자' <= OriginChar && OriginChar <= '짛',
    'ㅊ' => '차' <= OriginChar && OriginChar <= '칳',
    'ㅋ' => '카' <= OriginChar && OriginChar <= '킿',
    'ㅌ' => '타' <= OriginChar && OriginChar <= '팋',
    'ㅍ' => '파' <= OriginChar && OriginChar <= '핗',
    'ㅎ' => '하' <= OriginChar && OriginChar <= '힣',
    'ㄲ' => '까' <= OriginChar && OriginChar <= '낗',
    'ㄸ' => '따' <= OriginChar && OriginChar <= '띻',
    'ㅃ' => '빠' <= OriginChar && OriginChar <= '삫',
    'ㅆ' => '싸' <= OriginChar && OriginChar <= '앃',
    'ㅉ' => '짜' <= OriginChar && OriginChar <= '찧',
    _ => unicode == OriginChar
};

 

결과는 다음과 같습니다.

 

영어를 포함해도 뒤에 초성이 바르게 검색되는 것을 확인했습니다.

 

 

 

반복되는 형태의 문장들인데 계산식으로 만들 수 없을까? 궁금하신 분들은 2편을 참고해주세요.

https://pichen.tistory.com/57

 

[C#] 초성을 포함한 StartWith 함수 만들기 2편, char의 유니코드 값으로 계산해보자.

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

pichen.tistory.com

 

 

반응형

+ Recent posts