A cache keeps a few recent results close at hand so they are quick to read again. It can only hold a limited number of entries, so when it is full it must throw one away. An LRU (Least Recently Used) cache throws away the entry that was used longest ago. Build a class LRUCache that stores integer keys with integer values.
The class keeps: private int capacity, a Dictionary<int, int> called map (key to value) and a List<int> called order (the keys, least recently used first).
Create it: new LRUCache(capacity) makes an empty cache that holds at most capacity entries.
Methods and rules:
int Get(int key)returns the value stored forkey. A hit counts as a use: the key becomes the most recently used. If the key is not in the cache (never stored, or already evicted), return-1and change nothing.void Put(int key, int value)stores the value forkey, and the key becomes the most recently used.- If the key is already in the cache, replace its value. Nothing is evicted.
- If the key is new and the cache already holds
capacityentries, first evict the least recently used entry (remove it from the map and fromorder), then store the new one.
- Keep
ordersorted by use: the least recently used key at the front, the most recently used at the end. Every use moves the key to the end.
Tools for this language:
- The constructor
public LRUCache(int capacity)saves the capacity; the fields arenew Dictionary<int, int>()andnew List<int>(). map.ContainsKey(key)tells whether a key is stored;map[key] = valuestores or replaces a value;map.Remove(key)deletes an entry;map.Countcounts the entries.order.Remove(key)removes an item by value,order.Add(key)adds at the end, andorder[0]withorder.RemoveAt(0)reads and removes the first item.
This version walks over the list of keys for each operation, which is fine for learning. Interview solutions reach O(1) with a hash map plus a doubly linked list.
You start with the code that tests your class (write LRUCache above it):
class Solution {
public void MainFunction() {
var c = new LRUCache(2);
c.Put(1, 10);
c.Put(2, 20);
Console.WriteLine("Get 1: " + c.Get(1));
c.Put(3, 30);
Console.WriteLine("Get 2: " + c.Get(2));
Console.WriteLine("Get 3: " + c.Get(3));
Console.WriteLine("Miss 9: " + c.Get(9));
c.Put(1, 11);
Console.WriteLine("Update 1: " + c.Get(1));
c.Put(4, 40);
Console.WriteLine("Evicted 3: " + c.Get(3));
Console.WriteLine("Kept 1: " + c.Get(1));
Console.WriteLine("Kept 4: " + c.Get(4));
var s = new LRUCache(1);
s.Put(1, 100);
s.Put(2, 200);
Console.WriteLine("Small 1: " + s.Get(1));
Console.WriteLine("Small 2: " + s.Get(2));
}
}
Expected output:
Get 1: 10
Get 2: -1
Get 3: 30
Miss 9: -1
Update 1: 11
Evicted 3: -1
Kept 1: 11
Kept 4: 40
Small 1: -1
Small 2: 200
Check it against the rules: Get 2 is -1 because key 2 was the least recently used when key 3 arrived (key 1 had just been read). Updating key 1 to 11 does not evict anything. With capacity 1, the second put evicts the first key.
Two structures work together: the map gives a fast lookup of the value, and
order remembers which key was used when. Both must always hold the same keys.
order.Remove(key) then order.Add(key) moves a key to the end. order[0] is the least recently used key.
class LRUCache {
private int capacity;
private Dictionary<int, int> map = new Dictionary<int, int>();
private List<int> order = new List<int>();
public LRUCache(int capacity) {
this.capacity = capacity;
}
public int Get(int key) {
if (!map.ContainsKey(key)) return -1;
order.Remove(key);
order.Add(key);
return map[key];
}
public void Put(int key, int value) {
if (map.ContainsKey(key)) {
order.Remove(key);
} else if (map.Count >= capacity) {
int evict = order[0];
order.RemoveAt(0);
map.Remove(evict);
}
map[key] = value;
order.Add(key);
}
}
Free plan: 30 tutor messages. Premium is unlimited.
