태그 보관물: string

UnicodeScalar

UnicodeScalar

유니코드 문자 1개에 해당하는 코드 포인트값을 담고 있는 데이터이다. UnicodeScalar는 4바이트 유니코드 값 1개에 대응되기 때문에 UInt32 타입으로 변환하거나, UInt32값으로부터 생성해낼 수 있다.

let i:Uint32 = 0xac01
let c = UnicodeScalar(i)
print(c)
let v:UInt32 = c.value

Swift의 Character 타입은 글자 1개를 나타내는 자료형이지만, 유니코드 문자 중에서는 여러 개의 코드 값이 하나로 합쳐진 글자들이 있기 때문에 1개의 Character 타입 값은 1개 혹은 그 이상의 UnicodeScalar 값으로 표현된다.

예를 들어 한글 "학"은 1개의 글자로 그 자체로 유니코드 코드포인트 값 1개에 대응될 수도 있지만, 한글자모 "ㅎ" + "ㅏ" + "ㄱ"의 조합일 수도 있다. 이 경우에는 코드 포인트값은 3개이지만, 글자로는 1개로 취급된다.

예를 들어 한글 “학”의 경우를 보자.

let a = "학"
let c = a.unicodeScalars[a.unicodeScalars.startIndex].value
print(String(format:"0x%x", c)) 
// 0xd559 (45617)

print("학 ==> 학")
print("글자 수: \(a.characters.count)") // 1
print("코드 수: \(a.unicodeScalars.count)") // 
print("\n")

"학"의 유니코드 코드포인트 값은 45617로 하나의 코드로 된 문자이다. 그리고 당연히 1개의 글자로 되어 있다. 이를 유니코드의 초중종성 분리를 통해 각각의 자모로 분리해보자.

//  각각의 자모 인덱스로 분리
let x = (c - 0xac00) / 28 / 21
let y = ((c - 0xac00) / 28) % 21
let z = (c - 0xac00) % 28

//  각각의 자모 인덱스로부터 자모 코드값 생성
let i = UnicodeScalar(0x1100 + x) // 0x1100 -> 초성 'ㄱ'
let j = UnicodeScalar(0x1161 + y) // 0x1161 -> 중성 'ㅏ'
let k = UnicodeScalar(0x11a6 + 1 + z) // 0x11A6 -> 종성 {받침없음}

print("분해된 자모: \(i), \(j), \(k)") 
//  분해된 자모: ㅎ, ㅏ, ㄱ

이 때 분해된 각 자모의 값은 0x1112, 0x1161, 0x11A8이 된다. 이 자모코드값을 이용해서 문자열 "학"은 다음과 같이 쓸 수 있다.

let compositedString = "\u{1112}\u{1161}\u{11A8}"
print("자모코드를 합성한 문자열: \(compositedString)")
//  학

이 세 개의 자모 코드는 하나의 문자를 표시하도록 조합되기 때문에, 표현상 한 글자 짜리 문자열이 된다.

print("ㅎㅏㄱ ==> 학")
print("글자 수: \(compositedString.characters.count)") // 1
print("코드 수: \(compositedString.unicodeScalars.count)") // 3

하지만 글자 수는 1개이지만 학(0xD559)와 달리 3개의 코드포인트로 저장되어 있고 표시하는 글자는 같다. 그리고 이 두 개는 문자열로 표현되는 경우 내부에 저장된 데이터는 각각 다르지만

print(a == compositedString)
//  true

동일한 값으로 간주된다.

다루는 방법

문자열 타입에서 .unicodeScalars 프로퍼티를 이용해서 해당 문자열의 유니코드 코드 포인트뷰를 얻을 수 있다. 이는 UnicodeScalarView 타입으로, Slicable, SequenceType, ExtensibleCollectionType, RangeReplaceableColloectionType 등의 프로토콜을 따르고 있어서 배열과 유사하게 서브스크립팅을 하거나 append, insert, remove등의 조작을 통해 manipulate할 수 있다.

