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 val capacity: Int (declared in the class header), a mutable Map[Int, Int] called map (key to value) and an ArrayBuffer[Int] called order (the keys, least recently used first).
Create it: LRUCache(capacity) makes an empty cache that holds at most capacity entries.
Methods and rules:
def get(key: Int): Intreturns 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.def put(key: Int, value: Int): Unitstores 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 primary constructor is the class header:
class LRUCache(private val capacity: Int). The fields arescala.collection.mutable.Map()andscala.collection.mutable.ArrayBuffer(). map.contains(key)tells whether a key is stored;map(key) = valuestores or replaces a value;map.remove(key)deletes an entry;map.sizecounts the entries.order -= keyremoves an item by value,order += keyadds at the end, andorder(0)withorder.remove(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 {
def mainFunction(): Unit = {
val c = LRUCache(2)
c.put(1, 10)
c.put(2, 20)
println("Get 1: " + c.get(1))
c.put(3, 30)
println("Get 2: " + c.get(2))
println("Get 3: " + c.get(3))
println("Miss 9: " + c.get(9))
c.put(1, 11)
println("Update 1: " + c.get(1))
c.put(4, 40)
println("Evicted 3: " + c.get(3))
println("Kept 1: " + c.get(1))
println("Kept 4: " + c.get(4))
val s = LRUCache(1)
s.put(1, 100)
s.put(2, 200)
println("Small 1: " + s.get(1))
println("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 -= key then order += key moves a key to the end. order(0) is the least recently used key.
class LRUCache(private val capacity: Int) {
private val map: scala.collection.mutable.Map[Int, Int] = scala.collection.mutable.Map()
private val order: scala.collection.mutable.ArrayBuffer[Int] = scala.collection.mutable.ArrayBuffer()
def get(key: Int): Int = {
if (!map.contains(key)) return -1
order -= key
order += key
return map(key)
}
def put(key: Int, value: Int): Unit = {
if (map.contains(key)) {
order -= key
} else if (map.size >= capacity) {
val evict = order(0)
order.remove(0)
map.remove(evict)
}
map(key) = value
order += key
}
}
Free plan: 30 tutor messages. Premium is unlimited.
