Skip to content

Commit be0b1d5

Browse files
authored
Enhance docs, add more tests in LFUCache (#5949)
1 parent b64e53c commit be0b1d5

File tree

2 files changed

+82
-21
lines changed

2 files changed

+82
-21
lines changed

src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java

+10-5
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,21 @@
66
/**
77
* The {@code LFUCache} class implements a Least Frequently Used (LFU) cache.
88
* An LFU cache evicts the least frequently used item when the cache reaches its capacity.
9-
* It keeps track of how many times each item is used and maintains a doubly linked list
10-
* for efficient addition and removal of items based on their frequency of use.
9+
* It maintains a mapping of keys to nodes, where each node contains the key, its associated value,
10+
* and a frequency count that tracks how many times the item has been accessed. A doubly linked list
11+
* is used to efficiently manage the ordering of items based on their usage frequency.
1112
*
12-
* @param <K> The type of keys maintained by this cache.
13-
* @param <V> The type of mapped values.
13+
* <p>This implementation is designed to provide O(1) time complexity for both the {@code get} and
14+
* {@code put} operations, which is achieved through the use of a hashmap for quick access and a
15+
* doubly linked list for maintaining the order of item frequencies.</p>
1416
*
1517
* <p>
1618
* Reference: <a href="https://en.wikipedia.org/wiki/Least_frequently_used">LFU Cache - Wikipedia</a>
1719
* </p>
1820
*
21+
* @param <K> The type of keys maintained by this cache.
22+
* @param <V> The type of mapped values.
23+
*
1924
* @author Akshay Dubey (https://github.com/itsAkshayDubey)
2025
*/
2126
public class LFUCache<K, V> {
@@ -75,7 +80,7 @@ public LFUCache(int capacity) {
7580

7681
/**
7782
* Retrieves the value associated with the given key from the cache.
78-
* If the key exists, the node's frequency is increased and the node is repositioned
83+
* If the key exists, the node's frequency is incremented, and the node is repositioned
7984
* in the linked list based on its updated frequency.
8085
*
8186
* @param key The key whose associated value is to be returned.

src/test/java/com/thealgorithms/datastructures/caches/LFUCacheTest.java

+72-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.thealgorithms.datastructures.caches;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNull;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
46

57
import org.junit.jupiter.api.Test;
68

@@ -22,15 +24,15 @@ void testLFUCacheWithIntegerValueShouldPass() {
2224
lfuCache.put(6, 60);
2325

2426
// will return null as value with key 2 is now evicted
25-
assertEquals(null, lfuCache.get(2));
27+
assertNull(lfuCache.get(2));
2628

2729
// should return 60
2830
assertEquals(60, lfuCache.get(6));
2931

3032
// this operation will remove value with key as 3
3133
lfuCache.put(7, 70);
3234

33-
assertEquals(null, lfuCache.get(2));
35+
assertNull(lfuCache.get(2));
3436
assertEquals(70, lfuCache.get(7));
3537
}
3638

@@ -41,7 +43,7 @@ void testLFUCacheWithStringValueShouldPass() {
4143
lfuCache.put(2, "Beta");
4244
lfuCache.put(3, "Gamma");
4345
lfuCache.put(4, "Delta");
44-
lfuCache.put(5, "Eplison");
46+
lfuCache.put(5, "Epsilon");
4547

4648
// get method call will increase frequency of key 1 by 1
4749
assertEquals("Alpha", lfuCache.get(1));
@@ -50,33 +52,87 @@ void testLFUCacheWithStringValueShouldPass() {
5052
lfuCache.put(6, "Digamma");
5153

5254
// will return null as value with key 2 is now evicted
53-
assertEquals(null, lfuCache.get(2));
55+
assertNull(lfuCache.get(2));
5456

5557
// should return string Digamma
5658
assertEquals("Digamma", lfuCache.get(6));
5759

5860
// this operation will remove value with key as 3
5961
lfuCache.put(7, "Zeta");
6062

61-
assertEquals(null, lfuCache.get(2));
63+
assertNull(lfuCache.get(2));
6264
assertEquals("Zeta", lfuCache.get(7));
6365
}
6466

65-
/**
66-
* test addNodeWithUpdatedFrequency method
67-
* @author yuluo
68-
*/
6967
@Test
70-
void testAddNodeWithUpdatedFrequency() {
68+
void testUpdateValueShouldPreserveFrequency() {
7169
LFUCache<Integer, String> lfuCache = new LFUCache<>(3);
72-
lfuCache.put(1, "beijing");
73-
lfuCache.put(2, "shanghai");
74-
lfuCache.put(3, "gansu");
70+
lfuCache.put(1, "A");
71+
lfuCache.put(2, "B");
72+
lfuCache.put(3, "C");
7573

76-
assertEquals("beijing", lfuCache.get(1));
74+
assertEquals("A", lfuCache.get(1)); // Accessing key 1
75+
lfuCache.put(4, "D"); // This should evict key 2
7776

78-
lfuCache.put(1, "shanxi");
77+
assertNull(lfuCache.get(2)); // Key 2 should be evicted
78+
assertEquals("C", lfuCache.get(3)); // Key 3 should still exist
79+
assertEquals("A", lfuCache.get(1)); // Key 1 should still exist
7980

80-
assertEquals("shanxi", lfuCache.get(1));
81+
lfuCache.put(1, "Updated A"); // Update the value of key 1
82+
assertEquals("Updated A", lfuCache.get(1)); // Check if the update was successful
83+
}
84+
85+
@Test
86+
void testEvictionPolicyWhenFull() {
87+
LFUCache<Integer, String> lfuCache = new LFUCache<>(2);
88+
lfuCache.put(1, "One");
89+
lfuCache.put(2, "Two");
90+
91+
assertEquals("One", lfuCache.get(1)); // Access key 1
92+
lfuCache.put(3, "Three"); // This should evict key 2 (least frequently used)
93+
94+
assertNull(lfuCache.get(2)); // Key 2 should be evicted
95+
assertEquals("One", lfuCache.get(1)); // Key 1 should still exist
96+
assertEquals("Three", lfuCache.get(3)); // Check if key 3 exists
97+
}
98+
99+
@Test
100+
void testGetFromEmptyCacheShouldReturnNull() {
101+
LFUCache<Integer, String> lfuCache = new LFUCache<>(3);
102+
assertNull(lfuCache.get(1)); // Should return null as the cache is empty
103+
}
104+
105+
@Test
106+
void testPutNullValueShouldStoreNull() {
107+
LFUCache<Integer, String> lfuCache = new LFUCache<>(3);
108+
lfuCache.put(1, null); // Store a null value
109+
110+
assertNull(lfuCache.get(1)); // Should return null
111+
}
112+
113+
@Test
114+
void testInvalidCacheCapacityShouldThrowException() {
115+
assertThrows(IllegalArgumentException.class, () -> new LFUCache<>(0));
116+
assertThrows(IllegalArgumentException.class, () -> new LFUCache<>(-1));
117+
}
118+
119+
@Test
120+
void testMultipleAccessPatterns() {
121+
LFUCache<Integer, String> lfuCache = new LFUCache<>(5);
122+
lfuCache.put(1, "A");
123+
lfuCache.put(2, "B");
124+
lfuCache.put(3, "C");
125+
lfuCache.put(4, "D");
126+
127+
assertEquals("A", lfuCache.get(1)); // Access 1
128+
lfuCache.put(5, "E"); // Should not evict anything yet
129+
lfuCache.put(6, "F"); // Evict B
130+
131+
assertNull(lfuCache.get(2)); // B should be evicted
132+
assertEquals("C", lfuCache.get(3)); // C should still exist
133+
assertEquals("D", lfuCache.get(4)); // D should still exist
134+
assertEquals("A", lfuCache.get(1)); // A should still exist
135+
assertEquals("E", lfuCache.get(5)); // E should exist
136+
assertEquals("F", lfuCache.get(6)); // F should exist
81137
}
82138
}

0 commit comments

Comments
 (0)