[Swift] 문자열의 각 문자 요소에 접근하기

이정훈·2023년 3월 3일
0

Swift 파헤치기

목록 보기
4/10
post-thumbnail

다른 언어에서 문자열을 다루는 방법

예를 들어 Python에서 문자열에서 1번 문자를 출력하려면 다음과 같다.

s = "hello"
print(s[1])    # e

Python 뿐만 아니라 C언어에서도 비슷한 형식으로 사용한다.

Swift에서는?

하지만 Swift에서 위와 같은 방식으로 접근한다면..

let s: String = "hello"
print(s[1])    //에러

에러가 발생한다.

Swift에서는 0, 1, 2..와 같은 Int 타입 숫자로 문자열의 각 문자 요소에 접근할 수 없다.

왜 이런일이?

String 타입
Character 타입

Apple 공식 문서에 소개 되어 있기를 Swift에서 String 타입은 Character 타입의 집합이고 Character 타입은 UnicodeScalar 값의 집합이다.

UnicodeScalar 값은 Unicode에서 사용하는 21-bit의 정수 값으로 문자 하나를 나타내는데 사용하는 고유 값을 말한다.

ex) "A" == U+0041, "😀" == U+1F600

그 결과 아래와 같은 상황이 발생한다.

let A: String = "A"
print("string length: \(A.utf8.count)")    //string length: 1
print("string length: \(A.unicodeScalars.count)")    //string length: 1

let cat: String = "🐱"
print("string length: \(cat.utf8.count)")    //string length: 4
print("string length: \(cat.unicodeScalars.count)")    //string length: 1

let firstName: String = "김"
print("string length: \(firstName.utf8.count)")    //string length: 3
print("string length: \(firstName.unicodeScalars.count)")    //string length: 1

이렇게 영어로 되어 있는 문자열은 상관 없지만 문자열에 영어가 아닌 이모티콘이나 한글 등이 포함된 경우 일반적으로 많이 쓰이는 utf-8로 인코딩 하면 문자의 크기가 유동적이다.

그럼 "UnicodeScalar를 기준으로 문자열에 접근하면 되지 않을까?" 라고 할 수 있다.

let family: String = "👨‍👩‍👧‍👧"
print("string length: \(family.utf8.count)")    //string length: 25
print("string length: \(family.unicodeScalars.count)")    //string length: 7

이모티콘에 따라서도 문자의 크기는 1이지만 UnicodeScalar가 무조건 1이 아닌 경우도 존재한다.

결론은 UnicodeScalar 자체이던 UTF-8 형식으로 인코딩하던 혹은 UTF-16 형식으로 인코딩 하던 어떤 포맷이더라도 문자의 크기가 가변적이기 때문에 문자의 위치를 가져오기 위한 어떤 포맷을 한 기준으로 사용할 수 없다는 것이다.

String subscript

대신 대안으로 String.Index 타입을 인자로 받는 subscript가 존재한다.

해당 subscript의 선언은 다음과 같다.

extension String : BidirectionalCollection {
	...
    public subscript(i: String.Index) -> Character { get }

String.Index 타입을 매개변수로 전달 받아 Character를 반환한다.

그렇다면 위의 예시를 다시 이렇게 접근할 수 있다.

let s: String = "hello"    
let i: String.Index = //String.Index 타입 값
print(s[i])

이렇게 하면 i번째 위치에 있는 문자를 subscript를 통해 반환 받을 수 있다.

그렇다면 String.Index 타입의 값은 어떻게 얻을 수 있을까?

이것을 얻기 위한 String 타입의 여러 메서드가 존재한다. 그 중에서 몇가지만 알아보려고 한다.

startIndex

해당 메서드는 문자열의 첫번째 위치의 String.Index를 반환한다.

let s: String = "hello"

let i: String.Index = s.startIndex
print(s[i])   //h

index(before:)

해당 메서드는 매개변수로 전달된 특정 위치의 바로 앞 위치를 반환한다.

let s: String = "hello"
<let b: String.Index = s.index(before: k)
print(s[b])    //l

index(after:)

해당 메서드는 매개변수로 전달된 특정 위치의 바로 뒤 위치를 반환한다.

let s: String = "hello"
let a: String.Index = s.index(after: s.startIndex)
print(s[a])    //e

index(_:offsetBy:)

해당메서드는 첫번째 매개변수로 특정 위치를 전달하면 해당 값으로 부터 offsetBy 만큼 위치에 있는 위치를 반환한다.

주로 첫번째 매개변수에는 0번 위치인 startIndex의 반환 값을 전달한다.

let s: String = "hello"
let i: String.Index = s.index(s.startIndex, offsetBy: 0)
print(s[i])    //h

let j: String.Index = s.index(s.startIndex, offsetBy: 4)
print(s[j])    //o

firstIndex(of:)

특정 문자의 첫번째 위치를 알고 싶다면 firstIndex(of:)를 사용할 수 있다.

주의할 점은 firstIndex(of:)는 존재하지 않는 문자가 전달인자로 전달되는 것을 방지하기 위해 반환 값이 Optional이므로 Optional에 대한 처리를 해줘야 한다.

let str: String = "hello"
if let i = str.firstIndex(of: "h") {
    print(str[i])   //h
}

endIndex

endIndex는 문자열의 마지막 문자의 위치보다 +1 큰 위치를 반환한다.

let str: String = "hello"
let b: String.Index = str.index(before: str.endIndex)
print(str[b])    //o

substring(문자열 슬라이싱)

마찬가지로 일반적인 Int 타입 숫자로는 문자열 슬라이싱도 불가능하다.

let s: String = "hello"
print(s[1...3])    //에러

하지만 위에서 본 String.Index 타입으로 슬라이싱이 가능하다.

let s: String = "hello"
let i: String.Index = s.startIndex
let a: String.Index = s.index(after: s.startIndex)

print(s[i...a])    //he

문자열 순회

문자열의 문자를 하나씩 순회하는 방식은 for-in 구분으로 배열을 순회하는 방식과 동일한 방법으로 접근할 수 있다.

let str: String = "hello"

for s in str {
    print(s)
}

//h
//e
//l
//l
//o
profile
새롭게 알게된 것을 기록하는 공간

0개의 댓글