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 var capacity: Int, a dictionary map: [Int: Int] (key to value) and an array order: [Int] (the keys, least recently used first).
Create it: LRUCache(capacity) makes an empty cache that holds at most capacity entries.
Methods and rules:
func 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.func put(_ key: Int, _ value: Int)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 initializer
init(_ capacity: Int)saves the capacity; the fields start as[:]and[]. map[key] != niltells whether a key is stored;map[key] = valuestores or replaces a value;map.removeValue(forKey: key)deletes an entry;map.countcounts the entries.order.removeAll { $0 == key }removes an item by value,order.append(key)adds at the end, andorder[0]withorder.remove(at: 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 {
func mainFunction() {
let c = LRUCache(2)
c.put(1, 10)
c.put(2, 20)
print("Get 1: \(c.get(1))")
c.put(3, 30)
print("Get 2: \(c.get(2))")
print("Get 3: \(c.get(3))")
print("Miss 9: \(c.get(9))")
c.put(1, 11)
print("Update 1: \(c.get(1))")
c.put(4, 40)
print("Evicted 3: \(c.get(3))")
print("Kept 1: \(c.get(1))")
print("Kept 4: \(c.get(4))")
let s = LRUCache(1)
s.put(1, 100)
s.put(2, 200)
print("Small 1: \(s.get(1))")
print("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.removeAll { $0 == key } then order.append(key) moves a key to the end. order[0] is the least recently used key.
class LRUCache {
private var capacity: Int
private var map: [Int: Int] = [:]
private var order: [Int] = []
init(_ capacity: Int) {
self.capacity = capacity
}
func get(_ key: Int) -> Int {
if map[key] == nil {
return -1
}
order.removeAll { $0 == key }
order.append(key)
return map[key]!
}
func put(_ key: Int, _ value: Int) {
if map[key] != nil {
order.removeAll { $0 == key }
} else if map.count >= capacity {
let evict = order[0]
order.remove(at: 0)
map.removeValue(forKey: evict)
}
map[key] = value
order.append(key)
}
}
Free plan: 30 tutor messages. Premium is unlimited.
