-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add doubly-linked list implementation (#970)
* feat: add doubly-linked list implementation Add doubly-linked list implemented as a circular list. The API of the `List` class is almost identical to the `Deque` API. A `List` uses more memory than a `Queue`, but a `List` takes O(1) time to do insertions and removals anywhere in the list, whereas a `Queue` only takes O(1) time to do insertions and removals at the front and back of the queue, and O(n) everywhere else. Also add a testsuite for the `List` class. * feat: adjust push() and unshift() to return the added list node * feat: add LRU and MRU cache implementations * feat: add remove() to cache classes Add remove(value) to remove a value from the cache independently from the eviction policy.
- Loading branch information
1 parent
09df20c
commit e35f10b
Showing
4 changed files
with
808 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import { expect, test } from "@jest/globals"; | ||
import { LRUCache, MRUCache } from "./cache"; | ||
|
||
test("new LRU cache is not full", () => { | ||
const cache = new LRUCache<number>(3); | ||
expect(cache.isFull()).toBe(false); | ||
}); | ||
|
||
test("put into empty LRU cache", () => { | ||
const cache = new LRUCache<number>(3); | ||
const value = cache.put(10); | ||
expect(value).toBe(undefined); | ||
expect(cache.oldest()).toBe(10); | ||
expect(cache.oldest()).toBe(cache.newest()); | ||
}); | ||
|
||
test("remove from empty LRU cache", () => { | ||
const cache = new LRUCache<number>(3); | ||
cache.remove(10); | ||
expect(cache.asArray()).toEqual({}); | ||
}); | ||
|
||
test("put into non-empty LRU cache", () => { | ||
const cache = new LRUCache<number>(3); | ||
cache.put(10); | ||
cache.put(20); | ||
const value = cache.put(30); | ||
expect(value).toBe(undefined); | ||
expect(cache.oldest()).toBe(10); | ||
expect(cache.newest()).toBe(30); | ||
expect(cache.asArray()).toEqual({ 1: 10, 2: 20, 3: 30 }); | ||
}); | ||
|
||
test("remove from non-empty LRU cache", () => { | ||
const cache = new LRUCache<number>(3); | ||
cache.put(10); | ||
cache.put(20); | ||
cache.remove(10); | ||
expect(cache.oldest()).toBe(20); | ||
expect(cache.asArray()).toEqual({ 1: 20 }); | ||
}); | ||
|
||
test("put into full LRU cache", () => { | ||
const cache = new LRUCache<number>(3); | ||
cache.put(10); | ||
cache.put(20); | ||
cache.put(30); | ||
const value = cache.put(40); | ||
expect(value).toBe(10); | ||
expect(cache.asArray()).toEqual({ 1: 20, 2: 30, 3: 40 }); | ||
}); | ||
|
||
test("new MRU cache is not full", () => { | ||
const cache = new MRUCache<number>(3); | ||
expect(cache.isFull()).toBe(false); | ||
}); | ||
|
||
test("remove from empty MRU uache", () => { | ||
const cache = new MRUCache<number>(3); | ||
cache.remove(10); | ||
expect(cache.asArray()).toEqual({}); | ||
}); | ||
|
||
test("put into empty MRU cache", () => { | ||
const cache = new MRUCache<number>(3); | ||
const value = cache.put(10); | ||
expect(value).toBe(undefined); | ||
expect(cache.oldest()).toBe(10); | ||
expect(cache.oldest()).toBe(cache.newest()); | ||
}); | ||
|
||
test("put into non-empty MRU cache", () => { | ||
const cache = new MRUCache<number>(3); | ||
cache.put(10); | ||
cache.put(20); | ||
const value = cache.put(30); | ||
expect(value).toBe(undefined); | ||
expect(cache.oldest()).toBe(10); | ||
expect(cache.newest()).toBe(30); | ||
expect(cache.asArray()).toEqual({ 1: 10, 2: 20, 3: 30 }); | ||
}); | ||
|
||
test("remove from non-empty MRU cache", () => { | ||
const cache = new MRUCache<number>(3); | ||
cache.put(10); | ||
cache.put(20); | ||
cache.remove(10); | ||
expect(cache.oldest()).toBe(20); | ||
expect(cache.asArray()).toEqual({ 1: 20 }); | ||
}); | ||
|
||
test("put into full MRU cache", () => { | ||
const cache = new MRUCache<number>(3); | ||
cache.put(10); | ||
cache.put(20); | ||
cache.put(30); | ||
const value = cache.put(40); | ||
expect(value).toBe(30); | ||
expect(cache.asArray()).toEqual({ 1: 10, 2: 20, 3: 40 }); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { LuaObj } from "@wowts/lua"; | ||
import { List, ListNode } from "./list"; | ||
|
||
class Cache<T> { | ||
list: List<T>; | ||
nodeByValue: LuaObj<ListNode<T>> = {}; | ||
|
||
constructor(public size: number) { | ||
this.list = new List<T>(); | ||
} | ||
|
||
isFull() { | ||
return this.list.length >= this.size; | ||
} | ||
|
||
newest() { | ||
return this.list.back(); | ||
} | ||
|
||
oldest() { | ||
return this.list.front(); | ||
} | ||
|
||
asArray() { | ||
return this.list.asArray(); | ||
} | ||
|
||
evict() { | ||
return this.list.shift(); | ||
} | ||
|
||
put(value: T) { | ||
this.remove(value); | ||
const evicted = (this.isFull() && this.evict()) || undefined; | ||
/* Pretend to cast to string to satisfy TypeScript. | ||
* Lua tables can accept anything as a valid key. | ||
*/ | ||
const key = value as unknown as string; | ||
this.nodeByValue[key] = this.list.push(value); | ||
return evicted; | ||
} | ||
|
||
remove(value: T) { | ||
/* Pretend to cast to string to satisfy TypeScript. | ||
* Lua tables can accept anything as a valid key. | ||
*/ | ||
const key = value as unknown as string; | ||
const node = this.nodeByValue[key]; | ||
if (node) { | ||
this.list.remove(node); | ||
} | ||
} | ||
} | ||
|
||
export class LRUCache<T> extends Cache<T> { | ||
evict() { | ||
// LRU policy evicts the oldest item | ||
return this.list.shift(); | ||
} | ||
} | ||
|
||
export class MRUCache<T> extends Cache<T> { | ||
evict() { | ||
// MRU policy evicts the newest item | ||
return this.list.pop(); | ||
} | ||
} |
Oops, something went wrong.