개발자 중 누군가 초성을 포함하여 검색하는 방법을 물었는데, 시간이 남아서 개발을 했다가 초안이 맘에 안들어서 퇴근 후 공부를 마친 시간에 보완작업을 해보았습니다.
현재 다니는 회사에서 사용하는 언어가 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편을 참고해주세요.
[C#] 초성을 포함한 StartWith 함수 만들기 2편, char의 유니코드 값으로 계산해보자.
"[C#] 초성을 포함한 StartWith 함수 만들기" (링크: https://pichen.tistory.com/56 ) 글에서 ㄱ부터 ㅎ까지를 모두 직접 적어서 switch 돌리는 것에 계산식이 있지 않겠냐고 하는 반응을 보았습니다. [C#] 초성
pichen.tistory.com
'language & Framework > C#' 카테고리의 다른 글
[C#] 깊이 우선 탐색 (DFS) 개발 적용 사례 - 폴더구조 찾기 (5) | 2024.12.17 |
---|---|
[C#] 초성을 포함한 StartWith 함수 만들기 2편, char의 유니코드 값으로 계산해보자. (0) | 2024.05.09 |
[c# / .NET] 설정 파일을 읽는 법에 대하여(.json/.ini) (0) | 2024.03.19 |