또한 앞서 언급한 바와 같이 내부적으로 하나의 코드 포인트는 UInt32 타입의 정수값으로 취급되므로, 유니코드 스칼라 타입을 이용하여 유니코드 코드값을 문자로 변환하는 것도 가능하다.

String in Swift

Swift의 문자열

수정 (2015. 12. 07) : Swift2.0에서부터 문자열은 더 이상 Collection 타입이 아니므로 [Character] 타입으로 변경할 수 없으며, advance() 함수 역시 전역 함수에서 제거되고 Index 타입의 메소드로 변경되었다. NSString은 유니코드 문자열을 UTF16으로 인코딩한 바이트배열로 문자를 다루는데 비해, Swift의 문자열은 유니코드 문자열의 복잡 다단한 특성들을 정확히 반영하기 위해 애써서 디자인한 흔적들이 눈에 띈다. 보다 자세한 내용에 대해서는 따로 포스팅하겠다.

Swift의 문자열은 유니코드 문자열이고, 유니코드의어떤 특징들(여러개의 스칼라 코드가 하나의 문자로 결합하는 등)로 인해서 내부적으로는 단순 배열이 아니다. 따라서 Swift의 문자열은 인덱스에 의한 랜덤액세스를 지원하지 않는다. 즉, NSStringcharacterAtIndex:와 동일한 메소드를 지원하지 않는다. 컨셉상, Swift의 문자열은 배열보다는 양방향 리스트에 더 까운 구조이다.

문자열 내 개별문자(Character)와 범위의 인덱스들은 String.Index라는 불투명 타입에 의해 구현되며, 이 타입은 BidirectionalIndex라는 프로토콜 타입이다. 따라서 특정 위치의 문자를 얻기 위해서는 먼저 해당 문자열에 startIndex 값을 물어본 다음, 표준 함수인 —advance()를 사용하여 해당 위치 인덱스로 순차적으로 이동하고– 이 시작 인덱스의 advancedBy() 메소드를 이용해서 해당 위치로부터 원하는 만큼의 오프셋을 준 인덱스를 구한 후, 이 인덱스를 이용한 subscription으로 특정 위치의 문자를 구할 수 있다. (참 어렵게도 해놨다)

let digits = "0123456789"
let position = 3
let index = digits.startIndex.advancedBy(position)
let character = digits[index]

이를 이용하면 문자열의 뒤에서부터 접근하는 경우에

let index2 = digits.endIndex.advancedBy( -1 * position)
let character2 = digits[index2]

이런 식으로 advance() 함수의 두 번째 인자에 음수값을 주어 뒤에서부터 세어 나갈 수 있다.

메모리를 좀 더 많이 쓰는 대신 타이핑의 수고로움을 더는 방법으로는 문자열을 배열로 캐스팅하면 Character의 배열을 얻게 된다는 점을 착안하여,

let character3 = Array(digits)[4]

라고 쓰는 방법도 있긴하다.

Swift 2.0에서 문자열은 더 이상 집합 타입이 아니며, 따라서 [Characters] 타입으로 변환되지 않는다. 이는 문자열의 개수를 세는 countElement() 등의 함수의 비효율성을 극복하기 위해서, 단일 문자열이 여러 가지 타입으로 인코딩된 데이터 맵을 가지는 형태로 변환되었다.

좀 더 세련된 방법으로 문자열을 정수 인덱스를 지원하도록 서브스크립트를 확장하는 방법이 있다.

extension String {
    subscript(integerIndex: Int) -> Character {
        if integerIndex >= 0 {
            let index = startIndex.advancedBy(integerRange.startIndex)
            return self[index]
        }

    }

    subscript(integerRange: Range<Int>) -> String {
        let start = startIndex.advancedBy(integerRange.startIndex)
        let end = startIndexBy(integerRange.endIndex)
        let range = start..<end
        return self[range]
    }
}

위 서브스크립트에서 정수 인덱스가 음수인 경우, 파이썬과 비슷하게 뒤쪽에서부터 문자를 출력하도록 하는 것도 가능할 것이다.