Skip to content

Commit f9f9630

Browse files
authored
Add LRU Cache Challange (#138)
1 parent f67f38d commit f9f9630

File tree

8 files changed

+258
-3
lines changed

8 files changed

+258
-3
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ multiple times and be persistent over time.
131131
- [Min sub list length](src/test/kotlin/com/igorwojda/list/minsublistlength)
132132
- [Subtract](src/test/kotlin/com/igorwojda/list/subtract)
133133
- [Coins](src/test/kotlin/com/igorwojda/list/coins)
134-
- [Medan Of Sorted Lists](src/test/kotlin/com/igorwojda/list/medianoftwosorted/README.md)
134+
- [Medan Of Sorted Lists](src/test/kotlin/com/igorwojda/list/medianoftwosorted)
135+
- [LRU Cache](src/test/kotlin/com/igorwojda/cache/lru)
135136

136137
# Useful links
137138

misc/ChallengeGroups.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,7 @@ We use sliding window instead of nested loops which decreases complexity from `O
180180
- [Binary search tree](../src/test/kotlin/com/igorwojda/tree/binarysearchtree)
181181
- [Tree level width](../src/test/kotlin/com/igorwojda/tree/multiway/levelwidth)
182182
- [Tree traversal](../src/test/kotlin/com/igorwojda/tree/multiway/traversal)
183+
184+
## Cache
185+
186+
- [LRU Cache](../src/test/kotlin/com/igorwojda/cache/lru)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.igorwojda.cache.lru
2+
3+
import org.amshove.kluent.shouldBeEqualTo
4+
import org.junit.jupiter.api.Test
5+
6+
class LRUCache(private val capacity: Int) {
7+
val size: Int get() = TODO("Add your solution here")
8+
9+
fun get(key: Int): String? {
10+
TODO("Add your solution here")
11+
}
12+
13+
fun put(key: Int, value: String) {
14+
TODO("Add your solution here")
15+
}
16+
}
17+
18+
private class Test {
19+
@Test
20+
fun `lru cache is empty after creation`() {
21+
val lruCache = LRUCache(3)
22+
23+
lruCache.size shouldBeEqualTo 0
24+
}
25+
26+
@Test
27+
fun `oldest value is not removed from cache after capacity is exceeded`() {
28+
val lruCache = LRUCache(2)
29+
30+
lruCache.put(1, "Person1")
31+
lruCache.put(2, "Person2")
32+
lruCache.put(3, "Person3")
33+
34+
lruCache.size shouldBeEqualTo 2
35+
lruCache.get(1) shouldBeEqualTo null
36+
lruCache.get(2) shouldBeEqualTo "Person2"
37+
lruCache.get(3) shouldBeEqualTo "Person3"
38+
}
39+
40+
@Test
41+
fun `retrieved element becomes most recently used`() {
42+
val lruCache = LRUCache(2)
43+
lruCache.put(1, "Person1")
44+
lruCache.put(2, "Person2")
45+
lruCache.get(1)
46+
lruCache.put(3, "Person3")
47+
48+
lruCache.get(1) shouldBeEqualTo "Person1"
49+
lruCache.get(2) shouldBeEqualTo null
50+
lruCache.get(3) shouldBeEqualTo "Person3"
51+
}
52+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# LRU Cache
2+
3+
## Instructions
4+
5+
Design a data structure that follows the constraints of a
6+
[Least Recently Used (LRU) cache](https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU).
7+
8+
Implement the `LRUCache` class:
9+
10+
`LRUCache` (int capacity) Initialize the LRU cache with positive size `capacity` and two methods:
11+
- `get(key: Int)` - return the value of the key if the key exists, otherwise return `null`.
12+
- `put(key: Int, value: String)` - update the value of the key if the key exists, otherwise, add the key-value pair
13+
to the cache. If the number of keys exceeds the capacity from this operation, evict the least recently used key.
14+
- `size` - currently used cache.
15+
16+
The overall run time complexity of each methods should be `O(1)`.
17+
18+
[Challenge](Challenge.kt) | [Solution](Solution.kt)
19+
20+
## Examples
21+
22+
```kotlin
23+
val lruCache = LRUCache(3)
24+
lruCache.size shouldBeEqualTo 0
25+
```
26+
27+
```kotlin
28+
val lruCache = LRUCache(2)
29+
lruCache.put(1, 10)
30+
lruCache.put(2, 20)
31+
lruCache.put(3, 30)
32+
33+
lruCache.size shouldBeEqualTo 2
34+
lruCache.get(1) shouldBeEqualTo null
35+
lruCache.get(2) shouldBeEqualTo 20
36+
lruCache.get(3) shouldBeEqualTo 30
37+
```
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package com.igorwojda.cache.lru
2+
3+
// Implementation is using combination of HashMap and LinkedList.
4+
// Time Complexity: O(1)
5+
private object Solution1 {
6+
class LRUCache(private val capacity: Int) {
7+
private val map = HashMap<Int, Node>()
8+
9+
private var head: Node? = null
10+
private var tail: Node? = null
11+
12+
val size get() = map.size
13+
14+
fun put(key: Int, value: String) {
15+
// 1. Check if node exicts
16+
val existingNode = map[key]
17+
18+
if (existingNode == null) {
19+
// 2. Check Map capacity
20+
if (map.size >= capacity) {
21+
val removedNode = removeHead()
22+
map.remove(removedNode?.key)
23+
}
24+
25+
// 3. Add a new node
26+
val newNode = Node(key, value)
27+
28+
map[key] = newNode
29+
addTail(newNode)
30+
} else {
31+
existingNode.value = value
32+
moveToTail(existingNode)
33+
}
34+
}
35+
36+
private fun addTail(node: Node) {
37+
// 1. If list is empty
38+
if (head == null) {
39+
head = node
40+
} else {
41+
node.prev = tail
42+
tail?.next = node
43+
}
44+
45+
tail = node
46+
}
47+
48+
private fun removeHead(): Node? {
49+
// 1. Head exists
50+
if (head != null) {
51+
// 2. Store current head to return
52+
val node = head
53+
54+
// 3. Remove head
55+
head = head?.next
56+
head?.prev = null
57+
58+
// 4. Remove tail if head is tail
59+
if (node == tail) tail = null
60+
61+
return node
62+
}
63+
64+
return null
65+
}
66+
67+
fun get(key: Int): String? {
68+
// 1. get the node
69+
val node = map[key]
70+
71+
// 2. Move to tail if exists
72+
if (node != null) {
73+
moveToTail(node)
74+
}
75+
76+
// 3. Return value
77+
return node?.value
78+
}
79+
80+
private fun moveToTail(node: Node) {
81+
// 1. Check if node is tail
82+
if (node != tail) {
83+
// 2. Remove node from list
84+
if (node == head) {
85+
head = node.next
86+
} else {
87+
node.prev?.next = node.next
88+
node.next?.prev = node.prev
89+
}
90+
91+
// 3. Add node to tail
92+
addTail(node)
93+
}
94+
}
95+
96+
private data class Node(
97+
val key: Int,
98+
var value: String,
99+
var prev: Node? = null,
100+
var next: Node? = null,
101+
)
102+
}
103+
}
104+
105+
// Implementation using LinkedHashMap
106+
// Time Complexity: O(1)
107+
private object Solution2 {
108+
class LRUCache(private val capacity: Int) {
109+
val size get() = linkedHashMap.size
110+
111+
private val linkedHashMap = object :
112+
LinkedHashMap<Int, String>(capacity, 0.75f, true) {
113+
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<Int, String>?): Boolean {
114+
return size > capacity
115+
}
116+
}
117+
118+
fun put(key: Int, value: String) {
119+
linkedHashMap[key] = value
120+
}
121+
122+
fun get(key: Int): String? {
123+
return linkedHashMap[key]?.also {
124+
linkedHashMap.remove(key)
125+
linkedHashMap[key] = it
126+
}
127+
}
128+
}
129+
}
130+
131+
// Implementation using List
132+
// Time Complexity: O(n)
133+
private object Solution3 {
134+
class LRUCache(private val capacity: Int) {
135+
private val list = mutableListOf<Pair<Int, String>>()
136+
137+
val size get() = list.size
138+
139+
fun get(key: Int): String? {
140+
val value = list.firstOrNull { it.first == key }?.second
141+
142+
if (value != null) {
143+
val pair = Pair(key, value)
144+
list.remove(pair)
145+
list.add(pair)
146+
}
147+
148+
return list.firstOrNull { it.first == key }?.second
149+
}
150+
151+
fun put(key: Int, value: String) {
152+
list.removeIf { it.first == key }
153+
list.add(Pair(key, value))
154+
155+
if (list.size > capacity) {
156+
list.removeFirst()
157+
}
158+
}
159+
}
160+
}

src/test/kotlin/com/igorwojda/integer/addupto/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Add up to
22

33
## Instructions
4+
45
Given positive integer `n` implement a function which calculates sum of all numbers from `1` up to (and including)
56
number `n`.
67

src/test/kotlin/com/igorwojda/linkedlist/doubly/base/Challenge.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.igorwojda.linkedlist.doubly.base
22

33
private class DoublyLinkedList<E> {
4-
// implement
4+
// Add your solution here
55
}
66

77
private data class Node<T>(

src/test/kotlin/com/igorwojda/linkedlist/singly/base/Challenge.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.igorwojda.linkedlist.singly.base
22

33
private class SinglyLinkedList<E> {
4-
// implement
4+
// Add your solution here
55
}
66

77
private data class Node<T>(

0 commit comments

Comments
 (0)