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;
 |