diff --git a/Cache/LRUCache.js b/Cache/LRUCache.js index e416e03776..d524bcbaec 100644 --- a/Cache/LRUCache.js +++ b/Cache/LRUCache.js @@ -1,40 +1,146 @@ class LRUCache { // LRU Cache to store a given capacity of data + #capacity + + /** + * @param {number} capacity - the capacity of LRUCache + * @returns {LRUCache} - sealed + */ constructor (capacity) { - this.cache = new Map() - this.capacity = capacity + if (!Number.isInteger(capacity) || capacity < 0) { + throw new TypeError('Invalid capacity') + } + + this.#capacity = ~~capacity + this.misses = 0 this.hits = 0 - this.miss = 0 + this.cache = new Map() + + return Object.seal(this) + } + + get info () { + return Object.freeze({ + misses: this.misses, + hits: this.hits, + capacity: this.capacity, + size: this.size + }) + } + + get size () { + return this.cache.size } - cacheInfo () { - // Return the details for the cache instance [hits, misses, capacity, current_size] - return `CacheInfo(hits=${this.hits}, misses=${this.miss}, capacity=${this.capacity}, current size=${this.cache.size})` + get capacity () { + return this.#capacity } + set capacity (newCapacity) { + if (newCapacity < 0) { + throw new RangeError('Capacity should be greater than 0') + } + + if (newCapacity < this.capacity) { + let diff = this.capacity - newCapacity + + while (diff--) { + this.#removeLeastRecentlyUsed() + } + } + + this.#capacity = newCapacity + } + + /** + * delete oldest key existing in map by the help of iterator + */ + #removeLeastRecentlyUsed () { + this.cache.delete(this.cache.keys().next().value) + } + + /** + * @param {string} key + * @returns {*} + */ + has (key) { + key = String(key) + + return this.cache.has(key) + } + + /** + * @param {string} key + * @param {*} value + */ set (key, value) { + key = String(key) // Sets the value for the input key and if the key exists it updates the existing key - if (this.cache.size === this.capacity) { - // delete oldest key existing in map - this.cache.delete(this.cache.keys().next().value) + if (this.size === this.capacity) { + this.#removeLeastRecentlyUsed() } + this.cache.set(key, value) } + /** + * @param {string} key + * @returns {*} + */ get (key) { + key = String(key) // Returns the value for the input key. Returns null if key is not present in cache if (this.cache.has(key)) { const value = this.cache.get(key) + // refresh the cache to update the order of key this.cache.delete(key) this.cache.set(key, value) - this.hits += 1 + + this.hits++ return value - } else { - this.miss += 1 - return null } + + this.misses++ + return null + } + + /** + * @param {JSON} json + * @returns {LRUCache} + */ + parse (json) { + const { misses, hits, cache } = JSON.parse(json) + + this.misses += misses ?? 0 + this.hits += hits ?? 0 + + for (const key in cache) { + this.set(key, cache[key]) + } + + return this + } + + /** + * @param {number} indent + * @returns {JSON} - string + */ + toString (indent) { + const replacer = (_, value) => { + if (value instanceof Set) { + return [...value] + } + + if (value instanceof Map) { + return Object.fromEntries(value) + } + + return value + } + + return JSON.stringify(this, replacer, indent) } } -export { LRUCache } +export default LRUCache diff --git a/Cache/test/LRUCache.test.js b/Cache/test/LRUCache.test.js index b03d609d66..c9acc3dadf 100644 --- a/Cache/test/LRUCache.test.js +++ b/Cache/test/LRUCache.test.js @@ -1,9 +1,19 @@ -import { LRUCache } from '../LRUCache' +import LRUCache from '../LRUCache' import { fibonacciCache } from './cacheTest' -describe('LRUCache', () => { - it('Example 1 (Small Cache, size=2)', () => { - const cache = new LRUCache(2) +describe('Testing LRUCache', () => { + it('Testing with invalid capacity', () => { + expect(() => new LRUCache()).toThrow() + expect(() => new LRUCache('Invalid')).toThrow() + expect(() => new LRUCache(-1)).toThrow() + expect(() => new LRUCache(Infinity)).toThrow() + }) + + it('Example 1 (Small Cache, size = 2)', () => { + const cache = new LRUCache(1) // initially capacity + + cache.capacity++ // now the capacity is increasing by one + cache.set(1, 1) cache.set(2, 2) @@ -24,14 +34,41 @@ describe('LRUCache', () => { expect(cache.get(3)).toBe(3) expect(cache.get(4)).toBe(4) - expect(cache.cacheInfo()).toBe('CacheInfo(hits=6, misses=3, capacity=2, current size=2)') + expect(cache.info).toEqual({ + misses: 3, + hits: 6, + capacity: 2, + size: 2 + }) + + const json = '{"misses":3,"hits":6,"cache":{"3":3,"4":4}}' + expect(cache.toString()).toBe(json) + + // merge with json + cache.parse(json) + + cache.capacity-- // now the capacity decreasing by one + + expect(cache.info).toEqual({ + misses: 6, + hits: 12, + capacity: 1, + size: 1 + }) }) - it('Example 2 (Computing Fibonacci Series, size=100)', () => { + it('Example 2 (Computing Fibonacci Series, size = 100)', () => { const cache = new LRUCache(100) + for (let i = 1; i <= 100; i++) { fibonacciCache(i, cache) } - expect(cache.cacheInfo()).toBe('CacheInfo(hits=193, misses=103, capacity=100, current size=98)') + + expect(cache.info).toEqual({ + misses: 103, + hits: 193, + capacity: 100, + size: 98 + }) }) })