From 54d174ef6fe7d30ac98d1c21b3f91e01d12e7362 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Wed, 23 Oct 2024 12:55:59 +0530 Subject: [PATCH 1/8] refactor: Enhance docs, add more tests in `MRUCache` --- .../datastructures/caches/MRUCache.java | 101 ++++++++++++++---- .../datastructures/caches/MRUCacheTest.java | 101 ++++++++++++++++-- 2 files changed, 175 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java b/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java index 9c155be8b195..e4378520d965 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.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 V get(K key) { 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 void put(K key, V value) { 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 @@ private void addNewEntry(Entry newEntry) { 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,17 +172,23 @@ private void moveEntryToLast(Entry entry) { 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; private J value; - Entry() { + public Entry() { } - Entry(Entry preEntry, Entry nextEntry, I key, J value) { + public Entry(Entry preEntry, Entry nextEntry, I key, J value) { this.preEntry = preEntry; this.nextEntry = nextEntry; this.key = key; diff --git a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java index 447feb38e788..1a5d505685e8 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,93 @@ public void overCapacity() { 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)); + } + + @Test + public void capacityValidation() { + // Check that an exception is thrown for invalid capacities + Exception exception = null; + try { + new MRUCache<>(0); // Should throw an exception + } catch (IllegalArgumentException e) { + exception = e; + } + assertEquals("Capacity must be greater than 0!", exception.getMessage()); + + exception = null; + try { + new MRUCache<>(-1); // Should throw an exception + } catch (IllegalArgumentException e) { + exception = e; + } + assertEquals("Capacity must be greater than 0!", exception.getMessage()); } } From 7c40f778475f2d59e631912a17edeb9cff9a8b57 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Wed, 23 Oct 2024 13:02:31 +0530 Subject: [PATCH 2/8] Fix --- .../com/thealgorithms/datastructures/caches/MRUCache.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java b/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java index e4378520d965..93b13e6ad654 100644 --- a/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java +++ b/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java @@ -185,10 +185,10 @@ static final class Entry { private I key; private J value; - public Entry() { + Entry() { } - public Entry(Entry preEntry, Entry nextEntry, I key, J value) { + Entry(Entry preEntry, Entry nextEntry, I key, J value) { this.preEntry = preEntry; this.nextEntry = nextEntry; this.key = key; From 40b7f7dbf9eeb1618fb93227e14fb12721afc294 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Wed, 23 Oct 2024 13:03:55 +0530 Subject: [PATCH 3/8] refactor: Enhance docs, add more tests in `DynamicArray` --- .../datastructures/caches/MRUCacheTest.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java index 1a5d505685e8..be521adf423e 100644 --- a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java +++ b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java @@ -1,6 +1,7 @@ package com.thealgorithms.datastructures.caches; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import org.junit.jupiter.api.Test; @@ -127,19 +128,30 @@ public void testEmptyCacheBehavior() { public void capacityValidation() { // Check that an exception is thrown for invalid capacities Exception exception = null; + + // Testing for zero capacity try { new MRUCache<>(0); // Should throw an exception } catch (IllegalArgumentException e) { - exception = e; + exception = e; // Store the exception if caught } + + // Ensure exception is not null before accessing its message + assertNotNull(exception, "Expected IllegalArgumentException for capacity 0"); assertEquals("Capacity must be greater than 0!", exception.getMessage()); + // Resetting exception for the next test exception = null; + + // Testing for negative capacity try { new MRUCache<>(-1); // Should throw an exception } catch (IllegalArgumentException e) { - exception = e; + exception = e; // Store the exception if caught } + + // Ensure exception is not null before accessing its message + assertNotNull(exception, "Expected IllegalArgumentException for capacity -1"); assertEquals("Capacity must be greater than 0!", exception.getMessage()); } } From 81a9ddf071a0daa535a11a22672469e40a3466f1 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Wed, 23 Oct 2024 13:03:55 +0530 Subject: [PATCH 4/8] Fix --- .../datastructures/caches/MRUCacheTest.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java index 1a5d505685e8..be521adf423e 100644 --- a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java +++ b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java @@ -1,6 +1,7 @@ package com.thealgorithms.datastructures.caches; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import org.junit.jupiter.api.Test; @@ -127,19 +128,30 @@ public void testEmptyCacheBehavior() { public void capacityValidation() { // Check that an exception is thrown for invalid capacities Exception exception = null; + + // Testing for zero capacity try { new MRUCache<>(0); // Should throw an exception } catch (IllegalArgumentException e) { - exception = e; + exception = e; // Store the exception if caught } + + // Ensure exception is not null before accessing its message + assertNotNull(exception, "Expected IllegalArgumentException for capacity 0"); assertEquals("Capacity must be greater than 0!", exception.getMessage()); + // Resetting exception for the next test exception = null; + + // Testing for negative capacity try { new MRUCache<>(-1); // Should throw an exception } catch (IllegalArgumentException e) { - exception = e; + exception = e; // Store the exception if caught } + + // Ensure exception is not null before accessing its message + assertNotNull(exception, "Expected IllegalArgumentException for capacity -1"); assertEquals("Capacity must be greater than 0!", exception.getMessage()); } } From 1661b832747bcee92fd917e2697ccd8ce5c3b735 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Wed, 23 Oct 2024 13:12:28 +0530 Subject: [PATCH 5/8] Fix --- .../datastructures/caches/MRUCacheTest.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java index be521adf423e..207787b0e96c 100644 --- a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java +++ b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java @@ -138,7 +138,10 @@ public void capacityValidation() { // Ensure exception is not null before accessing its message assertNotNull(exception, "Expected IllegalArgumentException for capacity 0"); - assertEquals("Capacity must be greater than 0!", exception.getMessage()); + + if (exception != null) { + assertEquals("Capacity must be greater than 0!", exception.getMessage()); + } // Resetting exception for the next test exception = null; @@ -152,6 +155,9 @@ public void capacityValidation() { // Ensure exception is not null before accessing its message assertNotNull(exception, "Expected IllegalArgumentException for capacity -1"); - assertEquals("Capacity must be greater than 0!", exception.getMessage()); + + if (exception != null) { + assertEquals("Capacity must be greater than 0!", exception.getMessage()); + } } } From 7d27c1de9dc467193e5f8c17be35356748de9b92 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Wed, 23 Oct 2024 13:18:23 +0530 Subject: [PATCH 6/8] Fix --- .../datastructures/caches/MRUCacheTest.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java index 207787b0e96c..78206b72781f 100644 --- a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java +++ b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Test; @@ -132,32 +133,28 @@ public void capacityValidation() { // Testing for zero capacity try { new MRUCache<>(0); // Should throw an exception + // If no exception is thrown, we should fail the test + fail("Expected IllegalArgumentException for capacity 0"); } catch (IllegalArgumentException e) { exception = e; // Store the exception if caught } - // Ensure exception is not null before accessing its message + // Ensure exception is not null and check the message assertNotNull(exception, "Expected IllegalArgumentException for capacity 0"); - - if (exception != null) { - assertEquals("Capacity must be greater than 0!", exception.getMessage()); - } + assertEquals("Capacity must be greater than 0!", exception.getMessage()); // Resetting exception for the next test - exception = null; - // Testing for negative capacity try { new MRUCache<>(-1); // Should throw an exception + // If no exception is thrown, we should fail the test + fail("Expected IllegalArgumentException for capacity -1"); } catch (IllegalArgumentException e) { exception = e; // Store the exception if caught } - // Ensure exception is not null before accessing its message + // Ensure exception is not null and check the message assertNotNull(exception, "Expected IllegalArgumentException for capacity -1"); - - if (exception != null) { - assertEquals("Capacity must be greater than 0!", exception.getMessage()); - } + assertEquals("Capacity must be greater than 0!", exception.getMessage()); } } From cde7e5935e22fc56dea3e3cd2ed347c6b8490c8f Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:27:02 +0530 Subject: [PATCH 7/8] Update MRUCacheTest.java --- .../datastructures/caches/MRUCacheTest.java | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java index 78206b72781f..5a53afd2d811 100644 --- a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java +++ b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java @@ -124,37 +124,4 @@ public void testEmptyCacheBehavior() { mruCache.put(1, 10); assertEquals(10, mruCache.get(1)); } - - @Test - public void capacityValidation() { - // Check that an exception is thrown for invalid capacities - Exception exception = null; - - // Testing for zero capacity - try { - new MRUCache<>(0); // Should throw an exception - // If no exception is thrown, we should fail the test - fail("Expected IllegalArgumentException for capacity 0"); - } catch (IllegalArgumentException e) { - exception = e; // Store the exception if caught - } - - // Ensure exception is not null and check the message - assertNotNull(exception, "Expected IllegalArgumentException for capacity 0"); - assertEquals("Capacity must be greater than 0!", exception.getMessage()); - - // Resetting exception for the next test - // Testing for negative capacity - try { - new MRUCache<>(-1); // Should throw an exception - // If no exception is thrown, we should fail the test - fail("Expected IllegalArgumentException for capacity -1"); - } catch (IllegalArgumentException e) { - exception = e; // Store the exception if caught - } - - // Ensure exception is not null and check the message - assertNotNull(exception, "Expected IllegalArgumentException for capacity -1"); - assertEquals("Capacity must be greater than 0!", exception.getMessage()); - } } From 00cca7441178216d4a3e8fa10a2ef3180d7db924 Mon Sep 17 00:00:00 2001 From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:52:11 +0530 Subject: [PATCH 8/8] Update MRUCacheTest.java --- .../com/thealgorithms/datastructures/caches/MRUCacheTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java index 5a53afd2d811..50303ba239f6 100644 --- a/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java +++ b/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java @@ -1,9 +1,7 @@ package com.thealgorithms.datastructures.caches; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Test;