You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
262 lines
6.7 KiB
262 lines
6.7 KiB
/**
|
|
* Mnemonist LRUMap
|
|
* =================
|
|
*
|
|
* Variant of the LRUCache class that leverages an ES6 Map instead of an object.
|
|
* It might be faster for some use case but it is still hard to understand
|
|
* when a Map can outperform an object in v8.
|
|
*/
|
|
var LRUCache = require('./lru-cache.js'),
|
|
forEach = require('obliterator/foreach'),
|
|
typed = require('./utils/typed-arrays.js'),
|
|
iterables = require('./utils/iterables.js');
|
|
|
|
/**
|
|
* LRUMap.
|
|
*
|
|
* @constructor
|
|
* @param {function} Keys - Array class for storing keys.
|
|
* @param {function} Values - Array class for storing values.
|
|
* @param {number} capacity - Desired capacity.
|
|
*/
|
|
function LRUMap(Keys, Values, capacity) {
|
|
if (arguments.length < 2) {
|
|
capacity = Keys;
|
|
Keys = null;
|
|
Values = null;
|
|
}
|
|
|
|
this.capacity = capacity;
|
|
|
|
if (typeof this.capacity !== 'number' || this.capacity <= 0)
|
|
throw new Error('mnemonist/lru-map: capacity should be positive number.');
|
|
else if (!isFinite(this.capacity) || Math.floor(this.capacity) !== this.capacity)
|
|
throw new Error('mnemonist/lru-map: capacity should be a finite positive integer.');
|
|
|
|
var PointerArray = typed.getPointerArray(capacity);
|
|
|
|
this.forward = new PointerArray(capacity);
|
|
this.backward = new PointerArray(capacity);
|
|
this.K = typeof Keys === 'function' ? new Keys(capacity) : new Array(capacity);
|
|
this.V = typeof Values === 'function' ? new Values(capacity) : new Array(capacity);
|
|
|
|
// Properties
|
|
this.size = 0;
|
|
this.head = 0;
|
|
this.tail = 0;
|
|
this.items = new Map();
|
|
}
|
|
|
|
/**
|
|
* Method used to clear the structure.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
LRUMap.prototype.clear = function() {
|
|
this.size = 0;
|
|
this.head = 0;
|
|
this.tail = 0;
|
|
this.items.clear();
|
|
};
|
|
|
|
/**
|
|
* Method used to set the value for the given key in the cache.
|
|
*
|
|
* @param {any} key - Key.
|
|
* @param {any} value - Value.
|
|
* @return {undefined}
|
|
*/
|
|
LRUMap.prototype.set = function(key, value) {
|
|
|
|
var pointer = this.items.get(key);
|
|
|
|
// The key already exists, we just need to update the value and splay on top
|
|
if (typeof pointer !== 'undefined') {
|
|
this.splayOnTop(pointer);
|
|
this.V[pointer] = value;
|
|
|
|
return;
|
|
}
|
|
|
|
// The cache is not yet full
|
|
if (this.size < this.capacity) {
|
|
pointer = this.size++;
|
|
}
|
|
|
|
// Cache is full, we need to drop the last value
|
|
else {
|
|
pointer = this.tail;
|
|
this.tail = this.backward[pointer];
|
|
this.items.delete(this.K[pointer]);
|
|
}
|
|
|
|
// Storing key & value
|
|
this.items.set(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;
|
|
};
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
LRUMap.prototype.setpop = function(key, value) {
|
|
var oldValue = null;
|
|
var oldKey = null;
|
|
|
|
var pointer = this.items.get(key);
|
|
|
|
// The key already exists, we just need to update the value and splay on top
|
|
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) {
|
|
pointer = 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];
|
|
this.items.delete(this.K[pointer]);
|
|
}
|
|
|
|
// Storing key & value
|
|
this.items.set(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 check whether the key exists in the cache.
|
|
*
|
|
* @param {any} key - Key.
|
|
* @return {boolean}
|
|
*/
|
|
LRUMap.prototype.has = function(key) {
|
|
return this.items.has(key);
|
|
};
|
|
|
|
/**
|
|
* Method used to get the value attached to the given key. Will move the
|
|
* related key to the front of the underlying linked list.
|
|
*
|
|
* @param {any} key - Key.
|
|
* @return {any}
|
|
*/
|
|
LRUMap.prototype.get = function(key) {
|
|
var pointer = this.items.get(key);
|
|
|
|
if (typeof pointer === 'undefined')
|
|
return;
|
|
|
|
this.splayOnTop(pointer);
|
|
|
|
return this.V[pointer];
|
|
};
|
|
|
|
/**
|
|
* Method used to get the value attached to the given key. Does not modify
|
|
* the ordering of the underlying linked list.
|
|
*
|
|
* @param {any} key - Key.
|
|
* @return {any}
|
|
*/
|
|
LRUMap.prototype.peek = function(key) {
|
|
var pointer = this.items.get(key);
|
|
|
|
if (typeof pointer === 'undefined')
|
|
return;
|
|
|
|
return this.V[pointer];
|
|
};
|
|
|
|
/**
|
|
* Methods that can be reused as-is from LRUCache.
|
|
*/
|
|
LRUMap.prototype.splayOnTop = LRUCache.prototype.splayOnTop;
|
|
LRUMap.prototype.forEach = LRUCache.prototype.forEach;
|
|
LRUMap.prototype.keys = LRUCache.prototype.keys;
|
|
LRUMap.prototype.values = LRUCache.prototype.values;
|
|
LRUMap.prototype.entries = LRUCache.prototype.entries;
|
|
|
|
/**
|
|
* Attaching the #.entries method to Symbol.iterator if possible.
|
|
*/
|
|
if (typeof Symbol !== 'undefined')
|
|
LRUMap.prototype[Symbol.iterator] = LRUMap.prototype.entries;
|
|
|
|
/**
|
|
* Convenience known methods.
|
|
*/
|
|
LRUMap.prototype.inspect = LRUCache.prototype.inspect;
|
|
|
|
/**
|
|
* 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 {LRUMap}
|
|
*/
|
|
LRUMap.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 LRUMap(Keys, Values, capacity);
|
|
|
|
forEach(iterable, function(value, key) {
|
|
cache.set(key, value);
|
|
});
|
|
|
|
return cache;
|
|
};
|
|
|
|
/**
|
|
* Exporting.
|
|
*/
|
|
module.exports = LRUMap;
|