Add LFU Cache (#3161)
This commit is contained in:
parent
a910d8754e
commit
2a2c575c89
@ -0,0 +1,147 @@
|
|||||||
|
package com.thealgorithms.datastructures.caches;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java program for LFU Cache (https://en.wikipedia.org/wiki/Least_frequently_used)
|
||||||
|
* @author Akshay Dubey (https://github.com/itsAkshayDubey)
|
||||||
|
*/
|
||||||
|
public class LFUCache<K,V> {
|
||||||
|
|
||||||
|
private class Node {
|
||||||
|
private K key;
|
||||||
|
private V value;
|
||||||
|
private int frequency;
|
||||||
|
private Node previous;
|
||||||
|
private Node next;
|
||||||
|
|
||||||
|
public Node(K key, V value, int frequency) {
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
this.frequency = frequency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node head;
|
||||||
|
private Node tail;
|
||||||
|
private Map<K,Node> map = null;
|
||||||
|
private Integer capacity;
|
||||||
|
private static final int DEFAULT_CAPACITY = 100;
|
||||||
|
|
||||||
|
public LFUCache() {
|
||||||
|
this.capacity = DEFAULT_CAPACITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LFUCache(Integer capacity) {
|
||||||
|
this.capacity = capacity;
|
||||||
|
this.map = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns value present in the cache corresponding to the key passed as parameter
|
||||||
|
*
|
||||||
|
* @param <K> key for which value is to be retrieved
|
||||||
|
* @returns <V> object corresponding to the key passed as parameter, returns null if <K> key is not present in the cache
|
||||||
|
*/
|
||||||
|
public V get(K key) {
|
||||||
|
if(this.map.get(key) == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node node = map.get(key);
|
||||||
|
removeNode(node);
|
||||||
|
node.frequency += 1;
|
||||||
|
addNodeWithUpdatedFrequency(node);
|
||||||
|
|
||||||
|
return node.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method stores <K> key and <V> value in the cache
|
||||||
|
*
|
||||||
|
* @param <K> key which is to be stored in the cache
|
||||||
|
* @param <V> value which is to be stored in the cache
|
||||||
|
*/
|
||||||
|
public void put(K key, V value) {
|
||||||
|
if(map.containsKey(key)) {
|
||||||
|
Node node = map.get(key);
|
||||||
|
node.value = value;
|
||||||
|
node.frequency += 1;
|
||||||
|
removeNode(node);
|
||||||
|
addNodeWithUpdatedFrequency(node);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(map.size() >= capacity) {
|
||||||
|
map.remove(this.head.key);
|
||||||
|
removeNode(head);
|
||||||
|
}
|
||||||
|
Node node = new Node(key,value,1);
|
||||||
|
addNodeWithUpdatedFrequency(node);
|
||||||
|
map.put(key, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method stores the node in the cache with updated frequency
|
||||||
|
*
|
||||||
|
* @param Node node which is to be updated in the cache
|
||||||
|
*/
|
||||||
|
private void addNodeWithUpdatedFrequency(Node node) {
|
||||||
|
if(tail != null && head != null) {
|
||||||
|
Node temp = this.head;
|
||||||
|
while(temp != null) {
|
||||||
|
if(temp.frequency > node.frequency) {
|
||||||
|
if(temp==head) {
|
||||||
|
node.next = temp;
|
||||||
|
temp.previous = node;
|
||||||
|
this.head = node;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
node.next = temp;
|
||||||
|
node.previous = temp.previous;
|
||||||
|
temp.previous.next = node;
|
||||||
|
node.previous = temp.previous;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
temp = temp.next;
|
||||||
|
if(temp == null) {
|
||||||
|
tail.next = node;
|
||||||
|
node.previous = tail;
|
||||||
|
node.next = null;
|
||||||
|
tail = node;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tail = node;
|
||||||
|
head = tail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method removes node from the cache
|
||||||
|
*
|
||||||
|
* @param Node node which is to be removed in the cache
|
||||||
|
*/
|
||||||
|
private void removeNode(Node node) {
|
||||||
|
if(node.previous != null) {
|
||||||
|
node.previous.next = node.next;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.head = node.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(node.next != null) {
|
||||||
|
node.next.previous = node.previous;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.tail = node.previous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package com.thealgorithms.datastructures.caches;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class LFUCacheTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLFUCacheWithIntegerValueShouldPass() {
|
||||||
|
|
||||||
|
LFUCache<Integer, Integer> lfuCache = new LFUCache<>(5);
|
||||||
|
lfuCache.put(1, 10);
|
||||||
|
lfuCache.put(2, 20);
|
||||||
|
lfuCache.put(3, 30);
|
||||||
|
lfuCache.put(4, 40);
|
||||||
|
lfuCache.put(5, 50);
|
||||||
|
|
||||||
|
//get method call will increase frequency of key 1 by 1
|
||||||
|
assertEquals(10, lfuCache.get(1));
|
||||||
|
|
||||||
|
//this operation will remove value with key as 2
|
||||||
|
lfuCache.put(6, 60);
|
||||||
|
|
||||||
|
//will return null as value with key 2 is now evicted
|
||||||
|
assertEquals(null, lfuCache.get(2));
|
||||||
|
|
||||||
|
//should return 60
|
||||||
|
assertEquals(60, lfuCache.get(6));
|
||||||
|
|
||||||
|
//this operation will remove value with key as 3
|
||||||
|
lfuCache.put(7, 70);
|
||||||
|
|
||||||
|
assertEquals(null, lfuCache.get(2));
|
||||||
|
assertEquals(70, lfuCache.get(7));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLFUCacheWithStringValueShouldPass() {
|
||||||
|
|
||||||
|
LFUCache<Integer, String> lfuCache = new LFUCache<>(5);
|
||||||
|
lfuCache.put(1, "Alpha");
|
||||||
|
lfuCache.put(2, "Beta");
|
||||||
|
lfuCache.put(3, "Gamma");
|
||||||
|
lfuCache.put(4, "Delta");
|
||||||
|
lfuCache.put(5, "Eplison");
|
||||||
|
|
||||||
|
//get method call will increase frequency of key 1 by 1
|
||||||
|
assertEquals("Alpha", lfuCache.get(1));
|
||||||
|
|
||||||
|
//this operation will remove value with key as 2
|
||||||
|
lfuCache.put(6, "Digamma");
|
||||||
|
|
||||||
|
//will return null as value with key 2 is now evicted
|
||||||
|
assertEquals(null, lfuCache.get(2));
|
||||||
|
|
||||||
|
//should return string Digamma
|
||||||
|
assertEquals("Digamma", lfuCache.get(6));
|
||||||
|
|
||||||
|
//this operation will remove value with key as 3
|
||||||
|
lfuCache.put(7, "Zeta");
|
||||||
|
|
||||||
|
assertEquals(null, lfuCache.get(2));
|
||||||
|
assertEquals("Zeta", lfuCache.get(7));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user