LRUCache
LinkedHashMap way
public class LRUCache {
private final LinkedHashMap<Integer, Integer> keyToValueMap;
private final int maxCapacity;
public LRUCache(int maxCapacity) {
this.maxCapacity = maxCapacity;
keyToValueMap = new LinkedHashMap<>(maxCapacity);
}
public void put(Integer key, Integer value) {
keyToValueMap.put(key, value);
if (keyToValueMap.size() > maxCapacity) {
Integer oldestKey = keyToValueMap.keySet().iterator().next();
keyToValueMap.remove(oldestKey);
}
updateRecentlyUsed(key);
}
public Integer get(Integer key) {
if (!keyToValueMap.containsKey(key)) {
return -1;
}
Integer value = keyToValueMap.get(key);
updateRecentlyUsed(key);
return value;
}
private void updateRecentlyUsed(Integer key) {
Integer value = keyToValueMap.remove(key);
keyToValueMap.put(key, value);
}
}
Pros:
- Fast and easy to implement
Cons: - If in the future we want to add from the end, we need to loop through until the end to find the last node to remove
Hashmap + LinkedList way
package datastructures.LRUCache;
public interface Cache<K, V> {
void put(K key, V value);
V get(K key);
}
package datastructures.LRUCache;
class LRUNode {
public LRUNode next;
public LRUNode previous;
public Integer key;
public Integer value;
public LRUNode() {
}
public LRUNode(Integer key, Integer value) {
this.key = key;
this.value = value;
}
}
package datastructures.LRUCache;
import java.util.HashMap;
import java.util.Map;
public class LRUCache implements Cache<Integer, Integer> {
private final Integer capacity;
private final Map<Integer, LRUNode> keyToNode;
private final LRUNode tailNode;
private final LRUNode headNode;
public LRUCache(int capacity) {
tailNode = new LRUNode();
headNode = new LRUNode();
keyToNode = new HashMap<>(capacity);
this.capacity = capacity;
tailNode.previous = headNode;
headNode.next = tailNode;
}
@Override
public void put(Integer key, Integer value) {
LRUNode cacheNode = keyToNode.get(key);
if (cacheNode == null) {
cacheNode = new LRUNode(key, value);
keyToNode.put(key, cacheNode);
} else {
cacheNode.value = value;
}
if (keyToNode.size() > capacity) {
detach(cacheNode);
removeLeastRecentlyUsed();
}
updateRecentlyUsed(cacheNode);
}
private void removeLeastRecentlyUsed() {
LRUNode removeNode = headNode.next;
headNode.next = removeNode.next;
removeNode.next.previous = headNode;
keyToNode.remove(removeNode.key);
}
@Override
public Integer get(Integer key) {
if (!keyToNode.containsKey(key)) {
return -1;
}
LRUNode node = keyToNode.get(key);
updateRecentlyUsed(node);
return node.value;
}
private void updateRecentlyUsed(LRUNode cacheNode) {
detach(cacheNode);
connectToTail(cacheNode);
}
private void connectToTail(LRUNode cacheNode) {
LRUNode previousTailNode = tailNode.previous;
previousTailNode.next = cacheNode;
cacheNode.previous = previousTailNode;
cacheNode.next = tailNode;
tailNode.previous = cacheNode;
}
private void detach(LRUNode cacheNode) {
LRUNode previousNode = cacheNode.previous;
LRUNode nextNode = cacheNode.next;
if (previousNode != null) {
previousNode.next = nextNode;
}
if (nextNode != null) {
nextNode.previous = previousNode;
}
}
}
Remember to use a HashMap<Key, Node>
in the Node
we have previous, next
and the pair of key, value
.
Pros:
- Customisable
Cons: - Complex
Python implementation
class ListNode:
def __init__(self, key: int, value: int):
self.key = key
self.value = value
self.next = None
self.prev = None
class LRUCache:
KEY_NOT_FOUND = -1
def __init__(self, capacity: int):
self.capacity = capacity
self.keyToNodeMap: Map[key, ListNode] = {}
self.head = ListNode(None, None)
self.tail = ListNode(None, None)
self.head.next = self.tail
self.tail.prev = self.head
def get(self, key: int) -> int:
if key not in self.keyToNodeMap:
return LRUCache.KEY_NOT_FOUND
keyNode = self.keyToNodeMap.get(key)
self.updateRecentlyUsed(keyNode)
return keyNode.value
def put(self, key: int, value: int) -> None:
if key in self.keyToNodeMap:
keyNode = self.keyToNodeMap.get(key)
self.keyToNodeMap.get(key).value = value
self.updateRecentlyUsed(keyNode)
else:
if (self.capacity == 0):
self.removeLeastRecentlyUsed()
else:
self.capacity -= 1
keyNode = ListNode(key, value)
self.keyToNodeMap[key] = keyNode
self.updateRecentlyUsed(keyNode)
def removeLeastRecentlyUsed(self):
leastRecentlyUsedNode = self.head.next
self.detach(leastRecentlyUsedNode)
self.keyToNodeMap.pop(leastRecentlyUsedNode.key)
def updateRecentlyUsed(self, keyNode: ListNode) -> None:
self.detach(keyNode)
mostRecentNode = self.tail.prev
mostRecentNode.next = keyNode
keyNode.next = self.tail
keyNode.prev = mostRecentNode
self.tail.prev = keyNode
def detach(self, keyNode: ListNode) -> None:
nextNode = keyNode.next
prevNode = keyNode.prev
if (not nextNode and not prevNode):
# This is a new node. No action is required
return
nextNode.prev = prevNode
prevNode.next = nextNode
keyNode.next = None
keyNode.prev = None