Skip to content

Commit 2a2c575

Browse files
Add LFU Cache (TheAlgorithms#3161)
1 parent a910d87 commit 2a2c575

File tree

2 files changed

+214
-0
lines changed

2 files changed

+214
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package com.thealgorithms.datastructures.caches;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
6+
/**
7+
* Java program for LFU Cache (https://en.wikipedia.org/wiki/Least_frequently_used)
8+
* @author Akshay Dubey (https://github.com/itsAkshayDubey)
9+
*/
10+
public class LFUCache<K,V> {
11+
12+
private class Node {
13+
private K key;
14+
private V value;
15+
private int frequency;
16+
private Node previous;
17+
private Node next;
18+
19+
public Node(K key, V value, int frequency) {
20+
this.key = key;
21+
this.value = value;
22+
this.frequency = frequency;
23+
}
24+
}
25+
26+
private Node head;
27+
private Node tail;
28+
private Map<K,Node> map = null;
29+
private Integer capacity;
30+
private static final int DEFAULT_CAPACITY = 100;
31+
32+
public LFUCache() {
33+
this.capacity = DEFAULT_CAPACITY;
34+
}
35+
36+
public LFUCache(Integer capacity) {
37+
this.capacity = capacity;
38+
this.map = new HashMap<>();
39+
}
40+
41+
/**
42+
* This method returns value present in the cache corresponding to the key passed as parameter
43+
*
44+
* @param <K> key for which value is to be retrieved
45+
* @returns <V> object corresponding to the key passed as parameter, returns null if <K> key is not present in the cache
46+
*/
47+
public V get(K key) {
48+
if(this.map.get(key) == null) {
49+
return null;
50+
}
51+
52+
Node node = map.get(key);
53+
removeNode(node);
54+
node.frequency += 1;
55+
addNodeWithUpdatedFrequency(node);
56+
57+
return node.value;
58+
}
59+
60+
/**
61+
* This method stores <K> key and <V> value in the cache
62+
*
63+
* @param <K> key which is to be stored in the cache
64+
* @param <V> value which is to be stored in the cache
65+
*/
66+
public void put(K key, V value) {
67+
if(map.containsKey(key)) {
68+
Node node = map.get(key);
69+
node.value = value;
70+
node.frequency += 1;
71+
removeNode(node);
72+
addNodeWithUpdatedFrequency(node);
73+
}
74+
else {
75+
if(map.size() >= capacity) {
76+
map.remove(this.head.key);
77+
removeNode(head);
78+
}
79+
Node node = new Node(key,value,1);
80+
addNodeWithUpdatedFrequency(node);
81+
map.put(key, node);
82+
}
83+
}
84+
85+
/**
86+
* This method stores the node in the cache with updated frequency
87+
*
88+
* @param Node node which is to be updated in the cache
89+
*/
90+
private void addNodeWithUpdatedFrequency(Node node) {
91+
if(tail != null && head != null) {
92+
Node temp = this.head;
93+
while(temp != null) {
94+
if(temp.frequency > node.frequency) {
95+
if(temp==head) {
96+
node.next = temp;
97+
temp.previous = node;
98+
this.head = node;
99+
break;
100+
}
101+
else {
102+
node.next = temp;
103+
node.previous = temp.previous;
104+
temp.previous.next = node;
105+
node.previous = temp.previous;
106+
break;
107+
}
108+
}
109+
else {
110+
temp = temp.next;
111+
if(temp == null) {
112+
tail.next = node;
113+
node.previous = tail;
114+
node.next = null;
115+
tail = node;
116+
break;
117+
}
118+
}
119+
}
120+
}
121+
else {
122+
tail = node;
123+
head = tail;
124+
}
125+
}
126+
127+
/**
128+
* This method removes node from the cache
129+
*
130+
* @param Node node which is to be removed in the cache
131+
*/
132+
private void removeNode(Node node) {
133+
if(node.previous != null) {
134+
node.previous.next = node.next;
135+
}
136+
else {
137+
this.head = node.next;
138+
}
139+
140+
if(node.next != null) {
141+
node.next.previous = node.previous;
142+
}
143+
else {
144+
this.tail = node.previous;
145+
}
146+
}
147+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.thealgorithms.datastructures.caches;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
class LFUCacheTest {
8+
9+
@Test
10+
void testLFUCacheWithIntegerValueShouldPass() {
11+
12+
LFUCache<Integer, Integer> lfuCache = new LFUCache<>(5);
13+
lfuCache.put(1, 10);
14+
lfuCache.put(2, 20);
15+
lfuCache.put(3, 30);
16+
lfuCache.put(4, 40);
17+
lfuCache.put(5, 50);
18+
19+
//get method call will increase frequency of key 1 by 1
20+
assertEquals(10, lfuCache.get(1));
21+
22+
//this operation will remove value with key as 2
23+
lfuCache.put(6, 60);
24+
25+
//will return null as value with key 2 is now evicted
26+
assertEquals(null, lfuCache.get(2));
27+
28+
//should return 60
29+
assertEquals(60, lfuCache.get(6));
30+
31+
//this operation will remove value with key as 3
32+
lfuCache.put(7, 70);
33+
34+
assertEquals(null, lfuCache.get(2));
35+
assertEquals(70, lfuCache.get(7));
36+
}
37+
38+
@Test
39+
void testLFUCacheWithStringValueShouldPass() {
40+
41+
LFUCache<Integer, String> lfuCache = new LFUCache<>(5);
42+
lfuCache.put(1, "Alpha");
43+
lfuCache.put(2, "Beta");
44+
lfuCache.put(3, "Gamma");
45+
lfuCache.put(4, "Delta");
46+
lfuCache.put(5, "Eplison");
47+
48+
//get method call will increase frequency of key 1 by 1
49+
assertEquals("Alpha", lfuCache.get(1));
50+
51+
//this operation will remove value with key as 2
52+
lfuCache.put(6, "Digamma");
53+
54+
//will return null as value with key 2 is now evicted
55+
assertEquals(null, lfuCache.get(2));
56+
57+
//should return string Digamma
58+
assertEquals("Digamma", lfuCache.get(6));
59+
60+
//this operation will remove value with key as 3
61+
lfuCache.put(7, "Zeta");
62+
63+
assertEquals(null, lfuCache.get(2));
64+
assertEquals("Zeta", lfuCache.get(7));
65+
}
66+
67+
}

0 commit comments

Comments
 (0)