String.Index
)오늘부터 99클럽 코테 스터디를 시작해서 매일 코딩테스트 문제를 풀기로 했다. 첫 번째 문제는 명령 프롬프트였다.
하지만 시작부터 삽질이 시작되었다.
문제를 풀기 전에 "String은 Character의 배열이니까 Subscription을 Int로 접근 가능하겠지?" 라고 생각했는데, 이게 큰 오산이었다. Swift의 String
은 배열과 다르게 Int
인덱스로 접근할 수 없었다. 왜 그런 걸까?
Swift의 String
은 유니코드(Unicode) 기반으로 동작하기 때문이다.
우리가 보기에 "가나다"는 각각 하나의 글자로 보이지만, 컴퓨터 내부에서는 코드의 나열로 저장된다. 예를 들어:
즉, 단순히 Int
인덱스를 사용하면 문자 경계를 정확히 알 수 없기 때문에 잘못된 문자 데이터를 읽을 가능성이 생긴다. 따라서 Swift는 정확한 문자 단위를 보장하는 String.Index
타입을 제공한다.
문자열에 접근하려면 String.Index
를 사용해야 한다.
let text = "Hello, Swift!"
let index = text.index(text.startIndex, offsetBy: 7)
print(text[index]) // "S"
이 방식은 문자열의 시작 위치(startIndex
)에서 정확한 문자 단위로 이동하는 방식이기 때문에 안전하다.
만약 Int
인덱스를 쓰고 싶다면, 문자열을 배열로 변환하면 된다.
let characters = Array(text)
print(characters[7]) // "S"
하지만, 이 방식은 문자열 전체를 새로운 배열로 변환하는 과정이 필요하므로 성능을 고려해야 한다.
text[0]
처럼 배열처럼 접근하려다가 컴파일 에러가 발생했다.String
이 Collection
을 준수하니까 당연히 Int
인덱스로 접근할 수 있을 거라고 생각했는데, 그렇지 않았다.초기 문제 풀이:
if let count = Int(readLine() ?? "0") {
var inputs: [String] = []
for _ in 0..<count {
if let line = readLine() {
inputs.append(line)
}
}
func makeCommandPrompt(count: Int, _ values: [String]) -> String {
var result = String()
let length = values.first?.count ?? 0
for index in 0..<length {
var currentChar = String()
for value in values {
guard index < value.count else { continue }
let currentIndex = value.index(value.startIndex, offsetBy: index)
let char = value[currentIndex]
if currentChar.isEmpty {
currentChar.append(char)
}
if currentChar != String(char) {
currentChar = "?"
}
}
result.insert(contentsOf: currentChar, at: String.Index(utf16Offset: index, in: values.first ?? ""))
currentChar = String()
}
return result
}
makeCommandPrompt(count: count, inputs)
}
String.Index
를 활용하여 문제를 다시 접근했다.indices
를 사용하여 각 문자를 비교하는 방식으로 수정했다.최종 해결 방법:
let count = Int(readLine() ?? "0") ?? 0
var inputs: [String] = []
for _ in 0..<count {
if let line = readLine() {
inputs.append(line)
}
}
func makeCommandPrompt(count: Int, _ values: [String]) -> String {
return values[0].indices.map { index in
let isSame = values.allSatisfy { $0[index] == values[0][index] }
return isSame ? String(values[0][index]) : "?"
}.joined()
}
print(makeCommandPrompt(count: count, inputs))
String
은 유니코드 안전성을 보장하기 위해 Int
인덱스 접근을 막아놓았다는 사실.String.Index
를 활용하는 방법을 익혔다.allSatisfy
를 사용하여 문자열 비교를 간결하게 할 수 있음을 알게 되었다.