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.
		
		
		
		
		
			
		
			
				
					288 lines
				
				7.3 KiB
			
		
		
			
		
	
	
					288 lines
				
				7.3 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Mnemonist LRUMapWithDelete
							 | 
						||
| 
								 | 
							
								 * ===========================
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * An extension of LRUMap with delete functionality.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var LRUMap = require('./lru-map.js'),
							 | 
						||
| 
								 | 
							
								    forEach = require('obliterator/foreach'),
							 | 
						||
| 
								 | 
							
								    typed = require('./utils/typed-arrays.js'),
							 | 
						||
| 
								 | 
							
								    iterables = require('./utils/iterables.js');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// The only complication with deleting items is that the LRU's
							 | 
						||
| 
								 | 
							
								// performance depends on having a fixed-size list of pointers; the
							 | 
						||
| 
								 | 
							
								// doubly-linked-list is happy to expand and contract.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// On delete, we record the position of the former item's pointer in a
							 | 
						||
| 
								 | 
							
								// list of "holes" in the pointer array. On insert, if there is a hole
							 | 
						||
| 
								 | 
							
								// the new pointer slots in to fill the hole; otherwise, it is
							 | 
						||
| 
								 | 
							
								// appended as usual. (Note: we are only talking here about the
							 | 
						||
| 
								 | 
							
								// internal pointer list. setting or getting an item promotes it
							 | 
						||
| 
								 | 
							
								// to the top of the LRU ranking no matter what came before)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function LRUMapWithDelete(Keys, Values, capacity) {
							 | 
						||
| 
								 | 
							
								  if (arguments.length < 2) {
							 | 
						||
| 
								 | 
							
								    LRUMap.call(this, Keys);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  else {
							 | 
						||
| 
								 | 
							
								    LRUMap.call(this, Keys, Values, capacity);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  var PointerArray = typed.getPointerArray(this.capacity);
							 | 
						||
| 
								 | 
							
								  this.deleted = new PointerArray(this.capacity);
							 | 
						||
| 
								 | 
							
								  this.deletedSize = 0;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								for (var k in LRUMap.prototype)
							 | 
						||
| 
								 | 
							
								  LRUMapWithDelete.prototype[k] = LRUMap.prototype[k];
							 | 
						||
| 
								 | 
							
								if (typeof Symbol !== 'undefined')
							 | 
						||
| 
								 | 
							
								  LRUMapWithDelete.prototype[Symbol.iterator] = LRUMap.prototype[Symbol.iterator];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Method used to clear the structure.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @return {undefined}
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								LRUMapWithDelete.prototype.clear = function() {
							 | 
						||
| 
								 | 
							
								  LRUMap.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}
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								LRUMapWithDelete.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) {
							 | 
						||
| 
								 | 
							
								    if (this.deletedSize > 0) {
							 | 
						||
| 
								 | 
							
								      // If there is a "hole" in the pointer list, reuse it
							 | 
						||
| 
								 | 
							
								      pointer = this.deleted[--this.deletedSize];
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else {
							 | 
						||
| 
								 | 
							
								      // otherwise append to the pointer list
							 | 
						||
| 
								 | 
							
								      pointer = this.size;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    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.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								LRUMapWithDelete.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) {
							 | 
						||
| 
								 | 
							
								    if (this.deletedSize > 0) {
							 | 
						||
| 
								 | 
							
								      // If there is a "hole" in the pointer list, reuse it
							 | 
						||
| 
								 | 
							
								      pointer = this.deleted[--this.deletedSize];
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else {
							 | 
						||
| 
								 | 
							
								      // otherwise append to the pointer list
							 | 
						||
| 
								 | 
							
								      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];
							 | 
						||
| 
								 | 
							
								    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 delete the entry for the given key in the cache.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param  {any} key   - Key.
							 | 
						||
| 
								 | 
							
								 * @return {boolean}   - true if the item was present
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								LRUMapWithDelete.prototype.delete = function(key) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var pointer = this.items.get(key);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (typeof pointer === 'undefined') {
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this.items.delete(key);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (this.size === 1) {
							 | 
						||
| 
								 | 
							
								    this.size = 0;
							 | 
						||
| 
								 | 
							
								    this.head = 0;
							 | 
						||
| 
								 | 
							
								    this.tail = 0;
							 | 
						||
| 
								 | 
							
								    this.deletedSize = 0;
							 | 
						||
| 
								 | 
							
								    return true;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  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;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this.size--;
							 | 
						||
| 
								 | 
							
								  this.deleted[this.deletedSize++] = pointer;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return true;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Method used to remove and return the value for the given key in the cache.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param  {any} key                 - Key.
							 | 
						||
| 
								 | 
							
								 * @param  {any} [missing=undefined] - Value to return if item is absent
							 | 
						||
| 
								 | 
							
								 * @return {any} The value, if present; the missing indicator if absent
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								LRUMapWithDelete.prototype.remove = function(key, missing = undefined) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var pointer = this.items.get(key);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (typeof pointer === 'undefined') {
							 | 
						||
| 
								 | 
							
								    return missing;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var dead = this.V[pointer];
							 | 
						||
| 
								 | 
							
								  this.items.delete(key);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (this.size === 1) {
							 | 
						||
| 
								 | 
							
								    this.size = 0;
							 | 
						||
| 
								 | 
							
								    this.head = 0;
							 | 
						||
| 
								 | 
							
								    this.tail = 0;
							 | 
						||
| 
								 | 
							
								    this.deletedSize = 0;
							 | 
						||
| 
								 | 
							
								    return dead;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  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;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this.size--;
							 | 
						||
| 
								 | 
							
								  this.deleted[this.deletedSize++] = pointer;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return dead;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * 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 {LRUMapWithDelete}
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								LRUMapWithDelete.from = function(iterable, Keys, Values, capacity) {
							 | 
						||
| 
								 | 
							
								  if (arguments.length < 2) {
							 | 
						||
| 
								 | 
							
								    capacity = iterables.guessLength(iterable);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (typeof capacity !== 'number')
							 | 
						||
| 
								 | 
							
								      throw new Error('mnemonist/lru-map.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 LRUMapWithDelete(Keys, Values, capacity);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  forEach(iterable, function(value, key) {
							 | 
						||
| 
								 | 
							
								    cache.set(key, value);
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return cache;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = LRUMapWithDelete;
							 |