208 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			208 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| const createLRU = (options) => {
 | |
|   let { max } = options;
 | |
|   if (!(Number.isInteger(max) && max > 0))
 | |
|     throw new TypeError("`max` must be a positive integer");
 | |
|   let size = 0;
 | |
|   let head = 0;
 | |
|   let tail = 0;
 | |
|   let free = [];
 | |
|   const { onEviction } = options;
 | |
|   const keyMap = /* @__PURE__ */ new Map();
 | |
|   const keyList = new Array(max).fill(void 0);
 | |
|   const valList = new Array(max).fill(void 0);
 | |
|   const next = new Array(max).fill(0);
 | |
|   const prev = new Array(max).fill(0);
 | |
|   const setTail = (index, type) => {
 | |
|     if (index === tail) return;
 | |
|     const nextIndex = next[index];
 | |
|     const prevIndex = prev[index];
 | |
|     if (index === head) head = nextIndex;
 | |
|     else if (type === "get" || prevIndex !== 0) next[prevIndex] = nextIndex;
 | |
|     if (nextIndex !== 0) prev[nextIndex] = prevIndex;
 | |
|     next[tail] = index;
 | |
|     prev[index] = tail;
 | |
|     next[index] = 0;
 | |
|     tail = index;
 | |
|   };
 | |
|   const _evict = () => {
 | |
|     const evictHead = head;
 | |
|     const key = keyList[evictHead];
 | |
|     onEviction == null ? void 0 : onEviction(key, valList[evictHead]);
 | |
|     keyMap.delete(key);
 | |
|     keyList[evictHead] = void 0;
 | |
|     valList[evictHead] = void 0;
 | |
|     head = next[evictHead];
 | |
|     if (head !== 0) prev[head] = 0;
 | |
|     size--;
 | |
|     if (size === 0) head = tail = 0;
 | |
|     free.push(evictHead);
 | |
|     return evictHead;
 | |
|   };
 | |
|   return {
 | |
|     /** Adds a key-value pair to the cache. Updates the value if the key already exists. */
 | |
|     set(key, value) {
 | |
|       if (key === void 0) return;
 | |
|       let index = keyMap.get(key);
 | |
|       if (index === void 0) {
 | |
|         index = size === max ? _evict() : free.length > 0 ? free.pop() : size;
 | |
|         keyMap.set(key, index);
 | |
|         keyList[index] = key;
 | |
|         size++;
 | |
|       } else onEviction == null ? void 0 : onEviction(key, valList[index]);
 | |
|       valList[index] = value;
 | |
|       if (size === 1) head = tail = index;
 | |
|       else setTail(index, "set");
 | |
|     },
 | |
|     /** Retrieves the value for a given key and moves the key to the most recent position. */
 | |
|     get(key) {
 | |
|       const index = keyMap.get(key);
 | |
|       if (index === void 0) return;
 | |
|       if (index !== tail) setTail(index, "get");
 | |
|       return valList[index];
 | |
|     },
 | |
|     /** Retrieves the value for a given key without changing its position. */
 | |
|     peek: (key) => {
 | |
|       const index = keyMap.get(key);
 | |
|       return index !== void 0 ? valList[index] : void 0;
 | |
|     },
 | |
|     /** Checks if a key exists in the cache. */
 | |
|     has: (key) => keyMap.has(key),
 | |
|     /** Iterates over all keys in the cache, from most recent to least recent. */
 | |
|     *keys() {
 | |
|       let current = tail;
 | |
|       for (let i = 0; i < size; i++) {
 | |
|         yield keyList[current];
 | |
|         current = prev[current];
 | |
|       }
 | |
|     },
 | |
|     /** Iterates over all values in the cache, from most recent to least recent. */
 | |
|     *values() {
 | |
|       let current = tail;
 | |
|       for (let i = 0; i < size; i++) {
 | |
|         yield valList[current];
 | |
|         current = prev[current];
 | |
|       }
 | |
|     },
 | |
|     /** Iterates over `[key, value]` pairs in the cache, from most recent to least recent. */
 | |
|     *entries() {
 | |
|       let current = tail;
 | |
|       for (let i = 0; i < size; i++) {
 | |
|         yield [keyList[current], valList[current]];
 | |
|         current = prev[current];
 | |
|       }
 | |
|     },
 | |
|     /** Iterates over each value-key pair in the cache, from most recent to least recent. */
 | |
|     forEach: (callback) => {
 | |
|       let current = tail;
 | |
|       for (let i = 0; i < size; i++) {
 | |
|         const key = keyList[current];
 | |
|         const value = valList[current];
 | |
|         callback(value, key);
 | |
|         current = prev[current];
 | |
|       }
 | |
|     },
 | |
|     /** Deletes a key-value pair from the cache. */
 | |
|     delete(key) {
 | |
|       const index = keyMap.get(key);
 | |
|       if (index === void 0) return false;
 | |
|       onEviction == null ? void 0 : onEviction(key, valList[index]);
 | |
|       keyMap.delete(key);
 | |
|       free.push(index);
 | |
|       keyList[index] = void 0;
 | |
|       valList[index] = void 0;
 | |
|       const prevIndex = prev[index];
 | |
|       const nextIndex = next[index];
 | |
|       if (prevIndex !== 0) next[prevIndex] = nextIndex;
 | |
|       if (nextIndex !== 0) prev[nextIndex] = prevIndex;
 | |
|       if (index === head) head = nextIndex;
 | |
|       if (index === tail) tail = prevIndex;
 | |
|       size--;
 | |
|       return true;
 | |
|     },
 | |
|     /** Evicts the oldest item or the specified number of the oldest items from the cache. */
 | |
|     evict: (number) => {
 | |
|       let toPrune = Math.min(number, size);
 | |
|       while (toPrune > 0) {
 | |
|         _evict();
 | |
|         toPrune--;
 | |
|       }
 | |
|     },
 | |
|     /** Clears all key-value pairs from the cache. */
 | |
|     clear() {
 | |
|       if (typeof onEviction === "function") {
 | |
|         const iterator = keyMap.values();
 | |
|         for (let result = iterator.next(); !result.done; result = iterator.next())
 | |
|           onEviction(keyList[result.value], valList[result.value]);
 | |
|       }
 | |
|       keyMap.clear();
 | |
|       keyList.fill(void 0);
 | |
|       valList.fill(void 0);
 | |
|       free = [];
 | |
|       size = 0;
 | |
|       head = tail = 0;
 | |
|     },
 | |
|     /** Resizes the cache to a new maximum size, evicting items if necessary. */
 | |
|     resize: (newMax) => {
 | |
|       if (!(Number.isInteger(newMax) && newMax > 0))
 | |
|         throw new TypeError("`max` must be a positive integer");
 | |
|       if (newMax === max) return;
 | |
|       if (newMax < max) {
 | |
|         let current = tail;
 | |
|         const preserve = Math.min(size, newMax);
 | |
|         const remove = size - preserve;
 | |
|         const newKeyList = new Array(newMax);
 | |
|         const newValList = new Array(newMax);
 | |
|         const newNext = new Array(newMax);
 | |
|         const newPrev = new Array(newMax);
 | |
|         for (let i = 1; i <= remove; i++)
 | |
|           onEviction == null ? void 0 : onEviction(keyList[i], valList[i]);
 | |
|         for (let i = preserve - 1; i >= 0; i--) {
 | |
|           newKeyList[i] = keyList[current];
 | |
|           newValList[i] = valList[current];
 | |
|           newNext[i] = i + 1;
 | |
|           newPrev[i] = i - 1;
 | |
|           keyMap.set(newKeyList[i], i);
 | |
|           current = prev[current];
 | |
|         }
 | |
|         head = 0;
 | |
|         tail = preserve - 1;
 | |
|         size = preserve;
 | |
|         keyList.length = newMax;
 | |
|         valList.length = newMax;
 | |
|         next.length = newMax;
 | |
|         prev.length = newMax;
 | |
|         for (let i = 0; i < preserve; i++) {
 | |
|           keyList[i] = newKeyList[i];
 | |
|           valList[i] = newValList[i];
 | |
|           next[i] = newNext[i];
 | |
|           prev[i] = newPrev[i];
 | |
|         }
 | |
|         free = [];
 | |
|         for (let i = preserve; i < newMax; i++) free.push(i);
 | |
|       } else {
 | |
|         const fill = newMax - max;
 | |
|         keyList.push(...new Array(fill).fill(void 0));
 | |
|         valList.push(...new Array(fill).fill(void 0));
 | |
|         next.push(...new Array(fill).fill(0));
 | |
|         prev.push(...new Array(fill).fill(0));
 | |
|       }
 | |
|       max = newMax;
 | |
|     },
 | |
|     /** Returns the maximum number of items that can be stored in the cache. */
 | |
|     get max() {
 | |
|       return max;
 | |
|     },
 | |
|     /** Returns the number of items currently stored in the cache. */
 | |
|     get size() {
 | |
|       return size;
 | |
|     },
 | |
|     /** Returns the number of currently available slots in the cache before reaching the maximum size. */
 | |
|     get available() {
 | |
|       return max - size;
 | |
|     }
 | |
|   };
 | |
| };
 | |
| export {
 | |
|   createLRU
 | |
| };
 |