diff --git a/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java b/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java
index 9c155be8b..93b13e6ad 100644
--- a/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java
+++ b/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java
@@ -4,14 +4,17 @@ import java.util.HashMap;
import java.util.Map;
/**
- * Most recently used (MRU)
+ * Represents a Most Recently Used (MRU) Cache.
*
- * In contrast to Least Recently Used (LRU), MRU discards the most recently used
- * items first.
- * https://en.wikipedia.org/wiki/Cache_replacement_policies#Most_recently_used_(MRU)
+ * In contrast to the Least Recently Used (LRU) strategy, the MRU caching policy
+ * evicts the most recently accessed items first. This class provides methods to
+ * store key-value pairs and manage cache eviction based on this policy.
*
- * @param key type
- * @param value type
+ * For more information, refer to:
+ * MRU on Wikipedia.
+ *
+ * @param the type of keys maintained by this cache
+ * @param the type of values associated with the keys
*/
public class MRUCache {
@@ -21,40 +24,74 @@ public class MRUCache {
private int cap;
private static final int DEFAULT_CAP = 100;
+ /**
+ * Creates an MRUCache with the default capacity.
+ */
public MRUCache() {
setCapacity(DEFAULT_CAP);
}
+ /**
+ * Creates an MRUCache with a specified capacity.
+ *
+ * @param cap the maximum number of items the cache can hold
+ */
+ public MRUCache(int cap) {
+ setCapacity(cap);
+ }
+
+ /**
+ * Sets the capacity of the cache and evicts items if the new capacity
+ * is less than the current number of items.
+ *
+ * @param newCapacity the new capacity to set
+ */
private void setCapacity(int newCapacity) {
checkCapacity(newCapacity);
- for (int i = data.size(); i > newCapacity; i--) {
+ while (data.size() > newCapacity) {
Entry evicted = evict();
data.remove(evicted.getKey());
}
this.cap = newCapacity;
}
+ /**
+ * Checks if the specified capacity is valid.
+ *
+ * @param capacity the capacity to check
+ * @throws IllegalArgumentException if the capacity is less than or equal to zero
+ */
private void checkCapacity(int capacity) {
if (capacity <= 0) {
- throw new RuntimeException("capacity must greater than 0!");
+ throw new IllegalArgumentException("Capacity must be greater than 0!");
}
}
+ /**
+ * Evicts the most recently used entry from the cache.
+ *
+ * @return the evicted entry
+ * @throws RuntimeException if the cache is empty
+ */
private Entry evict() {
if (head == null) {
- throw new RuntimeException("cache cannot be empty!");
+ throw new RuntimeException("Cache cannot be empty!");
}
final Entry evicted = this.tail;
tail = evicted.getPreEntry();
- tail.setNextEntry(null);
+ if (tail != null) {
+ tail.setNextEntry(null);
+ }
evicted.setNextEntry(null);
return evicted;
}
- public MRUCache(int cap) {
- setCapacity(cap);
- }
-
+ /**
+ * Retrieves the value associated with the specified key.
+ *
+ * @param key the key whose associated value is to be returned
+ * @return the value associated with the specified key, or null if the key does not exist
+ */
public V get(K key) {
if (!data.containsKey(key)) {
return null;
@@ -64,11 +101,19 @@ public class MRUCache {
return entry.getValue();
}
+ /**
+ * Associates the specified value with the specified key in the cache.
+ * If the key already exists, its value is updated and the entry is moved to the most recently used position.
+ * If the cache is full, the most recently used entry is evicted before adding the new entry.
+ *
+ * @param key the key with which the specified value is to be associated
+ * @param value the value to be associated with the specified key
+ */
public void put(K key, V value) {
if (data.containsKey(key)) {
- final Entry exitingEntry = data.get(key);
- exitingEntry.setValue(value);
- moveEntryToLast(exitingEntry);
+ final Entry existingEntry = data.get(key);
+ existingEntry.setValue(value);
+ moveEntryToLast(existingEntry);
return;
}
Entry newEntry;
@@ -84,6 +129,11 @@ public class MRUCache {
data.put(key, newEntry);
}
+ /**
+ * Adds a new entry to the cache and updates the head and tail pointers accordingly.
+ *
+ * @param newEntry the new entry to be added
+ */
private void addNewEntry(Entry newEntry) {
if (data.isEmpty()) {
head = newEntry;
@@ -96,6 +146,11 @@ public class MRUCache {
tail = newEntry;
}
+ /**
+ * Moves the specified entry to the most recently used position in the cache.
+ *
+ * @param entry the entry to be moved
+ */
private void moveEntryToLast(Entry entry) {
if (tail == entry) {
return;
@@ -117,8 +172,14 @@ public class MRUCache {
tail = entry;
}
+ /**
+ * A nested class representing an entry in the cache, which holds a key-value pair
+ * and references to the previous and next entries in the linked list structure.
+ *
+ * @param the type of the key
+ * @param the type of the value
+ */
static final class Entry {
-
private Entry preEntry;
private Entry nextEntry;
private I key;
diff --git a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java
index 447feb38e..50303ba23 100644
--- a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java
@@ -11,27 +11,27 @@ public class MRUCacheTest {
@Test
public void putAndGetIntegerValues() {
- MRUCache lruCache = new MRUCache<>(SIZE);
+ MRUCache mruCache = new MRUCache<>(SIZE);
for (int i = 0; i < SIZE; i++) {
- lruCache.put(i, i);
+ mruCache.put(i, i);
}
for (int i = 0; i < SIZE; i++) {
- assertEquals(i, lruCache.get(i));
+ assertEquals(i, mruCache.get(i));
}
}
@Test
public void putAndGetStringValues() {
- MRUCache lruCache = new MRUCache<>(SIZE);
+ MRUCache mruCache = new MRUCache<>(SIZE);
for (int i = 0; i < SIZE; i++) {
- lruCache.put("key" + i, "value" + i);
+ mruCache.put("key" + i, "value" + i);
}
for (int i = 0; i < SIZE; i++) {
- assertEquals("value" + i, lruCache.get("key" + i));
+ assertEquals("value" + i, mruCache.get("key" + i));
}
}
@@ -53,6 +53,73 @@ public class MRUCacheTest {
mruCache.put(i, i);
}
- assertEquals(9, mruCache.get(9));
+ // After inserting 10 items, the cache should have evicted the least recently used ones.
+ assertEquals(9, mruCache.get(9)); // Most recently used
+ assertEquals(0, mruCache.get(0)); // Least recently used, should be evicted
+ }
+
+ @Test
+ public void overwriteExistingKey() {
+ MRUCache mruCache = new MRUCache<>(SIZE);
+ mruCache.put(1, "one");
+ mruCache.put(1, "uno"); // Overwriting the value for key 1
+
+ assertEquals("uno", mruCache.get(1));
+ assertNull(mruCache.get(2)); // Ensure other keys are unaffected
+ }
+
+ @Test
+ public void evictionOrder() {
+ MRUCache mruCache = new MRUCache<>(SIZE);
+
+ for (int i = 0; i < SIZE; i++) {
+ mruCache.put(i, i);
+ }
+
+ // Access a key to make it most recently used
+ mruCache.get(2);
+
+ // Add new items to trigger eviction
+ mruCache.put(5, 5);
+ mruCache.put(6, 6);
+
+ // Key 3 should be evicted since 2 is the most recently used
+ assertEquals(3, mruCache.get(3));
+ assertEquals(4, mruCache.get(4)); // Key 4 should still be available
+ assertEquals(6, mruCache.get(6)); // Key 6 should be available
+ }
+
+ @Test
+ public void cacheHandlesLargeValues() {
+ MRUCache mruCache = new MRUCache<>(SIZE);
+
+ for (int i = 0; i < SIZE; i++) {
+ mruCache.put("key" + i, "value" + i);
+ }
+
+ // Verify values
+ for (int i = 0; i < SIZE; i++) {
+ assertEquals("value" + i, mruCache.get("key" + i));
+ }
+
+ // Add large value
+ mruCache.put("largeKey", "largeValue");
+
+ // Verify eviction of the least recently used (key 0 should be evicted)
+ assertEquals("value0", mruCache.get("key0"));
+ assertEquals("largeValue", mruCache.get("largeKey"));
+ }
+
+ @Test
+ public void testEmptyCacheBehavior() {
+ MRUCache mruCache = new MRUCache<>(SIZE);
+
+ // Verify that accessing any key returns null
+ assertNull(mruCache.get(1));
+ assertNull(mruCache.get(100));
+
+ // Adding to cache and checking again
+ mruCache.put(1, 10);
+ assertEquals(10, mruCache.get(1));
}
}