먹고 기도하고 코딩하라

[Swift] String과 Substring, String.index 본문

앱/Swift

[Swift] String과 Substring, String.index

사과먹는사람 2023. 1. 22. 01:43
728x90
728x90

 

파이썬으로 문제를 풀다가 최근에는 스위프트로 문제를 푸는 연습을 했다.

제일 난감했던 것 중 하나는 스위프트 String은 인덱스를 지정할 때 그냥 Int형 숫자를 쓸 수 없고 String.index 타입을 써야 한다는 거다. String에 extension으로 커스텀 코드 추가하면 배열 접근하듯이 쓸 수 있게 만들 수 있는데, 문제 한 개 풀 때마다 귀찮아서 그 짓 어떻게 하나 싶기도 하다.

어차피 문자열 안 다룰 수는 없으니까 그냥 겸사겸사 index를 같이 본다.

 

문자열 인덱싱

var lyrics = "Thunder only happens when it's raining"
print(lyrics[lyrics.startIndex])	// T
print(lyrics[lyrics.index(before: lyrics.endIndex)])	// g
print(lyrics[lyrics.index(after: lyrics.startIndex)])	// h
print(lyrics[lyrics.index(lyrics.startIndex, offsetBy: 10)])	// l

lyrics.startIndex, lyrics.endIndex로 기본적인 String.index를 만들 수 있다.

이걸 출력해보면 Index(_rawBits: 15) 이런 식으로 나온다. 

String.index(before:), String.index(after:), String.index(_:offsetBy:) 등으로 String.index를 만들어줄 수 있다. offSetBy는 index에서 offSetBy만큼 떨어진 곳의 인덱스를 반환한다.

String.index 자체는 숫자가 아니다.

 

문자열 슬라이싱

하나의 문자가 아니라 어느 범위의 문자열을 슬라이싱하고 싶다면... 이렇게 쓸 수 있다.

조금 복잡해 보이긴 하지만 대괄호를 벗어나지 않은 상태에서 인덱스 구해서 범위 연산한다는 것만 알면 된다. String.index ..< String.index이다.

print(lyrics[lyrics.startIndex ..< lyrics.index(lyrics.startIndex, offsetBy: 8)])
// Thunder

String.indices로 DefaultIndices를 만들 수 있는데, 이걸로 for-in 루프를 돌리면 DefaultIndices.element가 나온다. 이걸 인덱스로 사용할 수 있다.

// ch는 DefaultIndices<String>.Element 타입
for ch in lyrics.indices {
    print(lyrics[ch], terminator: " ")
}
// T h u n d e r   o n l y   h a p p e n s   w h e n   i t ' s   r a i n i n g

 

문자열에 문자 추가와 삭제

(이 메소드는 컬렉션 타입인 배열, 셋, 딕셔너리 모두에 사용할 수 있다)

단일 문자를 추가할 때는 insert(:at:)을 사용할 수 있다.

lyrics.insert("!", at: lyrics.endIndex)
print(lyrics)	// Thunder only happens when it's raining!

여러 개의 문자를 추가할 때는 insert(contentsOf:at:)을 사용하면 된다. contentsOf는 컬렉션 타입을 받아 추가할 수 있다.

lyrics.insert(contentsOf: " Players only love you when they're playing.", at: lyrics.endIndex)
print(lyrics)	// Thunder only happens when it's raining! players only love you when they're playing.

단일 문자를 삭제할 때는 remove(:at:)을 사용한다.

lyrics.remove(at: lyrics.index(before: lyrics.endIndex))
print(lyrics)	// Thunder only happens when it's raining! Players only love you when they're playing

문자열에서 어느 범위를 삭제하고 싶을 때는 removeSubrange(:)를 사용하면 된다.

let range = lyrics.index(lyrics.startIndex, offsetBy: 38)..<lyrics.endIndex
lyrics.removeSubrange(range)
print(lyrics)	// Thunder only happens when it's raining

