Enhance docs, add more tests in LFUCache (#5949)

This commit is contained in:
Hardik Pawar
2024-10-23 23:51:03 +05:30
committed by GitHub
parent b64e53cd3d
commit be0b1d58d6
2 changed files with 82 additions and 21 deletions

View File

@ -6,16 +6,21 @@ import java.util.Map;
/** /**
* The {@code LFUCache} class implements a Least Frequently Used (LFU) cache. * The {@code LFUCache} class implements a Least Frequently Used (LFU) cache.
* An LFU cache evicts the least frequently used item when the cache reaches its capacity. * An LFU cache evicts the least frequently used item when the cache reaches its capacity.
* It keeps track of how many times each item is used and maintains a doubly linked list * It maintains a mapping of keys to nodes, where each node contains the key, its associated value,
* for efficient addition and removal of items based on their frequency of use. * and a frequency count that tracks how many times the item has been accessed. A doubly linked list
* is used to efficiently manage the ordering of items based on their usage frequency.
* *
* @param <K> The type of keys maintained by this cache. * <p>This implementation is designed to provide O(1) time complexity for both the {@code get} and
* @param <V> The type of mapped values. * {@code put} operations, which is achieved through the use of a hashmap for quick access and a
* doubly linked list for maintaining the order of item frequencies.</p>
* *
* <p> * <p>
* Reference: <a href="https://en.wikipedia.org/wiki/Least_frequently_used">LFU Cache - Wikipedia</a> * Reference: <a href="https://en.wikipedia.org/wiki/Least_frequently_used">LFU Cache - Wikipedia</a>
* </p> * </p>
* *
* @param <K> The type of keys maintained by this cache.
* @param <V> The type of mapped values.
*
* @author Akshay Dubey (https://github.com/itsAkshayDubey) * @author Akshay Dubey (https://github.com/itsAkshayDubey)
*/ */
public class LFUCache<K, V> { public class LFUCache<K, V> {
@ -75,7 +80,7 @@ public class LFUCache<K, V> {
/** /**
* Retrieves the value associated with the given key from the cache. * Retrieves the value associated with the given key from the cache.
* If the key exists, the node's frequency is increased and the node is repositioned * If the key exists, the node's frequency is incremented, and the node is repositioned
* in the linked list based on its updated frequency. * in the linked list based on its updated frequency.
* *
* @param key The key whose associated value is to be returned. * @param key The key whose associated value is to be returned.

View File

@ -1,6 +1,8 @@
package com.thealgorithms.datastructures.caches; package com.thealgorithms.datastructures.caches;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -22,7 +24,7 @@ class LFUCacheTest {
lfuCache.put(6, 60); lfuCache.put(6, 60);
// will return null as value with key 2 is now evicted // will return null as value with key 2 is now evicted
assertEquals(null, lfuCache.get(2)); assertNull(lfuCache.get(2));
// should return 60 // should return 60
assertEquals(60, lfuCache.get(6)); assertEquals(60, lfuCache.get(6));
@ -30,7 +32,7 @@ class LFUCacheTest {
// this operation will remove value with key as 3 // this operation will remove value with key as 3
lfuCache.put(7, 70); lfuCache.put(7, 70);
assertEquals(null, lfuCache.get(2)); assertNull(lfuCache.get(2));
assertEquals(70, lfuCache.get(7)); assertEquals(70, lfuCache.get(7));
} }
@ -41,7 +43,7 @@ class LFUCacheTest {
lfuCache.put(2, "Beta"); lfuCache.put(2, "Beta");
lfuCache.put(3, "Gamma"); lfuCache.put(3, "Gamma");
lfuCache.put(4, "Delta"); lfuCache.put(4, "Delta");
lfuCache.put(5, "Eplison"); lfuCache.put(5, "Epsilon");
// get method call will increase frequency of key 1 by 1 // get method call will increase frequency of key 1 by 1
assertEquals("Alpha", lfuCache.get(1)); assertEquals("Alpha", lfuCache.get(1));
@ -50,7 +52,7 @@ class LFUCacheTest {
lfuCache.put(6, "Digamma"); lfuCache.put(6, "Digamma");
// will return null as value with key 2 is now evicted // will return null as value with key 2 is now evicted
assertEquals(null, lfuCache.get(2)); assertNull(lfuCache.get(2));
// should return string Digamma // should return string Digamma
assertEquals("Digamma", lfuCache.get(6)); assertEquals("Digamma", lfuCache.get(6));
@ -58,25 +60,79 @@ class LFUCacheTest {
// this operation will remove value with key as 3 // this operation will remove value with key as 3
lfuCache.put(7, "Zeta"); lfuCache.put(7, "Zeta");
assertEquals(null, lfuCache.get(2)); assertNull(lfuCache.get(2));
assertEquals("Zeta", lfuCache.get(7)); assertEquals("Zeta", lfuCache.get(7));
} }
/**
* test addNodeWithUpdatedFrequency method
* @author yuluo
*/
@Test @Test
void testAddNodeWithUpdatedFrequency() { void testUpdateValueShouldPreserveFrequency() {
LFUCache<Integer, String> lfuCache = new LFUCache<>(3); LFUCache<Integer, String> lfuCache = new LFUCache<>(3);
lfuCache.put(1, "beijing"); lfuCache.put(1, "A");
lfuCache.put(2, "shanghai"); lfuCache.put(2, "B");
lfuCache.put(3, "gansu"); lfuCache.put(3, "C");
assertEquals("beijing", lfuCache.get(1)); assertEquals("A", lfuCache.get(1)); // Accessing key 1
lfuCache.put(4, "D"); // This should evict key 2
lfuCache.put(1, "shanxi"); assertNull(lfuCache.get(2)); // Key 2 should be evicted
assertEquals("C", lfuCache.get(3)); // Key 3 should still exist
assertEquals("A", lfuCache.get(1)); // Key 1 should still exist
assertEquals("shanxi", lfuCache.get(1)); lfuCache.put(1, "Updated A"); // Update the value of key 1
assertEquals("Updated A", lfuCache.get(1)); // Check if the update was successful
}
@Test
void testEvictionPolicyWhenFull() {
LFUCache<Integer, String> lfuCache = new LFUCache<>(2);
lfuCache.put(1, "One");
lfuCache.put(2, "Two");
assertEquals("One", lfuCache.get(1)); // Access key 1
lfuCache.put(3, "Three"); // This should evict key 2 (least frequently used)
assertNull(lfuCache.get(2)); // Key 2 should be evicted
assertEquals("One", lfuCache.get(1)); // Key 1 should still exist
assertEquals("Three", lfuCache.get(3)); // Check if key 3 exists
}
@Test
void testGetFromEmptyCacheShouldReturnNull() {
LFUCache<Integer, String> lfuCache = new LFUCache<>(3);
assertNull(lfuCache.get(1)); // Should return null as the cache is empty
}
@Test
void testPutNullValueShouldStoreNull() {
LFUCache<Integer, String> lfuCache = new LFUCache<>(3);
lfuCache.put(1, null); // Store a null value
assertNull(lfuCache.get(1)); // Should return null
}
@Test
void testInvalidCacheCapacityShouldThrowException() {
assertThrows(IllegalArgumentException.class, () -> new LFUCache<>(0));
assertThrows(IllegalArgumentException.class, () -> new LFUCache<>(-1));
}
@Test
void testMultipleAccessPatterns() {
LFUCache<Integer, String> lfuCache = new LFUCache<>(5);
lfuCache.put(1, "A");
lfuCache.put(2, "B");
lfuCache.put(3, "C");
lfuCache.put(4, "D");
assertEquals("A", lfuCache.get(1)); // Access 1
lfuCache.put(5, "E"); // Should not evict anything yet
lfuCache.put(6, "F"); // Evict B
assertNull(lfuCache.get(2)); // B should be evicted
assertEquals("C", lfuCache.get(3)); // C should still exist
assertEquals("D", lfuCache.get(4)); // D should still exist
assertEquals("A", lfuCache.get(1)); // A should still exist
assertEquals("E", lfuCache.get(5)); // E should exist
assertEquals("F", lfuCache.get(6)); // F should exist
} }
} }