Skip to content

Commit 81c771b

Browse files
authored
merge: Improved LRUCache (#953)
* feat: added new validation test casses & methods * style: formated with standard * feat: added parse method & test cases * docs: added js docs * chore: added default import export
1 parent 55da7a1 commit 81c771b

File tree

2 files changed

+164
-21
lines changed

2 files changed

+164
-21
lines changed

Cache/LRUCache.js

+120-14
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,146 @@
11
class LRUCache {
22
// LRU Cache to store a given capacity of data
3+
#capacity
4+
5+
/**
6+
* @param {number} capacity - the capacity of LRUCache
7+
* @returns {LRUCache} - sealed
8+
*/
39
constructor (capacity) {
4-
this.cache = new Map()
5-
this.capacity = capacity
10+
if (!Number.isInteger(capacity) || capacity < 0) {
11+
throw new TypeError('Invalid capacity')
12+
}
13+
14+
this.#capacity = ~~capacity
15+
this.misses = 0
616
this.hits = 0
7-
this.miss = 0
17+
this.cache = new Map()
18+
19+
return Object.seal(this)
20+
}
21+
22+
get info () {
23+
return Object.freeze({
24+
misses: this.misses,
25+
hits: this.hits,
26+
capacity: this.capacity,
27+
size: this.size
28+
})
29+
}
30+
31+
get size () {
32+
return this.cache.size
833
}
934

10-
cacheInfo () {
11-
// Return the details for the cache instance [hits, misses, capacity, current_size]
12-
return `CacheInfo(hits=${this.hits}, misses=${this.miss}, capacity=${this.capacity}, current size=${this.cache.size})`
35+
get capacity () {
36+
return this.#capacity
1337
}
1438

39+
set capacity (newCapacity) {
40+
if (newCapacity < 0) {
41+
throw new RangeError('Capacity should be greater than 0')
42+
}
43+
44+
if (newCapacity < this.capacity) {
45+
let diff = this.capacity - newCapacity
46+
47+
while (diff--) {
48+
this.#removeLeastRecentlyUsed()
49+
}
50+
}
51+
52+
this.#capacity = newCapacity
53+
}
54+
55+
/**
56+
* delete oldest key existing in map by the help of iterator
57+
*/
58+
#removeLeastRecentlyUsed () {
59+
this.cache.delete(this.cache.keys().next().value)
60+
}
61+
62+
/**
63+
* @param {string} key
64+
* @returns {*}
65+
*/
66+
has (key) {
67+
key = String(key)
68+
69+
return this.cache.has(key)
70+
}
71+
72+
/**
73+
* @param {string} key
74+
* @param {*} value
75+
*/
1576
set (key, value) {
77+
key = String(key)
1678
// Sets the value for the input key and if the key exists it updates the existing key
17-
if (this.cache.size === this.capacity) {
18-
// delete oldest key existing in map
19-
this.cache.delete(this.cache.keys().next().value)
79+
if (this.size === this.capacity) {
80+
this.#removeLeastRecentlyUsed()
2081
}
82+
2183
this.cache.set(key, value)
2284
}
2385

86+
/**
87+
* @param {string} key
88+
* @returns {*}
89+
*/
2490
get (key) {
91+
key = String(key)
2592
// Returns the value for the input key. Returns null if key is not present in cache
2693
if (this.cache.has(key)) {
2794
const value = this.cache.get(key)
95+
2896
// refresh the cache to update the order of key
2997
this.cache.delete(key)
3098
this.cache.set(key, value)
31-
this.hits += 1
99+
100+
this.hits++
32101
return value
33-
} else {
34-
this.miss += 1
35-
return null
36102
}
103+
104+
this.misses++
105+
return null
106+
}
107+
108+
/**
109+
* @param {JSON} json
110+
* @returns {LRUCache}
111+
*/
112+
parse (json) {
113+
const { misses, hits, cache } = JSON.parse(json)
114+
115+
this.misses += misses ?? 0
116+
this.hits += hits ?? 0
117+
118+
for (const key in cache) {
119+
this.set(key, cache[key])
120+
}
121+
122+
return this
123+
}
124+
125+
/**
126+
* @param {number} indent
127+
* @returns {JSON} - string
128+
*/
129+
toString (indent) {
130+
const replacer = (_, value) => {
131+
if (value instanceof Set) {
132+
return [...value]
133+
}
134+
135+
if (value instanceof Map) {
136+
return Object.fromEntries(value)
137+
}
138+
139+
return value
140+
}
141+
142+
return JSON.stringify(this, replacer, indent)
37143
}
38144
}
39145

40-
export { LRUCache }
146+
export default LRUCache

Cache/test/LRUCache.test.js

+44-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
1-
import { LRUCache } from '../LRUCache'
1+
import LRUCache from '../LRUCache'
22
import { fibonacciCache } from './cacheTest'
33

4-
describe('LRUCache', () => {
5-
it('Example 1 (Small Cache, size=2)', () => {
6-
const cache = new LRUCache(2)
4+
describe('Testing LRUCache', () => {
5+
it('Testing with invalid capacity', () => {
6+
expect(() => new LRUCache()).toThrow()
7+
expect(() => new LRUCache('Invalid')).toThrow()
8+
expect(() => new LRUCache(-1)).toThrow()
9+
expect(() => new LRUCache(Infinity)).toThrow()
10+
})
11+
12+
it('Example 1 (Small Cache, size = 2)', () => {
13+
const cache = new LRUCache(1) // initially capacity
14+
15+
cache.capacity++ // now the capacity is increasing by one
16+
717
cache.set(1, 1)
818
cache.set(2, 2)
919

@@ -24,14 +34,41 @@ describe('LRUCache', () => {
2434
expect(cache.get(3)).toBe(3)
2535
expect(cache.get(4)).toBe(4)
2636

27-
expect(cache.cacheInfo()).toBe('CacheInfo(hits=6, misses=3, capacity=2, current size=2)')
37+
expect(cache.info).toEqual({
38+
misses: 3,
39+
hits: 6,
40+
capacity: 2,
41+
size: 2
42+
})
43+
44+
const json = '{"misses":3,"hits":6,"cache":{"3":3,"4":4}}'
45+
expect(cache.toString()).toBe(json)
46+
47+
// merge with json
48+
cache.parse(json)
49+
50+
cache.capacity-- // now the capacity decreasing by one
51+
52+
expect(cache.info).toEqual({
53+
misses: 6,
54+
hits: 12,
55+
capacity: 1,
56+
size: 1
57+
})
2858
})
2959

30-
it('Example 2 (Computing Fibonacci Series, size=100)', () => {
60+
it('Example 2 (Computing Fibonacci Series, size = 100)', () => {
3161
const cache = new LRUCache(100)
62+
3263
for (let i = 1; i <= 100; i++) {
3364
fibonacciCache(i, cache)
3465
}
35-
expect(cache.cacheInfo()).toBe('CacheInfo(hits=193, misses=103, capacity=100, current size=98)')
66+
67+
expect(cache.info).toEqual({
68+
misses: 103,
69+
hits: 193,
70+
capacity: 100,
71+
size: 98
72+
})
3673
})
3774
})

0 commit comments

Comments
 (0)