Skip to content

Commit d868982

Browse files
authored
Enhance docs, add more tests in MRUCache (#5951)
1 parent 8db9d10 commit d868982

File tree

2 files changed

+153
-25
lines changed

2 files changed

+153
-25
lines changed

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

+79-18
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
import java.util.Map;
55

66
/**
7-
* Most recently used (MRU)
7+
* Represents a Most Recently Used (MRU) Cache.
88
* <p>
9-
* In contrast to Least Recently Used (LRU), MRU discards the most recently used
10-
* items first.
11-
* https://en.wikipedia.org/wiki/Cache_replacement_policies#Most_recently_used_(MRU)
9+
* In contrast to the Least Recently Used (LRU) strategy, the MRU caching policy
10+
* evicts the most recently accessed items first. This class provides methods to
11+
* store key-value pairs and manage cache eviction based on this policy.
1212
*
13-
* @param <K> key type
14-
* @param <V> value type
13+
* For more information, refer to:
14+
* <a href="https://en.wikipedia.org/wiki/Cache_replacement_policies#Most_recently_used_(MRU)">MRU on Wikipedia</a>.
15+
*
16+
* @param <K> the type of keys maintained by this cache
17+
* @param <V> the type of values associated with the keys
1518
*/
1619
public class MRUCache<K, V> {
1720

@@ -21,40 +24,74 @@ public class MRUCache<K, V> {
2124
private int cap;
2225
private static final int DEFAULT_CAP = 100;
2326

27+
/**
28+
* Creates an MRUCache with the default capacity.
29+
*/
2430
public MRUCache() {
2531
setCapacity(DEFAULT_CAP);
2632
}
2733

34+
/**
35+
* Creates an MRUCache with a specified capacity.
36+
*
37+
* @param cap the maximum number of items the cache can hold
38+
*/
39+
public MRUCache(int cap) {
40+
setCapacity(cap);
41+
}
42+
43+
/**
44+
* Sets the capacity of the cache and evicts items if the new capacity
45+
* is less than the current number of items.
46+
*
47+
* @param newCapacity the new capacity to set
48+
*/
2849
private void setCapacity(int newCapacity) {
2950
checkCapacity(newCapacity);
30-
for (int i = data.size(); i > newCapacity; i--) {
51+
while (data.size() > newCapacity) {
3152
Entry<K, V> evicted = evict();
3253
data.remove(evicted.getKey());
3354
}
3455
this.cap = newCapacity;
3556
}
3657

58+
/**
59+
* Checks if the specified capacity is valid.
60+
*
61+
* @param capacity the capacity to check
62+
* @throws IllegalArgumentException if the capacity is less than or equal to zero
63+
*/
3764
private void checkCapacity(int capacity) {
3865
if (capacity <= 0) {
39-
throw new RuntimeException("capacity must greater than 0!");
66+
throw new IllegalArgumentException("Capacity must be greater than 0!");
4067
}
4168
}
4269

70+
/**
71+
* Evicts the most recently used entry from the cache.
72+
*
73+
* @return the evicted entry
74+
* @throws RuntimeException if the cache is empty
75+
*/
4376
private Entry<K, V> evict() {
4477
if (head == null) {
45-
throw new RuntimeException("cache cannot be empty!");
78+
throw new RuntimeException("Cache cannot be empty!");
4679
}
4780
final Entry<K, V> evicted = this.tail;
4881
tail = evicted.getPreEntry();
49-
tail.setNextEntry(null);
82+
if (tail != null) {
83+
tail.setNextEntry(null);
84+
}
5085
evicted.setNextEntry(null);
5186
return evicted;
5287
}
5388

54-
public MRUCache(int cap) {
55-
setCapacity(cap);
56-
}
57-
89+
/**
90+
* Retrieves the value associated with the specified key.
91+
*
92+
* @param key the key whose associated value is to be returned
93+
* @return the value associated with the specified key, or null if the key does not exist
94+
*/
5895
public V get(K key) {
5996
if (!data.containsKey(key)) {
6097
return null;
@@ -64,11 +101,19 @@ public V get(K key) {
64101
return entry.getValue();
65102
}
66103

104+
/**
105+
* Associates the specified value with the specified key in the cache.
106+
* If the key already exists, its value is updated and the entry is moved to the most recently used position.
107+
* If the cache is full, the most recently used entry is evicted before adding the new entry.
108+
*
109+
* @param key the key with which the specified value is to be associated
110+
* @param value the value to be associated with the specified key
111+
*/
67112
public void put(K key, V value) {
68113
if (data.containsKey(key)) {
69-
final Entry<K, V> exitingEntry = data.get(key);
70-
exitingEntry.setValue(value);
71-
moveEntryToLast(exitingEntry);
114+
final Entry<K, V> existingEntry = data.get(key);
115+
existingEntry.setValue(value);
116+
moveEntryToLast(existingEntry);
72117
return;
73118
}
74119
Entry<K, V> newEntry;
@@ -84,6 +129,11 @@ public void put(K key, V value) {
84129
data.put(key, newEntry);
85130
}
86131

132+
/**
133+
* Adds a new entry to the cache and updates the head and tail pointers accordingly.
134+
*
135+
* @param newEntry the new entry to be added
136+
*/
87137
private void addNewEntry(Entry<K, V> newEntry) {
88138
if (data.isEmpty()) {
89139
head = newEntry;
@@ -96,6 +146,11 @@ private void addNewEntry(Entry<K, V> newEntry) {
96146
tail = newEntry;
97147
}
98148

149+
/**
150+
* Moves the specified entry to the most recently used position in the cache.
151+
*
152+
* @param entry the entry to be moved
153+
*/
99154
private void moveEntryToLast(Entry<K, V> entry) {
100155
if (tail == entry) {
101156
return;
@@ -117,8 +172,14 @@ private void moveEntryToLast(Entry<K, V> entry) {
117172
tail = entry;
118173
}
119174

175+
/**
176+
* A nested class representing an entry in the cache, which holds a key-value pair
177+
* and references to the previous and next entries in the linked list structure.
178+
*
179+
* @param <I> the type of the key
180+
* @param <J> the type of the value
181+
*/
120182
static final class Entry<I, J> {
121-
122183
private Entry<I, J> preEntry;
123184
private Entry<I, J> nextEntry;
124185
private I key;

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

+74-7
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,27 @@ public class MRUCacheTest {
1111

1212
@Test
1313
public void putAndGetIntegerValues() {
14-
MRUCache<Integer, Integer> lruCache = new MRUCache<>(SIZE);
14+
MRUCache<Integer, Integer> mruCache = new MRUCache<>(SIZE);
1515

1616
for (int i = 0; i < SIZE; i++) {
17-
lruCache.put(i, i);
17+
mruCache.put(i, i);
1818
}
1919

2020
for (int i = 0; i < SIZE; i++) {
21-
assertEquals(i, lruCache.get(i));
21+
assertEquals(i, mruCache.get(i));
2222
}
2323
}
2424

2525
@Test
2626
public void putAndGetStringValues() {
27-
MRUCache<String, String> lruCache = new MRUCache<>(SIZE);
27+
MRUCache<String, String> mruCache = new MRUCache<>(SIZE);
2828

2929
for (int i = 0; i < SIZE; i++) {
30-
lruCache.put("key" + i, "value" + i);
30+
mruCache.put("key" + i, "value" + i);
3131
}
3232

3333
for (int i = 0; i < SIZE; i++) {
34-
assertEquals("value" + i, lruCache.get("key" + i));
34+
assertEquals("value" + i, mruCache.get("key" + i));
3535
}
3636
}
3737

@@ -53,6 +53,73 @@ public void overCapacity() {
5353
mruCache.put(i, i);
5454
}
5555

56-
assertEquals(9, mruCache.get(9));
56+
// After inserting 10 items, the cache should have evicted the least recently used ones.
57+
assertEquals(9, mruCache.get(9)); // Most recently used
58+
assertEquals(0, mruCache.get(0)); // Least recently used, should be evicted
59+
}
60+
61+
@Test
62+
public void overwriteExistingKey() {
63+
MRUCache<Integer, String> mruCache = new MRUCache<>(SIZE);
64+
mruCache.put(1, "one");
65+
mruCache.put(1, "uno"); // Overwriting the value for key 1
66+
67+
assertEquals("uno", mruCache.get(1));
68+
assertNull(mruCache.get(2)); // Ensure other keys are unaffected
69+
}
70+
71+
@Test
72+
public void evictionOrder() {
73+
MRUCache<Integer, Integer> mruCache = new MRUCache<>(SIZE);
74+
75+
for (int i = 0; i < SIZE; i++) {
76+
mruCache.put(i, i);
77+
}
78+
79+
// Access a key to make it most recently used
80+
mruCache.get(2);
81+
82+
// Add new items to trigger eviction
83+
mruCache.put(5, 5);
84+
mruCache.put(6, 6);
85+
86+
// Key 3 should be evicted since 2 is the most recently used
87+
assertEquals(3, mruCache.get(3));
88+
assertEquals(4, mruCache.get(4)); // Key 4 should still be available
89+
assertEquals(6, mruCache.get(6)); // Key 6 should be available
90+
}
91+
92+
@Test
93+
public void cacheHandlesLargeValues() {
94+
MRUCache<String, String> mruCache = new MRUCache<>(SIZE);
95+
96+
for (int i = 0; i < SIZE; i++) {
97+
mruCache.put("key" + i, "value" + i);
98+
}
99+
100+
// Verify values
101+
for (int i = 0; i < SIZE; i++) {
102+
assertEquals("value" + i, mruCache.get("key" + i));
103+
}
104+
105+
// Add large value
106+
mruCache.put("largeKey", "largeValue");
107+
108+
// Verify eviction of the least recently used (key 0 should be evicted)
109+
assertEquals("value0", mruCache.get("key0"));
110+
assertEquals("largeValue", mruCache.get("largeKey"));
111+
}
112+
113+
@Test
114+
public void testEmptyCacheBehavior() {
115+
MRUCache<Integer, Integer> mruCache = new MRUCache<>(SIZE);
116+
117+
// Verify that accessing any key returns null
118+
assertNull(mruCache.get(1));
119+
assertNull(mruCache.get(100));
120+
121+
// Adding to cache and checking again
122+
mruCache.put(1, 10);
123+
assertEquals(10, mruCache.get(1));
57124
}
58125
}

0 commit comments

Comments
 (0)