Swift

[Swift] subscript를 사용해서 문자열도 배열처럼 쉽게 접근하자

Jade-Lee 2024. 3. 30. 15:42

 

안녕하세요!

오늘은 subscript 문법을 사용해서 문자열도 배열처럼 쉽게 접근해 볼 거예요.

그전에 배열과 문자열에서 요소 접근 방법이 어떻게 다른지 확인해 볼까요?

 

 

배열, 문자열에서는 특정 요소에 어떻게 접근할까?

 

우리는 배열에서 n번째 위치, 또는 특정 범위에 있는 요소에 접근할 때는 다음과 같이 쉽게 접근할 수 있어요.

var array = [1, 2, 3, 4, 5]

// 2번째 요소에 접근
print(array[2]) // 3

// 2~4번째 요소에 접근
print(array[2...4]) // [3, 4, 5]

 

그러면 문자열에서는 어떻게 접근하는지 한번 확인해 볼까요?

var string = "12345"

// 문자열 내 2번째 요소에 접근
string[string.index(string.startIndex, offsetBy: 2)]
// "3" (여기서는 Character 타입으로 리턴됨)

// 문자열 내 2~4번째 요소에 접근
let startIndex = string.index(string.startIndex, offsetBy: 2)
let endIndex = string.index(string.startIndex, offsetBy: 4)
string[startIndex...endIndex]
// "345"

 

배열에서 특정 위치, 범위에 있는 요소에 접근할 때는 Int 타입을 통해 접근할 수 있었지만,

문자열에서는 String.Index 타입을 사용해야 해요.

그래서 위와 같이 코드가 꽤나 복잡해지고 길어진 것이죠.

(Swift에서는 문자열 다루기가 쉽지 않네요...ㅠㅠ)

 

그래서 저는 문자열도 String.Index가 아닌 Int 타입으로 접근해보려고 해요!

그래서 필요한 것이 바로 subscript 랍니다~.

 

subscript에 대해 아주 간단하게 설명드리자면 대괄호([ ])를 사용하는 문법이에요.

위에서 보신 예시처럼 배열, 문자열 내 특정 위치에 접근할 때 대괄호를 사용했어요.

각 타입의 내부 살펴보면 subscript가 구현된 것을 확인할 수 있어요.

 

(Apple Swift 공식 문서에서 subscript를 검색한 결과)

 

문자열에는 Int 타입으로 문자열 접근하는 subscript가 정의되지 않았기 때문에

이를 직접 구현 해보도록 하죠!

 

 

문자열의 n번째 위치에 접근하는 subscript 정의

이거는 아까 위에서 작성했던 코드를 그대로 String 타입 내에서 subscript로 정의해주면 돼요!

extension String {
    subscript(index: Int) -> Self {
        get {
            let stringIndex = self.index(self.startIndex, offsetBy: index)
            return String(self[stringIndex])
        }
    }
}

var string = "12345"
print(string[2]) // "3"

 

n번째 위치에 접근하기 위해 매개변수 index를 정의해주고 String 타입으로 리턴해주는 것이죠!

(문자열 n번째 요소에 접근할 때 리턴 타입은 Character 타입인데, 여기서는 String 타입으로 리턴해줬습니다.)

리턴 타입이 String 타입이기 때문에 마지막에 Character 타입을 String 타입으로 변환해주었습니다.

 

 

문자열의 특정 범위에 접근하는 subscript 정의

특정 범위에 있는 요소들을 가져올때도 위와 같은 방법으로 사용하면 돼요!

다만, 이 경우에는 매개변수 타입으로 범위를 나타내는 타입을 사용해야돼요.

범위를 나타내는 타입의 종류로는 다음과 같이 있답니다.

 

(a..<b의 범위를 나타냄.)
(a...b의 범위를 나타냄.)
(a...의 범위를 나타냄)
(...a의 범위를 나타냄)
(..<a의 범위를 나타냄)

 

위 타입들 모두 제네릭 타입으로 정의되어 있고, Comparable 프로토콜을 채택한 타입만이 

범위를 나타내는 타입을 사용할 수 있습니다.

 

그리고 범위를 나타내는 타입은 상한, 하한 값을 알아야 하기 때문에

타입 내부에 upperBound, lowerBound 프로퍼티가 정의되어 있습니다.

(단, PartialRangeFrom 타입의 경우 하한 값만 알면 되기 때문에 lowerBound 프로퍼티만 정의되어 있고,

PartialRangeThrough, PartialRangeUpTo 타입은 상한 값만 알면 되기 때문에 upperBound 프로퍼티만 정의되어 있다.)

 

 

저는 일단 a...b 범위만을 이용할 것이기 때문에 ClosedRange를 이용한 subscript 하나만 구현해보도록 하겠습니다.

 

extension String {
    subscript(index: ClosedRange<Int>) -> Self {
        get {
            let start = self.index(self.startIndex, offsetBy: index.lowerBound)
            let end = self.index(self.startIndex, offsetBy: index.upperBound)
            
            return String(self[start...end])
        }
    }
}

var string = "12345"
print(string[2...4]) // "345"

 

ClosedRange 타입의 경우 하한, 상한 범위를 모두 설정해주기 때문에

lowerBound로 하한 index를, upperBound로 상한 index를 정의해주었습니다.

 

어때요??

한번만 subscript로 정의하고 나니까 전보다 문자열에 더 쉽게 접근할 수 있죠??

 

 

 

 

오늘은 subscript 문법을 활용해서 문자열에 보다 쉽게 접근할 수 있는 방법을 알아봤어요!

 

물론 이번 글에서는 값을 가져오는 get만을 구현했지만,

특정 위치, 범위에 있는 값을 변경하기 위한 set도 subscript를 통해서 구현하면 되겠습니다!