package design;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
/**
* Created by gouthamvidyapradhan on 20/03/2017. Design and implement a data structure for Least
* Frequently Used (LFU) cache. It should support the following operations: get and put.
*
*
get(key) - Get the value (will always be positive) of the key if the key exists in the cache,
* otherwise return -1. put(key, value) - Set or insert the value if the key is not already present.
* When the cache reaches its capacity, it should invalidate the least frequently used item before
* inserting a new item. For the purpose of this problem, when there is a tie (i.e., two or more
* keys that have the same frequency), the least recently used key would be evicted.
*
*
Follow up: Could you do both operations in O(1) time complexity?
*
*
Example:
*
*
LFUCache cache = new LFUCache( 2 /* capacity
*/
/*)
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.get(3); // returns 3.
cache.put(4, 4); // evicts key 1.
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4
*/
public class LFUCache {
private class Node {
int frequency;
Node prev;
Node next;
LinkedHashSet hashSet;
Node(int frequency, LinkedHashSet hashSet) {
this.frequency = frequency;
this.hashSet = hashSet;
prev = null;
next = null;
}
}
private int capacity;
private int currentSize;
private Map cache;
private Map fMap; // frequency
private Node head;
/**
* Main method
*
* @param args
*/
public static void main(String[] args) {
LFUCache cache1 = new LFUCache(2);
cache1.put(1, 1);
cache1.put(2, 2);
System.out.println(cache1.get(1));
cache1.put(3, 3);
System.out.println(cache1.get(2));
// System.out.println(cache1.get(3));
cache1.put(4, 4);
System.out.println(cache1.get(1));
System.out.println(cache1.get(3));
System.out.println(cache1.get(4));
System.out.println(cache1.get(1));
System.out.println(cache1.get(4));
System.out.println(cache1.get(2));
cache1.put(4, 4);
cache1.put(5, 4);
cache1.put(1, 9);
cache1.put(7, 1);
cache1.put(4, 2);
System.out.println(cache1.get(1));
System.out.println(cache1.get(4));
System.out.println(cache1.get(7));
// System.out.println(cache1.get(3));
// System.out.println(cache1.get(4));
}
public LFUCache(int capacity) {
currentSize = 0;
this.capacity = capacity;
cache = new HashMap<>();
fMap = new HashMap<>();
}
/**
* Remove node and delink
*
* @param node Node
*/
private void popNode(Node node) {
if (node.prev != null && node.next != null) {
node.prev.next = node.next;
node.next.prev = node.prev;
} else if (node.prev == null) {
node.next.prev = null;
node.next = null;
} else {
node.prev.next = null;
node.prev = null;
}
}
/**
* Get value
*
* @param key key
* @return value
*/
public int get(int key) {
if (!cache.containsKey(key)) return -1;
fMap.put(key, update(key));
return cache.get(key);
}
/**
* Update fMap
*
* @param key key
*/
private Node update(int key) {
Node node = fMap.get(key);
node.hashSet.remove(key);
Node newNode;
if (node.next == null) {
newNode = makeNewNode(key, node.frequency + 1);
node.next = newNode;
newNode.prev = node;
} else if (node.next.frequency == node.frequency + 1) {
node.next.hashSet.add(key);
newNode = node.next;
} else {
newNode = makeNewNode(key, node.frequency + 1);
node.next.prev = newNode;
newNode.next = node.next;
newNode.prev = node;
node.next = newNode;
}
if (node.equals(head)) incrementHead();
else if (node.hashSet.isEmpty()) popNode(node);
return newNode;
}
/**
* Make new node
*
* @param key key
* @param frequency frequency
* @return Node
*/
private Node makeNewNode(int key, int frequency) {
LinkedHashSet linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add(key);
return new Node(frequency, linkedHashSet);
}
/**
* Add new head
*
* @param key key
* @param frequency frequency
*/
private Node addHead(int key, int frequency) {
if (head == null) head = makeNewNode(key, frequency);
else if (head.frequency > frequency) {
Node node = makeNewNode(key, frequency);
node.next = head;
head.prev = node;
head = node;
} else head.hashSet.add(key);
return head;
}
/** Increment head */
private void incrementHead() {
if (head.hashSet.isEmpty()) {
head = head.next;
if (head != null) {
head.prev.next = null;
head.prev = null;
}
}
}
/**
* Put key value pair
*
* @param key key
* @param value value
*/
public void put(int key, int value) {
if (capacity != 0) {
if (cache.containsKey(key)) {
fMap.put(key, update(key)); // update existing
cache.put(key, value);
} else {
if (currentSize == capacity) {
evict();
cache.put(key, value);
fMap.put(key, addHead(key, 1));
} else {
fMap.put(key, addHead(key, 1)); // add new head
cache.put(key, value);
currentSize++;
}
}
}
}
/** Evict the node with least frequency */
private void evict() {
int key = head.hashSet.iterator().next();
head.hashSet.remove(key);
cache.remove(key);
fMap.remove(key);
incrementHead();
}
}