맨 마지막 문자를 삭제하고 싶을 때는 removeLast()를 쓴다. removeLast()는 맨 마지막 문자를 삭제해 반환한다.

 

 

문자열에서 단일 문자 찾기

firstIndex(of:)로 어떤 문자가 처음 등장하는 인덱스를 찾을 수 있다. 옵셔널 타입으로 반환하는데, 만약 해당 문자를 찾지 못했다면 nil을 반환한다. 

let pIndex = lyrics.firstIndex(of: "r") ?? lyrics.startIndex
let thunder = lyrics[...pIndex]
print(thunder)	// Thunder
print(lyrics.firstIndex(of: "z")	// nil

어떤 문자가 마지막으로 등장하는 인덱스도 찾을 수 있는데, 이건 lastIndex(of:)를 사용하면 된다.

let lastRIndex = lyrics.lastIndex(of: "r") ?? lyrics.startIndex
print(lyrics[lastRIndex ..< lyrics.endIndex])	// raining

 

부분 문자열

부분 문자열을 얻기 위해 서브스크립트 메소드를 사용할 수 있다. 이 때 얻는 부분 문자열은 String이 아닌 Substring 타입이다.

방금 위에서 딴 thunder도 Substring 타입이다.

print(type(of: thunder))	// Substring

Substring은 원본 String의 메모리를 참조하기 때문에 메모리를 아낄 수 있다는 장점은 있지만, Substring을 계속 사용한다면 원본 String을 더이상 사용하지 않더라도 계속 메모리에 유지해야 하기 때문에 Substring을 오래 사용한다면 String()으로 바꿔서 따로 메모리를 받는 것이 좋다.

 

혹시나 하고 실험을 해봤는데 다소 기묘한? 결과가 나오긴 한다. 원본 문자열의 T가 날라갔으니 Substring도 같이 바뀌어야 하지 않나 싶은데 그렇지는 않은 모양이다.

lyrics.remove(at: lyrics.startIndex)
print(lyrics)	// hunder only happens when it's raining
print(thunder)	// Thunder

 

접두사, 접미사 비교하기

hasPrefix(:), hasSuffix(:) 메소드를 사용해서 접두사와 접미사를 비교할 수 있다. true, false 값을 반환한다.

let romeoAndJuliet = [
    "Act 1 Scene 1: Verona, A public place",
    "Act 1 Scene 2: Capulet's mansion",
    "Act 1 Scene 3: A room in Capulet's mansion",
    "Act 1 Scene 4: A street outside Capulet's mansion",
    "Act 1 Scene 5: The Great Hall in Capulet's mansion",
    "Act 2 Scene 1: Outside Capulet's mansion",
    "Act 2 Scene 2: Capulet's orchard",
    "Act 2 Scene 3: Outside Friar Lawrence's cell",
    "Act 2 Scene 4: A street in Verona",
    "Act 2 Scene 5: Capulet's mansion",
    "Act 2 Scene 6: Friar Lawrence's cell"
]

var act1Cnt = 0
for subtitle in romeoAndJuliet where subtitle.hasPrefix("Act 1") {
    act1Cnt += 1
}
print(act1Cnt)	// 5

 

+ 서브스크립트

위에서 서브스크립트 메소드로 String에서 Substring을 얻을 수 있다고 적었다.

그럼 서브스크립트 메소드는 String 타입에 종속된 것일까? 그렇진 않다. 콜렉션, 리스트, 시퀀스 등 집합의 특정 엘리먼트에 접근할 수 있는 문법이 서브스크립트이다. String 자체는 Character들을 모아놓은 컬렉션이다. Array 타입의 엘리먼트는 Array[index], Dictionary 타입의 값은 Dictionary[key]로 접근하는데 [] <- 이게 바로 서브스크립트 문법이다. 더 자세한 것은 문서 참고. 

 

 

 

728x90
반응형
Comments