Skip to content

Commit ad68c63

Browse files
authored
merge: Added new clean LFUCache class (#939)
* feat: added new clean LFUCache class * fixed: resolved spell mistake & added test casses
1 parent 9d2a7f1 commit ad68c63

File tree

2 files changed

+290
-106
lines changed

2 files changed

+290
-106
lines changed

Cache/LFUCache.js

Lines changed: 233 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,254 @@
1-
class DoubleLinkedListNode {
2-
// Double Linked List Node built specifically for LFU Cache
3-
constructor (key, val) {
1+
class CacheNode {
2+
constructor (key, value, frequency) {
43
this.key = key
5-
this.val = val
6-
this.freq = 0
7-
this.next = null
8-
this.prev = null
4+
this.value = value
5+
this.frequency = frequency
6+
7+
return Object.seal(this)
98
}
109
}
1110

12-
class DoubleLinkedList {
13-
// Double Linked List built specifically for LFU Cache
14-
constructor () {
15-
this.head = new DoubleLinkedListNode(null, null)
16-
this.rear = new DoubleLinkedListNode(null, null)
17-
this.head.next = this.rear
18-
this.rear.prev = this.head
19-
}
11+
// This frequency map class will act like javascript Map DS with more two custom method refresh & insert
12+
class FrequencyMap extends Map {
13+
static get [Symbol.species] () { return Map } // for using Symbol.species we can access Map constructor @see -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/@@species
14+
get [Symbol.toStringTag] () { return '' }
2015

21-
_positionNode (node) {
22-
// Helper function to position a node based on the frequency of the key
23-
while (node.prev.key && node.prev.freq > node.freq) {
24-
const node1 = node
25-
const node2 = node.prev
26-
node1.prev = node2.prev
27-
node2.next = node1.prev
28-
node1.next = node2
29-
node2.prev = node1
30-
}
31-
}
16+
/**
17+
* @method refresh
18+
* @description - It's revive a CacheNode, increment of this nodes frequency and refresh the frequencyMap via new incremented nodes frequency
19+
* @param {CacheNode} node
20+
*/
21+
refresh (node) {
22+
const { frequency } = node
23+
const freqSet = this.get(frequency)
24+
freqSet.delete(node)
25+
26+
node.frequency++
3227

33-
add (node) {
34-
// Adds the given node to the end of the list (before rear) and positions it based on frequency
35-
const temp = this.rear.prev
36-
temp.next = node
37-
node.prev = temp
38-
this.rear.prev = node
39-
node.next = this.rear
40-
this._positionNode(node)
28+
this.insert(node)
4129
}
4230

43-
remove (node) {
44-
// Removes and returns the given node from the list
45-
const tempLast = node.prev
46-
const tempNext = node.next
47-
node.prev = null
48-
node.next = null
49-
tempLast.next = tempNext
50-
tempNext.prev = tempLast
31+
/**
32+
* @method insert
33+
* @description - Add new CacheNode into HashSet by the frequency
34+
* @param {CacheNode} node
35+
*/
36+
insert (node) {
37+
const { frequency } = node
5138

52-
return node
39+
if (!this.has(frequency)) {
40+
this.set(frequency, new Set())
41+
}
42+
43+
this.get(frequency).add(node)
5344
}
5445
}
5546

5647
class LFUCache {
57-
// LFU Cache to store a given capacity of data
58-
// The Double Linked List is used to store the order of deletion from the cache
59-
// The rear.prev holds the most frequently used key and the head.next holds the least used key
60-
// When the number of elements reaches the capacity, the least frequently used item is removed before adding the next key
61-
constructor (capacity) {
62-
this.list = new DoubleLinkedList()
63-
this.capacity = capacity
64-
this.numKeys = 0
65-
this.hits = 0
66-
this.miss = 0
67-
this.cache = {}
68-
}
48+
#capacity
49+
#frequencyMap
6950

70-
cacheInfo () {
71-
// Return the details for the cache instance [hits, misses, capacity, current_size]
72-
return `CacheInfo(hits=${this.hits}, misses=${this.miss}, capacity=${this.capacity}, current size=${this.numKeys})`
73-
}
51+
/**
52+
* @param {number} capacity - The range of LFUCache
53+
* @returns {LFUCache} - sealed
54+
*/
55+
constructor (capacity) {
56+
this.#capacity = capacity
57+
this.#frequencyMap = new FrequencyMap()
58+
this.misses = 0
59+
this.hits = 0
60+
this.cache = new Map()
61+
62+
return Object.seal(this)
63+
}
64+
65+
/**
66+
* Get the capacity of the LFUCache
67+
* @returns {number}
68+
*/
69+
get capacity () {
70+
return this.#capacity
71+
}
72+
73+
/**
74+
* Get the current size of LFUCache
75+
* @returns {number}
76+
*/
77+
get size () {
78+
return this.cache.size
79+
}
80+
81+
/**
82+
* Set the capacity of the LFUCache if you decrease the capacity its removed CacheNodes following the LFU - least frequency used
83+
*/
84+
set capacity (newCapacity) {
85+
if (this.#capacity > newCapacity) {
86+
let diff = this.#capacity - newCapacity // get the decrement number of capacity
87+
88+
while (diff--) {
89+
this.#removeCacheNode()
90+
}
7491

75-
set (key, value) {
76-
// Sets the value for the input key and updates the Double Linked List
77-
if (!(key in this.cache)) {
78-
if (this.numKeys >= this.capacity) {
79-
const keyToDelete = this.list.head.next.key
80-
this.list.remove(this.cache[keyToDelete])
81-
delete this.cache[keyToDelete]
82-
this.numKeys -= 1
92+
this.cache.size === 0 && this.#frequencyMap.clear()
8393
}
84-
this.cache[key] = new DoubleLinkedListNode(key, value)
85-
this.list.add(this.cache[key])
86-
this.numKeys += 1
87-
} else {
88-
const node = this.list.remove(this.cache[key])
89-
node.val = value
90-
this.list.add(node)
94+
95+
this.#capacity = newCapacity
9196
}
92-
}
9397

94-
get (key) {
95-
// Returns the value for the input key and updates the Double Linked List. Returns null if key is not present in cache
96-
if (key in this.cache) {
97-
this.hits += 1
98-
this.list.add(this.list.remove(this.cache[key]))
99-
return this.cache[key].val
98+
get info () {
99+
return Object.freeze({
100+
misses: this.misses,
101+
hits: this.hits,
102+
capacity: this.capacity,
103+
currentSize: this.size,
104+
leastFrequency: this.leastFrequency
105+
})
106+
}
107+
108+
get leastFrequency () {
109+
const freqCacheIterator = this.#frequencyMap.keys()
110+
let leastFrequency = freqCacheIterator.next().value || null
111+
112+
// select the non-empty frequency Set
113+
while (this.#frequencyMap.get(leastFrequency)?.size === 0) {
114+
leastFrequency = freqCacheIterator.next().value
115+
}
116+
117+
return leastFrequency
118+
}
119+
120+
#removeCacheNode () {
121+
const leastFreqSet = this.#frequencyMap.get(this.leastFrequency)
122+
// Select the least recently used node from the least Frequency set
123+
const LFUNode = leastFreqSet.values().next().value
124+
125+
leastFreqSet.delete(LFUNode)
126+
this.cache.delete(LFUNode.key)
127+
}
128+
129+
/**
130+
* if key exist then return true otherwise false
131+
* @param {any} key
132+
* @returns {boolean}
133+
*/
134+
has (key) {
135+
key = String(key) // converted to string
136+
137+
return this.cache.has(key)
138+
}
139+
140+
/**
141+
* @method get
142+
* @description - This method return the value of key & refresh the frequencyMap by the oldNode
143+
* @param {string} key
144+
* @returns {any}
145+
*/
146+
get (key) {
147+
key = String(key) // converted to string
148+
149+
if (this.cache.has(key)) {
150+
const oldNode = this.cache.get(key)
151+
this.#frequencyMap.refresh(oldNode)
152+
153+
this.hits++
154+
155+
return oldNode.value
156+
}
157+
158+
this.misses++
159+
return null
160+
}
161+
162+
/**
163+
* @method set
164+
* @description - This method stored the value by key & add frequency if it doesn't exist
165+
* @param {string} key
166+
* @param {any} value
167+
* @param {number} frequency
168+
* @returns {LFUCache}
169+
*/
170+
set (key, value, frequency = 1) {
171+
key = String(key) // converted to string
172+
173+
if (this.#capacity === 0) {
174+
throw new RangeError('LFUCache ERROR: The Capacity is 0')
175+
}
176+
177+
if (this.cache.has(key)) {
178+
const node = this.cache.get(key)
179+
node.value = value
180+
181+
this.#frequencyMap.refresh(node)
182+
183+
return this
184+
}
185+
186+
// if the cache size is full, then it's delete the Least Frequency Used node
187+
if (this.#capacity === this.cache.size) {
188+
this.#removeCacheNode()
189+
}
190+
191+
const newNode = new CacheNode(key, value, frequency)
192+
193+
this.cache.set(key, newNode)
194+
this.#frequencyMap.insert(newNode)
195+
196+
return this
197+
}
198+
199+
/**
200+
* @method parse
201+
* @description - This method receive a valid LFUCache JSON & run JSON.prase() method and merge with existing LFUCache
202+
* @param {JSON} json
203+
* @returns {LFUCache} - merged
204+
*/
205+
parse (json) {
206+
const { misses, hits, cache } = JSON.parse(json)
207+
208+
this.misses += misses ?? 0
209+
this.hits += hits ?? 0
210+
211+
for (const key in cache) {
212+
const { value, frequency } = cache[key]
213+
this.set(key, value, frequency)
214+
}
215+
216+
return this
217+
}
218+
219+
/**
220+
* @method clear
221+
* @description - This method cleared the whole LFUCache
222+
* @returns {LFUCache}
223+
*/
224+
clear () {
225+
this.cache.clear()
226+
this.#frequencyMap.clear()
227+
228+
return this
229+
}
230+
231+
/**
232+
* @method toString
233+
* @description - This method generate a JSON format of LFUCache & return it.
234+
* @param {number} indent
235+
* @returns {string} - JSON
236+
*/
237+
toString (indent) {
238+
const replacer = (_, value) => {
239+
if (value instanceof Set) {
240+
return [...value]
241+
}
242+
243+
if (value instanceof Map) {
244+
return Object.fromEntries(value)
245+
}
246+
247+
return value
248+
}
249+
250+
return JSON.stringify(this, replacer, indent)
100251
}
101-
this.miss += 1
102-
return null
103-
}
104252
}
105253

106-
export { LFUCache }
254+
export default LFUCache

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy