diff --git a/src/decorators/lru-memoize/lru-memoize.ts b/src/decorators/lru-memoize/lru-memoize.ts new file mode 100644 index 0000000..a7ad5ac --- /dev/null +++ b/src/decorators/lru-memoize/lru-memoize.ts @@ -0,0 +1,19 @@ +import { generateFunctionDecorator, isUndefined } from '../../helpers/general'; + +export function LruMemoize(maxStorage: number, hashFn?: (...args: any[]) => string) { + return generateFunctionDecorator('LruMemoize', decorator); +} + +function decorator(fn: (...args: any[]) => any, maxStorage: number, hashFn?: (...args: any[]) => string) { + const cache: { [key: string]: any } = {}; + + return function(...args: any[]) { + const hash = hashFn ? hashFn.apply(this, args) : JSON.stringify(args); + + if (!isUndefined(cache[hash])) { + return cache[hash]; + } + + return (cache[hash] = fn.apply(this, args)); + }; +} diff --git a/src/decorators/memoize/memoize.spec.ts b/src/decorators/memoize/memoize.spec.ts index e439a62..a20fa58 100644 --- a/src/decorators/memoize/memoize.spec.ts +++ b/src/decorators/memoize/memoize.spec.ts @@ -21,7 +21,7 @@ class TestMemoize { } } -describe('Memoize', () => { +describe('Lru', () => { it('should memoize values', () => { const test = new TestMemoize(); expect(test.count()).toEqual(1); diff --git a/src/decorators/memoize/memoize.ts b/src/decorators/memoize/memoize.ts index 606956c..7b75da0 100644 --- a/src/decorators/memoize/memoize.ts +++ b/src/decorators/memoize/memoize.ts @@ -1,4 +1,4 @@ -import { generateFunctionDecorator, isUndefined } from '../../helpers'; +import { generateFunctionDecorator, isUndefined } from '../../helpers/general'; export function Memoize(hashFn?: (...args: any[]) => string) { return generateFunctionDecorator('Memoize', decorator, hashFn); diff --git a/src/helpers.spec.ts b/src/helpers/general.spec.ts similarity index 89% rename from src/helpers.spec.ts rename to src/helpers/general.spec.ts index 2cf31d1..f88fbad 100644 --- a/src/helpers.spec.ts +++ b/src/helpers/general.spec.ts @@ -1,4 +1,4 @@ -import { isObject, isUndefined } from './helpers'; +import { isObject, isUndefined } from './general'; describe('Helpers', () => { it('should check isUndefined', () => { diff --git a/src/helpers.ts b/src/helpers/general.ts similarity index 100% rename from src/helpers.ts rename to src/helpers/general.ts diff --git a/src/helpers/lru.spec.ts b/src/helpers/lru.spec.ts new file mode 100644 index 0000000..8a677b0 --- /dev/null +++ b/src/helpers/lru.spec.ts @@ -0,0 +1,36 @@ +import { LRU } from './lru'; + +describe('LRU', () => { + it('should check isUndefined', () => { + const lru = new LRU(3); + + lru.insert('foo', 1); + lru.insert('bar', 2); + lru.insert('baz', 3); + + expect(lru.toString()).toEqual('3, 2, 1'); + + lru.insert('foo', 1.5); + + expect(lru.toString()).toEqual('1.5, 3, 2'); + + lru.insert('foo', 1); + + expect(lru.toString()).toEqual('1, 3, 2'); + + lru.insert('foo', 1); + lru.insert('foo', 1); + lru.insert('foo', 1); + + expect(lru.toString()).toEqual('1, 3, 2'); + + lru.insert('bar', 2.5); + lru.insert('bar', 2.5); + + expect(lru.toString()).toEqual('2.5, 1, 3'); + + lru.insert('foobar', 4); + + expect(lru.toString()).toEqual('4, 2.5, 1'); + }); +}); diff --git a/src/helpers/lru.ts b/src/helpers/lru.ts new file mode 100644 index 0000000..705eb44 --- /dev/null +++ b/src/helpers/lru.ts @@ -0,0 +1,84 @@ +import { isUndefined } from './general'; + +interface Node { + prev?: Node + next?: Node + value: T, + key: string +} + +export class LRU { + private totalStorage = 0; + private cache: { [key: string]: Node } = {}; + private head: Node | undefined; + private tail: Node | undefined; + + constructor(private maxStorage: number) {} + + get(key: string): T | undefined { + return !isUndefined(this.cache[key]) + ? this.cache[key].value + : undefined; + } + + insert(key: string, value: T): void { + if (this.totalStorage >= this.maxStorage) { + + if (!isUndefined(this.get(key))) { + this.remove(key); + } else { + const { key } = >this.tail; + this.remove(key); + } + + } + + const node: Node = { key, value, next: this.head }; + this.add(key, node); + } + + remove(key: string): void { + const node = this.cache[key]; + + if (!node) return; + + const { prev, next } = node; + + if (prev) { + prev.next = next; + } else { + this.head = next; + } + + if (node === this.tail) { + this.tail = prev || this.head; + } + + if (next) { + next.prev = prev; + } + + delete this.cache[key]; + --this.totalStorage; + } + + toString() { + const out: any[] = []; + for (let node = this.head; node; out.push(node.value), node = node.next); + return out.join(', '); + } + + private add(key: string, node: Node) { + if (this.head) { + this.head.prev = node; + } + + if (!this.tail) { + this.tail = node; + } + + this.cache[key] = this.head = node; + + ++this.totalStorage; + } +}