Merge pull request #1371 from tanmaylaud/Development

Added LFU Cache implementation
This commit is contained in:
Stepfen Shawn 2020-07-27 14:51:47 +08:00 committed by GitHub
commit 80b765ba30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 180 additions and 0 deletions

View File

@ -0,0 +1,155 @@
package com.caching;
import java.util.HashMap;
import java.util.NoSuchElementException;
import java.util.TreeMap;
/**
* Your LFUCache object can be instantiated and called as such: LFUCache
* lfuCache = new LFUCache(capacity); lfuCache.put(key,value); int param_1 =
* lfuCache.get(key);
*/
class LFUCache<T> {
// internal Node to store cache element
private class Node {
int key;
T value;
int freq;
Node next;
Node pre;
public Node(int key, T value, int freq) {
this.key = key;
this.value = value;
this.freq = freq;
next = pre = null;
}
public String toString() {
return " Key: " + key + "Value: " + value + "Freq: " + freq;
}
}
// internal Doubly Linked List to store cache nodes
private class DLL {
Node head;
Node tail;
int len;
public DLL() {
head = new Node(-1, null, -1);
tail = new Node(-1, null, -1);
head.next = tail;
tail.pre = head;
len = 0;
}
public void addToHead(Node node) {
node.next = head.next;
head.next.pre = node;
head.next = node;
node.pre = head;
len++;
}
public void deleteNode(Node node) {
node.pre.next = node.next;
node.next.pre = node.pre;
len--;
}
}
private int capacity;
private int size;
private TreeMap<Integer, DLL> freq;
private HashMap<Integer, Node> map;
/**
* instantiates LFUCache with fixed capacity
*
* @param capacity The capacity of the cache. Once the cache reaches capacity,
* new elements will replace old elements in LFU manner
*/
public LFUCache(int capacity) {
this.capacity = capacity;
size = 0;
freq = new TreeMap<Integer, DLL>();
map = new HashMap<Integer, Node>();
System.out.println("LFUCache initialised with capacity: " + capacity);
}
/**
* To get the cached value for given key
*
* @param key The key (int) of the expected value
* @return corresponding value for input key
* @throws NoSuchElementException if key is absent
*/
public T get(int key) {
// Cache hit condition
if (map.containsKey(key)) {
Node node = map.get(key);
System.out.println("Returning value from cache:" + node.toString());
DLL dll = freq.get(node.freq);
dll.deleteNode(node);
if (dll.len == 0)
freq.remove(node.freq);
node.freq += 1;
dll = freq.computeIfAbsent(node.freq, k -> new DLL());
dll.addToHead(node);
return node.value;
}
// Cache miss condition
throw new NoSuchElementException("No element for key: " + key);
}
/**
* To put a value in LFU cache with corresponding key
*
* @param key The key (int)
* @param value The value to be cached
*/
public void put(int key, T value) {
if (capacity == 0) {
System.out.println("Cache set to 0 capacity. No element will be cached");
return;
}
if (map.containsKey(key)) {
System.out.println("Key " + key + " already present in cache.Value will be replaced");
Node node = map.get(key);
node.value = value;
DLL dll = freq.get(node.freq);
dll.deleteNode(node);
if (dll.len == 0)
freq.remove(node.freq);
node.freq += 1;
dll = freq.computeIfAbsent(node.freq, k -> new DLL());
dll.addToHead(node);
} else {
System.out.println("Adding new key " + key + " to cache");
Node node = new Node(key, value, 1);
map.put(key, node);
if (size < capacity) {
size++;
DLL dll = freq.computeIfAbsent(1, k -> new DLL());
dll.addToHead(node);
} else {
System.out.println("Cache at peak capacity.Old values will be removed in LFU fashion");
Integer lowest = freq.firstKey();
DLL dll = freq.get(lowest);
map.remove(dll.tail.pre.key);
System.out.println("Value removed:" + dll.tail.pre.value.toString());
dll.deleteNode(dll.tail.pre);
if (dll.len == 0 && lowest != 1)
freq.remove(lowest);
DLL freq_one = freq.computeIfAbsent(1, k -> new DLL());
freq_one.addToHead(node);
}
}
}
}

View File

@ -0,0 +1,25 @@
package com.caching;
import java.util.NoSuchElementException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class LFUCacheTest {
@Test
public void testLFUCache() {
LFUCache<Integer> cache = new LFUCache<Integer>(2 /* capacity */ );
cache.put(1, 1);
cache.put(2, 2);
Assertions.assertEquals(1, cache.get(1)); // returns 1
cache.put(3, 3); // evicts key 2
Assertions.assertThrows(NoSuchElementException.class, () -> cache.get(2));// throws exception
Assertions.assertEquals(3, cache.get(3)); // returns 3.
cache.put(4, 4); // evicts key 1.
Assertions.assertThrows(NoSuchElementException.class, () -> cache.get(1));// throws exception
Assertions.assertEquals(3, cache.get(3)); // returns 3
Assertions.assertEquals(4, cache.get(4)); // returns 4
}
}