일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- delegation
- NumberFormatter
- SWIFT
- 독후감
- Mock
- mvvm
- Failed to register bundle identifier
- 책후기
- UIResponder
- 독서후기
- NotificationCenter
- Structures and Classes
- human interface guidelines
- viewcontroller
- Modality
- 스위프트
- contentInset
- roundingMode
- 부트캠프
- @available
- View Life Cycle
- 스타트업주니어로살아남기
- Navigation
- Codegen
- IOS
- 야곰아카데미
- Info.plist
- 아이폰
- SWIFTUI
- xcode
- Today
- Total
호댕의 iOS 개발
[Swift] 초성에 따라 글자 분리하기 (한글 초성 분리) 본문
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로 나누면 어떤 초성인지 알 수 있게 된다.
따라서 위와 같은 코드가 나오게 됐다!!
이렇게 하면 한글을 포함해서 문자열의 초성을 파악할 수 있게 된다!