Skip to content

Commit 4b4d770

Browse files
committed
Add an example of the LRU Cache based on the Map.
1 parent 69c3a16 commit 4b4d770

File tree

4 files changed

+216
-3
lines changed

4 files changed

+216
-3
lines changed

‎src/data-structures/lru-cache/LRUCache.js‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class LinkedListNode{
2424
* Implementation of the LRU (Least Recently Used) Cache
2525
* based on the HashMap and Doubly Linked List data-structures.
2626
*
27-
* Current implementation allows to have fast (O(1)) read and write operations.
27+
* Current implementation allows to have fast O(1) (in average) read and write operations.
2828
*
2929
* At any moment in time the LRU Cache holds not more that "capacity" number of items in it.
3030
*/
@@ -43,7 +43,7 @@ class LRUCache{
4343

4444
/**
4545
* Returns the cached value by its key.
46-
* Time complexity: O(1).
46+
* Time complexity: O(1) in average.
4747
* @param{string} key
4848
* @returns{any}
4949
*/
@@ -56,7 +56,7 @@ class LRUCache{
5656

5757
/**
5858
* Sets the value to cache by its key.
59-
* Time complexity: O(1).
59+
* Time complexity: O(1) in average.
6060
* @param{string} key
6161
* @param{any} val
6262
*/
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/* eslint-disable no-restricted-syntax, no-unreachable-loop */
2+
3+
/**
4+
* Implementation of the LRU (Least Recently Used) Cache
5+
* based on the (ordered) Map data-structure.
6+
*
7+
* Current implementation allows to have fast O(1) (in average) read and write operations.
8+
*
9+
* At any moment in time the LRU Cache holds not more that "capacity" number of items in it.
10+
*/
11+
classLRUCacheOnMap{
12+
/**
13+
* Creates a cache instance of a specific capacity.
14+
* @param{number} capacity
15+
*/
16+
constructor(capacity){
17+
this.capacity=capacity;// How many items to store in cache at max.
18+
this.items=newMap();// The ordered hash map of all cached items.
19+
}
20+
21+
/**
22+
* Returns the cached value by its key.
23+
* Time complexity: O(1) in average.
24+
* @param{string} key
25+
* @returns{any}
26+
*/
27+
get(key){
28+
if(!this.items.has(key))returnundefined;
29+
constval=this.items.get(key);
30+
this.items.delete(key);
31+
this.items.set(key,val);
32+
returnval;
33+
}
34+
35+
/**
36+
* Sets the value to cache by its key.
37+
* Time complexity: O(1).
38+
* @param{string} key
39+
* @param{any} val
40+
*/
41+
set(key,val){
42+
this.items.delete(key);
43+
this.items.set(key,val);
44+
if(this.items.size>this.capacity){
45+
for(constheadKeyofthis.items.keys()){
46+
this.items.delete(headKey);
47+
break;
48+
}
49+
}
50+
}
51+
}
52+
53+
exportdefaultLRUCacheOnMap;

‎src/data-structures/lru-cache/README.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ The functions `get()` and `set()` must each run in `O(1)` average time complexit
1616

1717
## Implementation
1818

19+
### Version 1: Doubly Linked List + Hash Map
20+
1921
See the `LRUCache` implementation example in [LRUCache.js](./LRUCache.js). The solution uses a `HashMap` for fast `O(1)` (in average) cache items access, and a `DoublyLinkedList` for fast `O(1)` (in average) cache items promotions and eviction (to keep the maximum allowed cache capacity).
2022

2123
![Linked List](./images/lru-cache.jpg)
@@ -24,6 +26,16 @@ See the `LRUCache` implementation example in [LRUCache.js](./LRUCache.js). The s
2426

2527
You may also find more test-case examples of how the LRU Cache works in [LRUCache.test.js](./__test__/LRUCache.test.js) file.
2628

29+
### Version 2: Ordered Map
30+
31+
The first implementation that uses doubly linked list is good for learning purposes and for better understanding of how the average `O(1)` time complexity is achievable while doing `set()` and `get()`.
32+
33+
However, the simpler approach might be to use a JavaScript [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) object. The `Map` object holds key-value pairs and **remembers the original insertion order** of the keys. We can use this fact in order to keep the recently-used items in the "end" of the map by removing and re-adding items. The item at the beginning of the `Map` is the first one to be evicted if cache capacity overflows. The order of the items may checked by using the `IterableIterator` like `map.keys()`.
34+
35+
See the `LRUCacheOnMap` implementation example in [LRUCacheOnMap.js](./LRUCacheOnMap.js).
36+
37+
You may also find more test-case examples of how the LRU Cache works in [LRUCacheOnMap.test.js](./__test__/LRUCacheOnMap.test.js) file.
38+
2739
## Complexities
2840

2941
|| Average |
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
importLRUCachefrom'../LRUCacheOnMap';
2+
3+
describe('LRUCacheOnMap',()=>{
4+
it('should set and get values to and from the cache',()=>{
5+
constcache=newLRUCache(100);
6+
expect(cache.get('key-1')).toBeUndefined();
7+
8+
cache.set('key-1',15);
9+
cache.set('key-2',16);
10+
cache.set('key-3',17);
11+
expect(cache.get('key-1')).toBe(15);
12+
expect(cache.get('key-2')).toBe(16);
13+
expect(cache.get('key-3')).toBe(17);
14+
expect(cache.get('key-3')).toBe(17);
15+
expect(cache.get('key-2')).toBe(16);
16+
expect(cache.get('key-1')).toBe(15);
17+
18+
cache.set('key-1',5);
19+
cache.set('key-2',6);
20+
cache.set('key-3',7);
21+
expect(cache.get('key-1')).toBe(5);
22+
expect(cache.get('key-2')).toBe(6);
23+
expect(cache.get('key-3')).toBe(7);
24+
});
25+
26+
it('should evict least recently used items from cache with cache size of 1',()=>{
27+
constcache=newLRUCache(1);
28+
expect(cache.get('key-1')).toBeUndefined();
29+
30+
cache.set('key-1',15);
31+
expect(cache.get('key-1')).toBe(15);
32+
33+
cache.set('key-2',16);
34+
expect(cache.get('key-1')).toBeUndefined();
35+
expect(cache.get('key-2')).toBe(16);
36+
37+
cache.set('key-2',17);
38+
expect(cache.get('key-2')).toBe(17);
39+
40+
cache.set('key-3',18);
41+
cache.set('key-4',19);
42+
expect(cache.get('key-2')).toBeUndefined();
43+
expect(cache.get('key-3')).toBeUndefined();
44+
expect(cache.get('key-4')).toBe(19);
45+
});
46+
47+
it('should evict least recently used items from cache with cache size of 2',()=>{
48+
constcache=newLRUCache(2);
49+
expect(cache.get('key-21')).toBeUndefined();
50+
51+
cache.set('key-21',15);
52+
expect(cache.get('key-21')).toBe(15);
53+
54+
cache.set('key-22',16);
55+
expect(cache.get('key-21')).toBe(15);
56+
expect(cache.get('key-22')).toBe(16);
57+
58+
cache.set('key-22',17);
59+
expect(cache.get('key-22')).toBe(17);
60+
61+
cache.set('key-23',18);
62+
expect(cache.get('key-21')).toBeUndefined();
63+
expect(cache.get('key-22')).toBe(17);
64+
expect(cache.get('key-23')).toBe(18);
65+
66+
cache.set('key-24',19);
67+
expect(cache.get('key-21')).toBeUndefined();
68+
expect(cache.get('key-22')).toBeUndefined();
69+
expect(cache.get('key-23')).toBe(18);
70+
expect(cache.get('key-24')).toBe(19);
71+
});
72+
73+
it('should evict least recently used items from cache with cache size of 3',()=>{
74+
constcache=newLRUCache(3);
75+
76+
cache.set('key-1',1);
77+
cache.set('key-2',2);
78+
cache.set('key-3',3);
79+
expect(cache.get('key-1')).toBe(1);
80+
expect(cache.get('key-2')).toBe(2);
81+
expect(cache.get('key-3')).toBe(3);
82+
83+
cache.set('key-3',4);
84+
expect(cache.get('key-1')).toBe(1);
85+
expect(cache.get('key-2')).toBe(2);
86+
expect(cache.get('key-3')).toBe(4);
87+
88+
cache.set('key-4',5);
89+
expect(cache.get('key-1')).toBeUndefined();
90+
expect(cache.get('key-2')).toBe(2);
91+
expect(cache.get('key-3')).toBe(4);
92+
expect(cache.get('key-4')).toBe(5);
93+
});
94+
95+
it('should promote the node while calling set() method',()=>{
96+
constcache=newLRUCache(2);
97+
98+
cache.set('2',1);
99+
cache.set('1',1);
100+
cache.set('2',3);
101+
cache.set('4',1);
102+
expect(cache.get('1')).toBeUndefined();
103+
expect(cache.get('2')).toBe(3);
104+
});
105+
106+
it('should promote the recently accessed item with cache size of 3',()=>{
107+
constcache=newLRUCache(3);
108+
109+
cache.set('key-1',1);
110+
cache.set('key-2',2);
111+
cache.set('key-3',3);
112+
expect(cache.get('key-1')).toBe(1);
113+
114+
cache.set('key-4',4);
115+
expect(cache.get('key-1')).toBe(1);
116+
expect(cache.get('key-3')).toBe(3);
117+
expect(cache.get('key-4')).toBe(4);
118+
expect(cache.get('key-2')).toBeUndefined();
119+
});
120+
121+
it('should promote the recently accessed item with cache size of 4',()=>{
122+
constcache=newLRUCache(4);
123+
124+
cache.set('key-1',1);
125+
cache.set('key-2',2);
126+
cache.set('key-3',3);
127+
cache.set('key-4',4);
128+
expect(cache.get('key-4')).toBe(4);
129+
expect(cache.get('key-3')).toBe(3);
130+
expect(cache.get('key-2')).toBe(2);
131+
expect(cache.get('key-1')).toBe(1);
132+
133+
cache.set('key-5',5);
134+
expect(cache.get('key-1')).toBe(1);
135+
expect(cache.get('key-2')).toBe(2);
136+
expect(cache.get('key-3')).toBe(3);
137+
expect(cache.get('key-4')).toBeUndefined();
138+
expect(cache.get('key-5')).toBe(5);
139+
140+
cache.set('key-6',6);
141+
expect(cache.get('key-1')).toBeUndefined();
142+
expect(cache.get('key-2')).toBe(2);
143+
expect(cache.get('key-3')).toBe(3);
144+
expect(cache.get('key-4')).toBeUndefined();
145+
expect(cache.get('key-5')).toBe(5);
146+
expect(cache.get('key-6')).toBe(6);
147+
});
148+
});

0 commit comments

Comments
(0)