-
-
Notifications
You must be signed in to change notification settings - Fork 93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add ObliviousLRUCache with delete functionality #162
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/** | ||
* Mnemonist ObliviousLRUCache Typings | ||
* =========================== | ||
*/ | ||
import {IArrayLikeConstructor} from './utils/types'; | ||
|
||
export default class ObliviousLRUCache<K, V> implements Iterable<[K, V]> { | ||
|
||
// Members | ||
capacity: number; | ||
size: number; | ||
|
||
// Constructor | ||
constructor(capacity: number); | ||
constructor(KeyArrayClass: IArrayLikeConstructor, ValueArrayClass: IArrayLikeConstructor, capacity: number); | ||
|
||
// Methods | ||
clear(): void; | ||
set(key: K, value: V): this; | ||
setpop(key: K, value: V): {evicted: boolean, key: K, value: V}; | ||
get(key: K): V | undefined; | ||
peek(key: K): V | undefined; | ||
remove(key: K): this; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And probably return |
||
has(key: K): boolean; | ||
forEach(callback: (value: V, key: K, cache: this) => void, scope?: any): void; | ||
keys(): IterableIterator<K>; | ||
values(): IterableIterator<V>; | ||
entries(): IterableIterator<[K, V]>; | ||
[Symbol.iterator](): IterableIterator<[K, V]>; | ||
inspect(): any; | ||
|
||
// Statics | ||
static from<I, J>( | ||
iterable: Iterable<[I, J]> | {[key: string]: J}, | ||
KeyArrayClass: IArrayLikeConstructor, | ||
ValueArrayClass: IArrayLikeConstructor, | ||
capacity?: number | ||
): ObliviousLRUCache<I, J>; | ||
|
||
static from<I, J>( | ||
iterable: Iterable<[I, J]> | {[key: string]: J}, | ||
capacity?: number | ||
): ObliviousLRUCache<I, J>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
/** | ||
* Mnemonist ObliviousLRUCache | ||
* =================== | ||
* | ||
* An extension of LRUCache with delete functionality. | ||
*/ | ||
|
||
var LRUCache = require('./lru-cache.js'), | ||
forEach = require('obliterator/foreach'), | ||
typed = require('./utils/typed-arrays.js'), | ||
iterables = require('./utils/iterables.js'); | ||
|
||
function ObliviousLRUCache(Keys, Values, capacity) { | ||
if (arguments.length < 2) { | ||
LRUCache.call(this, Keys); | ||
} | ||
else { | ||
LRUCache.call(this, Keys, Values, capacity); | ||
} | ||
var PointerArray = typed.getPointerArray(this.capacity); | ||
this.deleted = new PointerArray(this.capacity); | ||
this.deletedSize = 0; | ||
} | ||
|
||
ObliviousLRUCache.prototype = Object.create(LRUCache.prototype); | ||
ObliviousLRUCache.prototype.constructor = ObliviousLRUCache; | ||
|
||
/** | ||
* Method used to clear the structure. | ||
* | ||
* @return {undefined} | ||
*/ | ||
ObliviousLRUCache.prototype.clear = function() { | ||
LRUCache.prototype.clear.call(this); | ||
this.deletedSize = 0; | ||
}; | ||
|
||
/** | ||
* Method used to set the value for the given key in the cache. | ||
* | ||
* @param {any} key - Key. | ||
* @param {any} value - Value. | ||
* @return {undefined} | ||
*/ | ||
ObliviousLRUCache.prototype.set = function(key, value) { | ||
this.setpop(key, value); | ||
}; | ||
|
||
/** | ||
* Method used to set the value for the given key in the cache | ||
* | ||
* @param {any} key - Key. | ||
* @param {any} value - Value. | ||
* @return {{evicted: boolean, key: any, value: any}} An object containing the | ||
* key and value of an item that was overwritten or evicted in the set | ||
* operation, as well as a boolean indicating whether it was evicted due to | ||
* limited capacity. Return value is null if nothing was evicted or overwritten | ||
* during the set operation. | ||
*/ | ||
ObliviousLRUCache.prototype.setpop = function(key, value) { | ||
var oldValue = null; | ||
var oldKey = null; | ||
// The key already exists, we just need to update the value and splay on top | ||
var pointer = this.items[key]; | ||
|
||
if (typeof pointer !== 'undefined') { | ||
this.splayOnTop(pointer); | ||
oldValue = this.V[pointer]; | ||
this.V[pointer] = value; | ||
return {evicted: false, key: key, value: oldValue}; | ||
} | ||
|
||
// The cache is not yet full | ||
if (this.size < this.capacity) { | ||
if (this.deletedSize > 0) { | ||
pointer = this.deleted[--this.deletedSize]; | ||
} | ||
else { | ||
pointer = this.size; | ||
} | ||
this.size++; | ||
} | ||
|
||
// Cache is full, we need to drop the last value | ||
else { | ||
pointer = this.tail; | ||
this.tail = this.backward[pointer]; | ||
oldValue = this.V[pointer]; | ||
oldKey = this.K[pointer]; | ||
delete this.items[this.K[pointer]]; | ||
} | ||
|
||
// Storing key & value | ||
this.items[key] = pointer; | ||
this.K[pointer] = key; | ||
this.V[pointer] = value; | ||
|
||
// Moving the item at the front of the list | ||
this.forward[pointer] = this.head; | ||
this.backward[this.head] = pointer; | ||
this.head = pointer; | ||
|
||
// Return object if eviction took place, otherwise return null | ||
if (oldKey) { | ||
return {evicted: true, key: oldKey, value: oldValue}; | ||
} | ||
else { | ||
return null; | ||
} | ||
}; | ||
|
||
/** | ||
* Method used to delete the value for the given key in the cache. | ||
* | ||
* @param {any} key - Key. | ||
* @return {undefined} | ||
*/ | ||
ObliviousLRUCache.prototype.delete = function(key) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method could a |
||
|
||
var pointer = this.items[key]; | ||
|
||
if (typeof pointer === 'undefined') { | ||
return; | ||
} | ||
|
||
if (this.head === pointer && this.tail === pointer) { | ||
this.clear(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using |
||
return; | ||
} | ||
|
||
var previous = this.backward[pointer], | ||
next = this.forward[pointer]; | ||
|
||
if (this.head === pointer) { | ||
this.head = next; | ||
} | ||
if (this.tail === pointer) { | ||
this.tail = previous; | ||
} | ||
|
||
this.forward[previous] = next; | ||
this.backward[next] = previous; | ||
|
||
delete this.items[key]; | ||
this.size--; | ||
this.deleted[this.deletedSize++] = pointer; | ||
}; | ||
|
||
/** | ||
* Static @.from function taking an arbitrary iterable & converting it into | ||
* a structure. | ||
* | ||
* @param {Iterable} iterable - Target iterable. | ||
* @param {function} Keys - Array class for storing keys. | ||
* @param {function} Values - Array class for storing values. | ||
* @param {number} capacity - Cache's capacity. | ||
* @return {ObliviousLRUCache} | ||
*/ | ||
ObliviousLRUCache.from = function(iterable, Keys, Values, capacity) { | ||
if (arguments.length < 2) { | ||
capacity = iterables.guessLength(iterable); | ||
|
||
if (typeof capacity !== 'number') | ||
throw new Error('mnemonist/lru-cache.from: could not guess iterable length. Please provide desired capacity as last argument.'); | ||
} | ||
else if (arguments.length === 2) { | ||
capacity = Keys; | ||
Keys = null; | ||
Values = null; | ||
} | ||
|
||
var cache = new ObliviousLRUCache(Keys, Values, capacity); | ||
|
||
forEach(iterable, function(value, key) { | ||
cache.set(key, value); | ||
}); | ||
|
||
return cache; | ||
}; | ||
|
||
module.exports = ObliviousLRUCache; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,8 @@ | |
*/ | ||
var assert = require('assert'), | ||
LRUCache = require('../lru-cache.js'), | ||
LRUMap = require('../lru-map.js'); | ||
LRUMap = require('../lru-map.js'), | ||
ObliviousLRUCache = require('../oblivious-lru-cache.js'); | ||
|
||
function makeTests(Cache, name) { | ||
describe(name, function() { | ||
|
@@ -56,7 +57,7 @@ function makeTests(Cache, name) { | |
assert.strictEqual(cache.peek('two'), 5); | ||
assert.deepStrictEqual(Array.from(cache.entries()), [['three', 3], ['four', 4], ['two', 5]]); | ||
|
||
if (name === 'LRUCache') | ||
if (name === 'LRUCache' || name === 'ObliviousLRUCache') | ||
assert.strictEqual(Object.keys(cache.items).length, 3); | ||
else | ||
assert.strictEqual(cache.items.size, 3); | ||
|
@@ -215,8 +216,52 @@ function makeTests(Cache, name) { | |
|
||
assert.deepStrictEqual(entries, Array.from(cache.entries())); | ||
}); | ||
|
||
if (name === 'ObliviousLRUCache') { | ||
it('should be possible to delete keys from a LRU cache.', function() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am wondering whether a rolling test should be done? Like add 2 remove 1, in a loop of ~10 items for a cache of ~4-5 capacity. |
||
var cache = new Cache(3); | ||
|
||
assert.strictEqual(cache.capacity, 3); | ||
|
||
cache.set('one', 1); | ||
cache.set('two', 2); | ||
cache.set('three', 3); | ||
|
||
assert.deepStrictEqual(Array.from(cache.entries()), [['three', 3], ['two', 2], ['one', 1]]); | ||
|
||
// Delete head | ||
cache.delete('three'); | ||
assert.deepStrictEqual(Array.from(cache.entries()), [['two', 2], ['one', 1]]); | ||
|
||
cache.set('three', 3); | ||
assert.deepStrictEqual(Array.from(cache.entries()), [['three', 3], ['two', 2], ['one', 1]]); | ||
// Delete node which is neither head or tail | ||
cache.delete('two'); | ||
assert.deepStrictEqual(Array.from(cache.entries()), [['three', 3], ['one', 1]]); | ||
|
||
// Delete tail | ||
cache.delete('one'); | ||
assert.deepStrictEqual(Array.from(cache.entries()), [['three', 3]]); | ||
|
||
// Delete the only key | ||
cache.delete('three'); | ||
assert.strictEqual(cache.capacity, 3); | ||
assert.strictEqual(cache.size, 0); | ||
assert.strictEqual(cache.head, 0); | ||
assert.strictEqual(cache.tail, 0); | ||
|
||
cache.set('one', 1); | ||
cache.set('two', 2); | ||
cache.set('three', 3); | ||
cache.set('two', 6); | ||
cache.set('four', 4); | ||
|
||
assert.deepStrictEqual(Array.from(cache.entries()), [['four', 4], ['two', 6], ['three', 3]]); | ||
}); | ||
} | ||
}); | ||
} | ||
|
||
makeTests(LRUCache, 'LRUCache'); | ||
makeTests(LRUMap, 'LRUMap'); | ||
makeTests(ObliviousLRUCache, 'ObliviousLRUCache'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may be wrong but I think you can import the
LRUCache
class in this definition file and make this class definitionextends
it, no?