diff --git a/src/main/java/com/caching/LFUCache.java b/src/main/java/com/caching/LFUCache.java new file mode 100644 index 00000000..c61ba6c7 --- /dev/null +++ b/src/main/java/com/caching/LFUCache.java @@ -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 { + // 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 freq; + private HashMap 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(); + map = new HashMap(); + 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); + } + } + + } + +} \ No newline at end of file diff --git a/src/test/java/com/caching/LFUCacheTest.java b/src/test/java/com/caching/LFUCacheTest.java new file mode 100644 index 00000000..14e8a1e9 --- /dev/null +++ b/src/test/java/com/caching/LFUCacheTest.java @@ -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 cache = new LFUCache(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 + } +} \ No newline at end of file