호댕의 iOS 개발

[Swift] 초성에 따라 글자 분리하기 (한글 초성 분리) 본문

Software Engineering/Swift

[Swift] 초성에 따라 글자 분리하기 (한글 초성 분리)

호르댕댕댕 2023. 9. 23. 16:29

String 배열에서 초성이 동일한 문자열끼리 묶어서 2차원 배열로 만드는 방식이 필요했다. 

 

이 작업을 하면서 실제로 자료구조 / 알고리즘이 직접적으로 사용이 되는구나 싶었다. 사실 뷰를 그리거나 데이터를 파싱하거나 할 때에는 직접적으로 자료구조 / 알고리즘을 사용할 일은 없었으나, 이번에 필요성을 몸소 느끼게 되었다. 

 

어찌됐든 한글 / 영어 / 숫자로 시작하는 문자열들을 같은 문자(한글의 경우 초성)로 시작하는 문자열로 변경하기 위해선 일단 초성을 가지고 오는 방식이 필요했다. 

 

단순히 영문으로만 되어 있었다면 아래처럼 해도 시작 글자가 동일한지 알 수 있었을 것이다. 

func getFirstCharacter(word: String) -> String {
    return word.first?.uppercased() ?? ""
}

여기서 대소문자 관계 없이 동일한 초성으로 파악할 수 있도록 이렇게 구현을 했다. 

 

하지만...!

우리는 한글을 쓰니 한글의 경우도 저 함수를 통해 초성을 뽑아낼 수 있을까

 

var words = ["가방", "가위", "기린", "바나나", "나방", "라디오"]

func getFirstCharacter(word: String) -> String {
    return word.first?.uppercased() ?? ""
}

words.forEach {
    print(getFirstCharacter(word: $0))
}

// 가
// 가
// 기
// 바
// 나
// 라

이런 식으로 모음까지 붙어서 나오게 되고 이렇게 하면 초성을 기준으로 묶는다는 목적을 달성할 수 없다. 

 

그래서 다른 방식에 대해 고민을 해보게 됐다. 

 

일단 ASCII 코드! 

아스키코드는 영문 알파벳을 바탕으로 사용되는 문자 인코딩 방법이다. 

 

 

따라서 한글이 문제였던 상황은 해결이 불가능하다.

그럼 한글을 이렇게 변환할 수 있는 방법은...?

 

바로 유니코드!

 

이런 식으로 한글부터 이모지까지 전부 표시가 가능한 표준 문자 전산 처리 방식이다. 

그럼 이를 활용해서 어떻게 한글인지 파악하고 초성을 가지고 올 수 있을까?

 

한글인지 파악하는 방법

이는 Unicode.Scalar를 사용하면 된다! 

https://developer.apple.com/documentation/swift/unicode/scalar

 

Unicode.Scalar | Apple Developer Documentation

A Unicode scalar value.

developer.apple.com

UnicodeScalar는 동일한 타입을 typealias로 지정해놓은 것이다. 

 

그럼 이 타입을 활용해 어떻게 파악할 수 있을까?

 

한글의 첫 글자와 가장 마지막 글자를 생각해보면 일 것이다. 

 

자음은 ㄱ ㄴ ㄷ ㄹ ••• ㅍ ㅎ 로 구성이 되어 있고 모음은 ㅏ ㅑ ••• ㅡ ㅣ로 구성이 되어 있기 때문이다. 

 

가의 유니코드는 AC00 (10진법 value 44032)이고 힣의 유니코드는 D7A3 (10진법 value 55203)이다. 

그래서 Unicode.Scalar로 첫 글자를 변환한 후 이 범위 안에 있는지 확인하면 된다. 

 

private func isKoreanCharacter(_ character: Character) -> Bool {
    let hangulRange = UnicodeScalar("가")...UnicodeScalar("힣")
    return hangulRange.contains(character.unicodeScalars.first!)
}

이런 식으로 말이다. 

 

저기서 10진법 value를 알고 싶다면 

Unicode.Scalar("가").value

이런 식으로 뒤에 value를 붙여 확인이 가능하다. 

이 값은 UInt32 타입이다. 

 

초성만 추출하는 함수

특정 글자가 한국어인지 파악하는 방법은 이제 알았다. 

그런데 UnicodeScalar를 이용해도 초성을 알 순 없다. 

 

하나의 자음 모음 조합에 대한 Unicode 값만 알 수 있는 것이다. 

 

일단 한글이 아닌 경우 앞 글자만 가지고 오면 되고 대 소문자 관계 없이 앞 글자가 어떤 것인지가 중요하니 

위에서 만든 isKoreanCharacter 함수를 통해 한글이 아니면 파라미터로 받은 Character의 uppercased를 통해 대문자 문자열을 반환할 수 있도록 했다. 

 

if !isKoreanCharacter(character) {
    return character.uppercased()
}

그렇다면 초성은 어떻게 뺄 수 있을까?

일단 초성으로 올 수 있는 자음은 다음과 같다. 

 

let hangulInitialList = [
    "ㄱ", "ㄲ", "ㄴ", "ㄷ", "ㄸ", "ㄹ", "ㅁ", "ㅂ", "ㅃ", "ㅅ",
    "ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ"
]

그리고 한글에서 Unicode 가장 첫 글자는 위에서 언급했듯 "가"이고 "가"의 Unicode 10진법 값은 44032이다. 

private func getInitialConsonant(_ character: Character) -> String {
    if !isKoreanCharacter(character) {
        return character.uppercased()
    }

    let hangulBase: UInt32 = 44032 // "가"의 유니코드 "\u{AC00}" value
    let unicodeScalar = character.unicodeScalars.first!
    let value = unicodeScalar.value - hangulBase

    let initialList = [
        "ㄱ", "ㄲ", "ㄴ", "ㄷ", "ㄸ", "ㄹ", "ㅁ", "ㅂ", "ㅃ", "ㅅ",
        "ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ"
    ]
    let index = Int(value / 588) // 초성 인덱스 가져오기 - 중성 21개 종성 28개

    return initialList[index]
}

그래서 구현한 코드는 다음과 같다. 

 

초성을 구해야 하는 글자의 첫 글자 10진법 유니코드 값을 구한 뒤 가의 10진법 유니코드 값을 빼서 얼마나 차이가 있는지 구한다. 그리고 하나의 자음 당 올 수 있는 경우의 수는 다음과 같다. 

 

한글 중성으로 올 수 있는 값 (21개)
ㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ

 

한글 종성으로 올 수 있는 값 (28개)

ㄱㄲㄳㄴㄵㄶㄷㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅄㅅㅆㅇㅈㅊㅋㅌㅍㅎ

 

잉...? 그런데 종성으로 올 수 있는 값이 세어보니 27개이다. 

고민을 하다... 아 종성에는 아무것도 안 올 수 있구나... 란 걸 깨달았다. (ex: 가)

 

그럼 초성 하나당 올 수 있는 경우의 수는 21 * 28 인 588이다. 

 

따라서 초성을 구해야 하는 글자의 첫 글자 10진법 유니코드 값을 구한 뒤 가의 10진법 유니코드 값을 빼서 얼마나 차이가 있는지 구하고 588로 나누면 어떤 초성인지 알 수 있게 된다. 

 

따라서 위와 같은 코드가 나오게 됐다!! 

 

이렇게 하면 한글을 포함해서 문자열의 초성을 파악할 수 있게 된다! 

Comments