캐시
지도개발팀에서 근무하는 제이지는 지도에서 도시 이름을 검색하면 해당 도시와 관련된 맛집 게시물들을 데이터베이스에서 읽어 보여주는 서비스를 개발하고 있다.
이 프로그램의 테스팅 업무를 담당하고 있는 어피치는 서비스를 오픈하기 전 각 로직에 대한 성능 측정을 수행하였는데, 제이지가 작성한 부분 중 데이터베이스에서 게시물을 가져오는 부분의 실행시간이 너무 오래 걸린다는 것을 알게 되었다.
어피치는 제이지에게 해당 로직을 개선하라고 닦달하기 시작하였고, 제이지는 DB 캐시를 적용하여 성능 개선을 시도하고 있지만 캐시 크기를 얼마로 해야 효율적인지 몰라 난감한 상황이다.
어피치에게 시달리는 제이지를 도와, DB 캐시를 적용할 때 캐시 크기에 따른 실행시간 측정 프로그램을 작성하시오.
입력 형식
출력 형식
조건
캐시크기(cacheSize) | 도시이름(cities) | 실행시간 |
---|---|---|
3 | ["Jeju", "Pangyo", "Seoul", "NewYork", "LA", "Jeju", "Pangyo", "Seoul", "NewYork", "LA"] | 50 |
3 | ["Jeju", "Pangyo", "Seoul", "Jeju", "Pangyo", "Seoul", "Jeju", "Pangyo", "Seoul"] | 21 |
2 | ["Jeju", "Pangyo", "Seoul", "NewYork", "LA", "SanFrancisco", "Seoul", "Rome", "Paris", "Jeju", "NewYork", "Rome"] | 60 |
5 | ["Jeju", "Pangyo", "Seoul", "NewYork", "LA", "SanFrancisco", "Seoul", "Rome", "Paris", "Jeju", "NewYork", "Rome"] | 52 |
2 | ["Jeju", "Pangyo", "NewYork", "newyork"] | 16 |
0 | ["Jeju", "Pangyo", "Seoul", "NewYork", "LA"] | 25 |
우선, 크기가 cahceSize인 배열을 만들고 그 배열로 캐시 역할을 해야겠다고 생각했다. cities 문자열 배열의 값을 차례대로 캐시 안에 넣는데, 이때 찾는 값의 여부에 따라 결과가 달라지기 때문에 if 조건문으로 나눴다.
찾는 값이 있으면 결과값을 1증가하고 문제 조건에 캐시 교체 알고리즘으로 LRU를 사용하라 했으므로 캐시 안에 이미 존재하는 값만 제거한다.
예시)
cache = [seoul, pangyo, jeju, la]
새로 들어올 값(= 찾는 값) : jeju
➜ cache = [jeju, seoul, pangyo, la]
찾는 값이 없으면 결과값을 5증가하고 캐시 안에 마지막 값만 제거한다. 어차피 찾는 값이 있든 없든 새로 들어갈 값은 무조건 첫 번째 인덱스에 위치해야 하기 때문에 밀어버리는 셈이다.
마지막으로 입출력 예에도 있듯이 cacheSize가 0일 때는 cities 수만큼 5씩 증가시키도록 해야 에러가 발생하지 않는다.
func solution(_ cacheSize:Int, _ cities:[String]) -> Int {
var result: Int = 0
var cache: [String] = Array(repeating: "0", count: cacheSize) // 크기가 cacheSize인 배열 만들기
if cacheSize != 0 {
// cache 안에 city가 있으면 result에 +1, 없으면 +5
cities.forEach { city in
if cache.contains(city.lowercased()) {
result += 1
cache.remove(at: cache.firstIndex(of: city.lowercased())!) // 그 자리만 제거하기
} else {
result += 5
cache.remove(at: cache.count-1) // 밀어버리기
}
cache.insert(city.lowercased(), at: 0)
}
} else {
return cities.count * 5
}
return result
}
내 풀이와 유사하다. 하지만 cacheSize가 0 일 때를 아예 따로 빼서 다뤄줬고, for 문 안에 if 조건문도 다르다. 나는 찾는 값이 캐시 안에 있는지로 조건을 나눴다면 이 분은 아예 조건문에서 캐시 안에 찾는 값의 인덱스 값을 옵셔널 바인딩 하여 가져오도록 했다.
만약 찾는 값이 없다면 옵셔널 바인딩 값이 nil 일 테니 else 문으로 넘어갈 것이다. 이 코드가 더 깔끔한 듯싶다.
func solution(_ cacheSize:Int, _ cities:[String]) -> Int {
if cacheSize == 0 {
return 5*cities.count
}
var time = 0
var cache: [String] = []
for city in cities {
if let i = cache.index(of: city.lowercased()) {
cache.remove(at: i)
time += 1
} else {
if (cache.count >= cacheSize) {
cache.remove(at: 0)
}
time += 5
}
cache += [city.lowercased()]
}
return time
}
⏰
목표 풀이 시간 : 1시간
실제 풀이 시간 : 44분
정답률 : 45.26%
이 문제는 2018년 카카오의 신입 개발자 채용을 위해 출제된 문제로 총 7문제 중 세 번째로 출제된 난이도는 '하'다.
참고
여기서부터 문제가 좀 어려워졌던 거 같습니다. 정답률이 많이 낮은데요. 이 문제는 ‘조건’에도 나와있지만 LRU 캐시 교체 알고리즘을 구현하는 문제이고, 이미 잘 알고 있다면 또는 검색해봤다면 잘 구현된 LRU 알고리즘 코드는 많이 찾을 수 있습니다.
단, 이 문제에는 입출력 예제에 캐시 사이즈 0이 포함되어 있습니다. 공개된 대부분의 LRU 구현 코드는 0일 때의 비정상적인 상황은 가정하지 않고 있기 때문에 생각 없이 그냥 가져와 붙인다면 에러가 나서 많이 고생했을 거 같네요. 하지만 사이즈 0을 처리하는 예외 처리 자체는 어렵지 않게 구현할 수 있으므로 입출력 예제가 왜 자꾸 틀리는지를 유심히 살펴봤다면 쉽게 풀 수 있는 문제입니다.
아울러 검색해서 가져온 코드는 반드시 사용 가능한지 라이선스를 확인하고, 가져올 때는 꼭 출처를 명시해야 한다는 점 잊지 마세요.
정보처리기사 공부할 때 달달 외웠던 LRU를 여기서 만났다. 처음에 보고 뭔가 싶었는데 페이징 기법 중 하나로 가장 오랫동안 참조되지 않은 페이지를 교체하는 방법이다. 문제에 캐시 교체 알고리즘음 LRU를 사용하라고 명시되어 있어서 꼭 LRU 알고리즘 코드로 문제를 풀어야 하나 의문이다. 필자는 LRU 알고리즘 코드가 아니지만 동작 방식은 동일해서 괜찮다고 생각되는데, 그 기준은 확실히 모르겠다.