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
						
					
					
				| /**
 | |
|  * Mnemonist LRUCacheWithDelete
 | |
|  * =============================
 | |
|  *
 | |
|  * 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');
 | |
| 
 | |
| // 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 LRUCacheWithDelete(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;
 | |
| }
 | |
| 
 | |
| for (var k in LRUCache.prototype)
 | |
|   LRUCacheWithDelete.prototype[k] = LRUCache.prototype[k];
 | |
| if (typeof Symbol !== 'undefined')
 | |
|   LRUCacheWithDelete.prototype[Symbol.iterator] = LRUCache.prototype[Symbol.iterator];
 | |
| 
 | |
| /**
 | |
|  * Method used to clear the structure.
 | |
|  *
 | |
|  * @return {undefined}
 | |
|  */
 | |
| LRUCacheWithDelete.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}
 | |
|  */
 | |
| LRUCacheWithDelete.prototype.set = function(key, value) {
 | |
| 
 | |
|   var pointer = this.items[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];
 | |
|     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;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * 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.
 | |
|  */
 | |
| LRUCacheWithDelete.prototype.setpop = function(key, value) {
 | |
|   var oldValue = null;
 | |
|   var oldKey = null;
 | |
| 
 | |
|   var pointer = this.items[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];
 | |
|     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 entry for the given key in the cache.
 | |
|  *
 | |
|  * @param  {any} key   - Key.
 | |
|  * @return {boolean}   - true if the item was present
 | |
|  */
 | |
| LRUCacheWithDelete.prototype.delete = function(key) {
 | |
| 
 | |
|   var pointer = this.items[key];
 | |
| 
 | |
|   if (typeof pointer === 'undefined') {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   delete this.items[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
 | |
|  */
 | |
| LRUCacheWithDelete.prototype.remove = function(key, missing = undefined) {
 | |
| 
 | |
|   var pointer = this.items[key];
 | |
| 
 | |
|   if (typeof pointer === 'undefined') {
 | |
|     return missing;
 | |
|   }
 | |
| 
 | |
|   var dead = this.V[pointer];
 | |
|   delete this.items[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 {LRUCacheWithDelete}
 | |
|  */
 | |
| LRUCacheWithDelete.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 LRUCacheWithDelete(Keys, Values, capacity);
 | |
| 
 | |
|   forEach(iterable, function(value, key) {
 | |
|     cache.set(key, value);
 | |
|   });
 | |
| 
 | |
|   return cache;
 | |
| };
 | |
| 
 | |
| module.exports = LRUCacheWithDelete;
 |