한글조합기를 AI를 통해서 만들었다..
가상키보드를 만든다던가.. 뭐.. 이런..
여튼 잘쓰셈.. ㅋㅋ
public class HangulComposer{ // 기본 테이블 static readonly char[] CHO = new[] { 'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ' }; static readonly char[] JUNG = new[] { 'ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ', 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ', 'ㅙ', 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ', 'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ', 'ㅣ' }; static readonly string[] JONG = new[] { "", "ㄱ", "ㄲ", "ㄳ", "ㄴ", "ㄵ", "ㄶ", "ㄷ", "ㄹ", "ㄺ", "ㄻ", "ㄼ", "ㄽ", "ㄾ", "ㄿ", "ㅀ", "ㅁ", "ㅂ", "ㅄ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ" }; // 겹받침 맵 static readonly Dictionary<string, string> FinalComposite = new() { {"ㄱㅅ","ㄳ"}, {"ㄴㅈ","ㄵ"}, {"ㄴㅎ","ㄶ"}, {"ㄹㄱ","ㄺ"}, {"ㄹㅁ","ㄻ"}, {"ㄹㅂ","ㄼ"}, {"ㄹㅅ","ㄽ"}, {"ㄹㅌ","ㄾ"}, {"ㄹㅍ","ㄿ"}, {"ㄹㅎ","ㅀ"}, {"ㅂㅅ","ㅄ"} }; // 겹모음 맵 static readonly Dictionary<string, char> MedialComposite = new() { {"ㅗㅏ",'ㅘ'}, {"ㅗㅐ",'ㅙ'}, {"ㅗㅣ",'ㅚ'}, {"ㅜㅓ",'ㅝ'}, {"ㅜㅔ",'ㅞ'}, {"ㅜㅣ",'ㅟ'}, {"ㅡㅣ",'ㅢ'} }; // 이중초성(선택) static readonly Dictionary<string, char> InitialComposite = new() { {"ㄱㄱ",'ㄲ'}, {"ㄷㄷ",'ㄸ'}, {"ㅂㅂ",'ㅃ'}, {"ㅅㅅ",'ㅆ'}, {"ㅈㅈ",'ㅉ'} }; // 내부 버퍼 private List<char> jamoBuffer = new List<char>(); private StringBuilder committed = new StringBuilder(); private string preedit = ""; // 입력 public void Input(char c) { if (IsJamo(c)) { jamoBuffer.Add(c); RecomposeAndMaybeCommit(); } else { CommitAll(); committed.Append(c); preedit = ""; } } // 백스페이스 public void Backspace() { if (jamoBuffer.Count > 0) { jamoBuffer.RemoveAt(jamoBuffer.Count - 1); RecomposeAndMaybeCommit(); return; } if (committed.Length > 0) committed.Remove(committed.Length - 1, 1); } // 현재까지 확정된 문자열 public string Committed => committed.ToString(); // 현재 미완성(조합 중) 문자열 public string Preedit => preedit; // 확정 문자열 + 조합중 문자열 public String GuessWord() { return Committed + Preedit; } //클리어 public void Clear() { jamoBuffer.Clear(); committed.Clear(); preedit = ""; } // 강제 커밋 public void CommitAll() { var parsed = ParseJamos(jamoBuffer); foreach (var p in parsed) committed.Append(p.composed); jamoBuffer.Clear(); preedit = ""; } // --- 내부 로직 --- private void RecomposeAndMaybeCommit() { var parsed = ParseJamos(jamoBuffer); if (parsed.Count == 0) { preedit = ""; return; } // 마지막 음절 제외하고 모두 확정 int stableCount = Math.Max(0, parsed.Count - 1); int removeJamoCount = 0; for (int i = 0; i < stableCount; i++) { committed.Append(parsed[i].composed); removeJamoCount += parsed[i].consumed; } if (removeJamoCount > 0) jamoBuffer.RemoveRange(0, removeJamoCount); // 마지막 항목은 미완성 preedit 으로 유지 preedit = parsed.Last().composed; } private List<(string composed, int consumed)> ParseJamos(List<char> arr) { var res = new List<(string composed, int consumed)>(); int i = 0; while (i < arr.Count) { char cur = arr[i]; // 모음 시작 처리 (수정: 모음 단독일 경우 초성 없이 모음만 표시) if (IsVowel(cur)) { int consumed = 1; char medial = cur; // 겹모음 체크 if (i + 1 < arr.Count && IsVowel(arr[i + 1])) { string mk = $"{cur}{arr[i + 1]}"; if (MedialComposite.ContainsKey(mk)) { medial = MedialComposite[mk]; consumed = 2; } } // 종성 처리 string finalStr = ""; // 모음 단독인 경우 (뒤에 자음 없거나, 모음 다음에 자음이 초성일 가능성 있는 경우) // 여기서는 “모음만 단독” 표시 위해 초성 없이 모음 문자 그대로 출력 // (예: ㅜ, ㅏ 같은 단독 모음) if (consumed == arr.Count - i) { res.Add((new string(medial, 1), consumed)); i += consumed; continue; } // 기존 종성 판단 유지 if (i + consumed < arr.Count && IsConsonant(arr[i + consumed])) { if (i + consumed + 1 < arr.Count && IsConsonant(arr[i + consumed + 1])) { string pk = $"{arr[i + consumed]}{arr[i + consumed + 1]}"; if (FinalComposite.ContainsKey(pk)) { if (i + consumed + 2 < arr.Count && IsVowel(arr[i + consumed + 2])) { finalStr = arr[i + consumed].ToString(); consumed += 1; } else { finalStr = FinalComposite[pk]; consumed += 2; } } else { if (i + consumed + 2 < arr.Count && IsVowel(arr[i + consumed + 2])) { // 다음 초성으로 넘어감 } else { finalStr = arr[i + consumed].ToString(); consumed += 1; } } } else { if (i + consumed + 1 < arr.Count && IsVowel(arr[i + consumed + 1])) { // 다음 초성으로 넘어감 } else { finalStr = arr[i + consumed].ToString(); consumed += 1; } } } // 초성 없이 조합 if (finalStr == "") { res.Add((new string(medial, 1), consumed)); } else { res.Add((Combine('ㅇ', medial, finalStr), consumed)); } i += consumed; continue; } // 자음 시작 처리 if (IsConsonant(cur)) { if (i + 1 < arr.Count && IsVowel(arr[i + 1])) { char onset = cur; int usedOnset = 1; if (i + 1 < arr.Count && IsConsonant(arr[i + 1]) && i + 2 < arr.Count && IsVowel(arr[i + 2])) { string ik = $"{arr[i]}{arr[i + 1]}"; if (InitialComposite.ContainsKey(ik)) { onset = InitialComposite[ik]; usedOnset = 2; } } char medial = arr[i + usedOnset]; int consumed = usedOnset + 1; if (i + consumed < arr.Count && IsVowel(arr[i + consumed])) { string mk = $"{medial}{arr[i + consumed]}"; if (MedialComposite.ContainsKey(mk)) { medial = MedialComposite[mk]; consumed += 1; } } string finalStr = ""; if (i + consumed < arr.Count && IsConsonant(arr[i + consumed])) { if (i + consumed + 1 < arr.Count && IsConsonant(arr[i + consumed + 1])) { string pk = $"{arr[i + consumed]}{arr[i + consumed + 1]}"; if (FinalComposite.ContainsKey(pk)) { if (i + consumed + 2 < arr.Count && IsVowel(arr[i + consumed + 2])) { finalStr = arr[i + consumed].ToString(); consumed += 1; } else { finalStr = FinalComposite[pk]; consumed += 2; } } else { if (i + consumed + 2 < arr.Count && IsVowel(arr[i + consumed + 2])) { // 다음 초성으로 넘어감 } else { finalStr = arr[i + consumed].ToString(); consumed += 1; } } } else { if (i + consumed + 1 < arr.Count && IsVowel(arr[i + consumed + 1])) { // 다음 초성으로 넘어감 } else { finalStr = arr[i + consumed].ToString(); consumed += 1; } } } res.Add((Combine(onset, medial, finalStr), consumed)); i += consumed; continue; } // 자음 단독 res.Add((cur.ToString(), 1)); i += 1; continue; } // 그 외 문자 (에러 방지용) res.Add((cur.ToString(), 1)); i += 1; } return res; } private string Combine(char cho, char jung, string jong) { int choIndex = Array.IndexOf(CHO, cho); if (choIndex < 0) choIndex = Array.IndexOf(CHO, 'ㅇ'); int jungIndex = Array.IndexOf(JUNG, jung); if (jungIndex < 0) jungIndex = 0; int jongIndex = Array.IndexOf(JONG, jong); if (jongIndex < 0) jongIndex = 0; int code = 0xAC00 + (choIndex * 21 + jungIndex) * 28 + jongIndex; return ((char)code).ToString(); } private bool IsVowel(char c) => Array.IndexOf(JUNG, c) >= 0 || MedialComposite.Values.Contains(c); private bool IsConsonant(char c) => Array.IndexOf(CHO, c) >= 0 || InitialComposite.Values.Contains(c); private bool IsJamo(char c) => IsVowel(c) || IsConsonant(c);}
활용 예시..
HangulComposer hc = new HangulComposer();
hc.Input('ㅈ');
Console.WriteLine(hc.GuessWord());
hc.Input('ㅗ');
Console.WriteLine(hc.GuessWord());
hc.Input('ㅈ');
Console.WriteLine(hc.GuessWord());
hc.Input('ㄲ');
Console.WriteLine(hc.GuessWord());
hc.Input('ㅏ');
Console.WriteLine(hc.GuessWord());
hc.Input(' ');
Console.WriteLine(hc.GuessWord());
hc.Input('ㅅ');
Console.WriteLine(hc.GuessWord());
hc.Input('ㅡ');
Console.WriteLine(hc.GuessWord());
hc.Input('B');
Console.WriteLine(hc.GuessWord());
hc.Input('u');
Console.WriteLine(hc.GuessWord());
hc.Input('l');
Console.WriteLine(hc.GuessWord());
//
string strr = hc.GuessWord();