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 Node:
    def __init__(self, key=None, val=0):
        self.key = key
        self.val = val
        self.next = None
        self.prev = None


class LRUCache:

    def __init__(self, capacity: int):
        self.store: dict[int, Node] = {}
        self.capacity = capacity
        
        self.head: Node = Node()
        self.tail: Node = Node()

        self.head.next = self.tail
        self.tail.prev = self.head

    def get(self, key: int) -> int:
        _node: Node = self.store.get(key)

        if not _node: return -1

        self._updateRecentlyUsed(_node)
        
        return _node.val

    def put(self, key: int, val: int) -> None:
        _node: Node = self.store.get(key, Node(key, val))
        
        if key in self.store:
            self._updateRecentlyUsed(_node)
            _node.val = val
        else:
            self._attach(_node)
        
        self.store[key] = _node
        
        if len(self.store) > self.capacity:
            self._removeLRU()
    
    def _updateRecentlyUsed(self, node: Node):
        self._detach(node)
        self._attach(node)
        
    def _removeLRU(self):
        _lru = self.head.next
        
        if _lru is None or _lru.key is None:
            # Nothing to delete
            return

        del self.store[_lru.key]

        self._detach(_lru)

    def _detach(self, node: Node):
        if not node:
            return

        node.prev.next = node.next
        node.next.prev = node.prev

    def _attach(self, node: Node):
        node.next = self.tail
        node.prev = self.tail.prev
        
        node.prev.next = node
        node.next.prev = node