TIL은 편한 말투로 작성됩니다.
📍 오늘 학습한 내용 정리
1. 프로그래머스 문제 풀기
2. todo 앱 마무리
프로그래머스 문제 풀기
programmers의 기초 트레이닝에 있는 수 조작하기 2 문제를 풀었다.
아래가 내가 풀면서 처음 작성한 코드이다.
import Foundation
func solution(_ numLog:[Int]) -> String {
var result = ""
for i in 1..<numLog.count {
let n = numLog[i] - numLog[i - 1]
switch n {
case 1:
result += "w"
case -1:
result += "s"
case 10:
result += "d"
case -10:
result += "a"
default:
break
}
}
return result
}
문제를 풀었는데 위 이미지처럼 "시간 초과" 문제로 테스트에 실패했다는 결과가 나왔다.
지금까지 문제를 풀면서 그냥 케이스가 틀린 경우는 많았지만, 시간 초과로 실패한 건 이번이 처음이었다.
구글링을 통해 찾아보니, 문자열은 값 타입이기에 문자열 연결 연산이 반복되면 새로운 문자열이 계속 생성되고, 큰 배열에서 반복이 많아져 느려질 수 있다고 한다.
result += "w"는 아래와 같은 과정을 거치게된다.
1. result의 현재 값을 읽는다.
2. 새로운 문자열 (result + "w")을 만든다.
3. 그 새 문자열을 다시 result에 할당한다.
이렇게 매번 새로운 문자열을 만들고, 다시 result에 할당하기에 연산이 비효율적이다.
그래서 고민하다가 문자열 연결(result += "w") 부분을 아래 코드와 같이 append()로 수정했다.
import Foundation
func solution(_ numLog:[Int]) -> String {
var result = ""
for i in 1..<numLog.count {
let n = numLog[i] - numLog[i - 1]
switch n {
case 1:
result.append("w")
case -1:
result.append("s")
case 10:
result.append("d")
case -10:
result.append("a")
default:
break
}
}
return result
}
이렇게 하면 매번 result 값을 읽지 않아도 되고, 새로운 문자열을 생성하지도 않아서 효율적이다.
이제 잘 해결된 것을 확인할 수 있었다.
아래 코드는 문제를 풀고 난 뒤, 다른 분들의 코드를 참고하면서 공부하던 도중 발견한 코드이다.
import Foundation
func solution(_ numLog:[Int]) -> String {
let log: [Int: String] = [1: "w", -1: "s", 10: "d", -10: "a"]
return (1..<numLog.count).map { log[numLog[$0] - numLog[$0 - 1]]! }.joined()
}
보자마자 "어? 이렇게 짧게도 할 수 있는 거였어?"라는 생각이 들었고, 코드를 분석해 보았다.
📍코드 해설
1. log 딕셔너리에 이동 값과 문자를 매핑한다.
2. numLog 배열을 순회하면서 앞 뒤 숫자의 차이를 구하고, 그 차이에 해당하는 문자를 log에서 찾아낸다.
3. 고차함수. map으로 문자 배열을 만들고,. joined()를 사용하여 하나의 문자열로 합친다.
이걸 보면서 "map과 joined()를 사용할 생각은 해보지도 못했는데, 이런 식으로 짧고 깔끔하게 코드를 작성할 수 있구나." 하는 생각이 들었다.
다만, 개인적으로는 가독성이 아주 좋은 코드는 아니라고 느꼈다.
물론 코딩테스트 상황에서는 최대한 효율적으로 빠르게 푸는 게 우선이기에, 코딩테스트에서는 이런 코드가 매우 좋은 코드이지 않을까 생각한다.
Todo 앱 마무리
오늘은 Codable과 UserDefaults를 사용해서 Todo앱을 껐다가 켜도, 변경된 데이터가 잘 저장되도록 마무리했다.
Codable은 두 개의 프로토콜을 합쳐놓은 프로토콜이다.
구조체 같은 사용자 정의 타입을 쉽게 저장하거나 불러올 수 있도록 도와주는 역할을 한다.
typealias Codable = Encodable & Decodable
즉, Codable을 채택하게 되면,
- Encodable: 구조체나 클래스를 JSON, PropertyList, Data 등으로 인코딩(변환) 할 수 있게 해 준다.
- Decodable: 반대로 Data, JSON 등을 객체로 디코딩(복원) 할 수 있게 해 준다.
Swift의 기본 타입(Data, String, Int 등)은 UserDefaults에 바로 저장이 가능하지만, 내가 만든 구조체나 클래스 등 사용자 정의 타입은 직접 저장할 수 없다.
그래서 Codable을 통해 "구조체 -> Data"로 바꿔서 저장하고, 필요시에 불러올 때는 다시 "Data -> 구조체"로 변환해서 사용하는 것이다.
그래서 내가 만든 Todo 구조체를 저장할 수 없기에 아래와 같이 Codable을 채택해 주었다.
이제 Codable을 채택해서 데이터를 저장할 수 있도록 하였으니, ViewController에서 UserDefaults를 이용해서 데이터를 저장, 불러오는 코드를 작성했다.
그리고 이제 앱이 꺼졌다 켜질 때마다 저장된 데이터를 불러와야 하기에 viewDidLoad()에 loadTodos()를 넣어주고,
값이 추가, 삭제, 저장되면서 todos배열에 변동된 값이 들어가고, 그 변동된 값이 UserDefaults에 저장되도록 각 코드에 saveTodos()를 넣어주었다.
마지막으로, cellForRowAt의 tableView에 있는 "할 일 체크"와 "즐겨찾기" 버튼이 토글 된 후에도, 그 값들도 저장되어야 하므로 동일하게 saveTodos()를 넣어주었다.
처음에는 데이터를 UserDefaults에 저장하고 그 상황에 맞도록 데이터를 저장하고 불러와야 하니까 매우 복잡하겠다고 생각했었다.
근데 Codable을 선언해서 UserDefaults에 저장할 수 있도록 하고 저장, 수정 함수들을 한 번 만들어주기만 하면, "무엇을 눌렀을 때, 데이터를 저장해야 할지, 데이터를 불러와야 할지"만 찾고 그 부분에 함수를 호출해주기만 하면 끝이라서 생각보다 쉬웠다.
(물론 데이터가 복잡해지고, 화면도 많아지면 더 복잡하겠지만..)
처음 함수를 공부했을 때, 여러 곳에 재사용되는 코드들을 함수로 만들어두고 쓴다고 들었을 때는 마냥 감이 오지 않았지만 실제로 사용해 보니, 함수를 왜 사용하는지, 코드가 길어질수록 훨씬 깔끔해지고 유지보수도 쉬워지겠구나. 싶은 생각이 들었다.
오늘의 공부는 여기까지!
'🖋️ TIL Journal' 카테고리의 다른 글
05.01 (목) iOS 사전 캠프 (0) | 2025.05.01 |
---|---|
04.30 (수) iOS 사전 캠프 (0) | 2025.04.30 |
04.29 (화) iOS 사전 캠프 (1) | 2025.04.29 |
04.28 (월) iOS 사전 캠프 (1) | 2025.04.28 |