package com.freetymekiyan.algorithms.level.hard;

import java.util.HashMap;
import java.util.Map;

/**
 * Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following
 * operations: get and set.
 * <p>
 * get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
 * set(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it
 * should invalidate the least recently used item before inserting a new item.
 * <p>
 * Company Tags: Google, Uber, Facebook, Twitter, Zenefits, Amazon, Microsoft, Snapchat, Yahoo, Bloomberg, Palantir
 * Tags: Design
 * <p>
 * Use 2 data structures to implement an LRU Cache:
 * 1. A queue which is implemented by a doubly linked list. The max size of the queue will be equal to cache size. Put
 * least recently used at the tail.
 * 2. A hash map with Node's value as key and the Node as value.
 * 3. A dummy head and a dummy tail of the queue. So that we can access both head and tail fairly quick.
 */
class LRUCache {

    private final int capacity;
    private final Map<Integer, Node> cache;
    private final Node head;
    private final Node tail;

    /**
     * Remember capacity.
     * Create cache map and double linked list.
     */
    public LRUCache(int capacity) {
        this.capacity = capacity;
        cache = new HashMap<>();
        head = new Node();
        tail = new Node();
        head.next = tail;
        tail.prev = head;
    }

    /**
     * Check key in cache.
     * If not in cache, return -1.
     * If in cache, get the node, move it to head, return its value.
     */
    public int get(int key) {
        if (!cache.containsKey(key)) {
            return -1;
        }
        Node node = cache.get(key);
        moveToHead(node);
        return node.val;
    }

    /**
     * If key already in cache:
     * | Get the node, update its value, move to head.
     * If key is not in cache:
     * | Create a new node.
     * | Add it to list and cache.
     * | If cache size exceeds capacity:
     * |   Get the last node.
     * |   Remove it from list and cache.
     */
    public void set(int key, int value) {
        if (cache.containsKey(key)) {
            Node node = cache.get(key);
            node.val = value;
            moveToHead(node);
        } else {
            Node newNode = new Node(key, value);
            addNode(newNode);
            cache.put(key, newNode);
            if (cache.size() > capacity) {
                Node last = tail.prev;
                removeNode(last);
                cache.remove(last.key);
            }
        }
    }

    /**
     * Remove node from list and add it to head.
     */
    private void moveToHead(Node node) {
        removeNode(node);
        addNode(node);
    }

    /**
     * Remove a node from double linked list.
     */
    private void removeNode(Node node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    /**
     * Add a node after head.
     */
    private void addNode(Node node) {
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }

    /**
     * Double linked list node.
     * With previous node, next node, key, and value.
     */
    class Node {

        Node prev;
        Node next;
        int key;
        int val;

        public Node() {
        }

        public Node(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }
}

@@ -96,16 +96,41 @@
 p;%0A%0A/**%0A
+ * 146. LRU Cache%0A * %3Cp%3E%0A
  * Desig
@@ -1326,25 +1326,25 @@
 ap and doubl
-e
+y
  linked list
@@ -1942,32 +1942,59 @@
 already in cache
+, only need to update value
 :%0A     * %7C Get t
@@ -2112,24 +2112,46 @@
 d it to 
-list and
+the head of list and put it in
  cache.%0A
@@ -2223,50 +2223,142 @@
 node
-.%0A     * %7C   Remove it from list and cache
+, which is the previous node of tail.%0A     * %7C   Remove it from list by its self-reference.%0A     * %7C   Remove it from cache by its key
 .%0A  

update 146. LRU Cache