Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.
Implement the LRUCache class:
LRUCache(int capacity)
Initialize the LRU cache with positive size capacity
.int get(int key)
Return the value of the key if the key exists, otherwise return -1
.void put(int key, int value)
Update the value of the key
if the key
exists. Otherwise, add the key-value
pair to the cache. If the number of keys exceeds the capacity
from this operation, evict the least recently used key.Follow up:
Could you do get
and put
in O(1)
time complexity?
Example 1:
Input
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
Output
[null, null, null, 1, null, -1, null, -1, 3, 4]
Explanation
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // cache is {1=1}
lRUCache.put(2, 2); // cache is {1=1, 2=2}
lRUCache.get(1); // return 1
lRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3}
lRUCache.get(2); // returns -1 (not found)
lRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3}
lRUCache.get(1); // return -1 (not found)
lRUCache.get(3); // return 3
lRUCache.get(4); // return 4
Constraints:
At most calls will be made to get
and put
.
Cache 란 자주 사용하는 데이터나 값을 미리 복사해 놓는 임시장소 를 가리킨다.
아래와 같은 저장공간 계층 구조에서 확인 할 수 있듯이, cache 는 저장공간이 작고 비용이 비싼 대신 빠른 성능을 제공한다.
cache 의 성능을 측정할 때 Hit latency 와 Miss latency 가 중요한 요인으로 꼽힌다.
Hit latency : Cache hit(CPU에서 요청한 데이터가 캐시에 존재하는 경우) 가 발생해 캐싱된 데이터를 가져올 때 소요되는 시간.
Miss latency : Cache miss(CPU에서 요청한 데이터가 캐시에 존재하지 않는 경우) 가 발생해 상위 캐시 혹은 메모리에서 데이터를 가져올 때 소요되는 시간.
우리가 관심을 가져야 할 것은 한정된 자원속에서 cache hit 를 높이고 cache miss 를 줄이는 방법이다. 그 중 LRU algorithm 은 가장 최근에 사용된 적이 없는 데이터는 앞으로도 사용될 확률이 적을 거라는 판단으로 추가되는 새로운 데이터와 교체하는 알고리즘이다. 아래 그림과 같이 Head
에 가까울 수록 가장 최근에 사용된 데이터 이며, Tail
에 가까울수록 최근에 사용되지 않은 데이터이다.
파이썬 3.6 이전의 dict
는 데이터를 삽입된 순서대로 데이터를 얻을수가 없었다. 대신 collections
모듈의 OrderedDict
를 통해 데이터의 순서를 보장받을수 있다. (파이썬 3.6 부터는 기본 dict
가 OrderedDict
와 동일하게 동작하기 때문에 사용할일은 없을지라도 하위 호환성 보장 측면에서 사용하는것을 권장한다.)
it returns and removes a (key,value) pair
Move an existing key to either end of an ordered dictionary
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity: int):
self.__capacity=capacity
self.__cache=OrderedDict()
def get(self, key: int) -> int:
value=self.__cache.get(key,-1)
if value !=-1: # If key exists
self.__cache.move_to_end(key,last=True) # Update time(Move to header)
return value
def put(self, key: int, value: int) -> None:
if self.__cache.get(key,False): # If key exists
self.__cache.move_to_end(key,last=True) # Update time (Move to header)
elif len(self.__cache)==self.__capacity: # If cache exceeds capacity
self.__cache.popitem(last=False) # FIFO
self.__cache[key]=value # Update key-value
# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)