325 lines
9.1 KiB
JavaScript
325 lines
9.1 KiB
JavaScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
'use strict';
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.TrieMap = exports.LRUCache = exports.BoundedLinkedMap = exports.LinkedMap = void 0;
|
|
/**
|
|
* A simple map to store value by a key object. Key can be any object that has toString() function to get
|
|
* string value of the key.
|
|
*/
|
|
class LinkedMap {
|
|
map;
|
|
_size;
|
|
constructor() {
|
|
this.map = Object.create(null);
|
|
this._size = 0;
|
|
}
|
|
get size() {
|
|
return this._size;
|
|
}
|
|
get(k) {
|
|
const value = this.peek(k);
|
|
return value ? value : null;
|
|
}
|
|
getOrSet(k, t) {
|
|
const res = this.get(k);
|
|
if (res) {
|
|
return res;
|
|
}
|
|
this.set(k, t);
|
|
return t;
|
|
}
|
|
keys() {
|
|
const keys = [];
|
|
for (let key in this.map) {
|
|
keys.push(this.map[key].key);
|
|
}
|
|
return keys;
|
|
}
|
|
values() {
|
|
const values = [];
|
|
for (let key in this.map) {
|
|
values.push(this.map[key].value);
|
|
}
|
|
return values;
|
|
}
|
|
entries() {
|
|
const entries = [];
|
|
for (let key in this.map) {
|
|
entries.push(this.map[key]);
|
|
}
|
|
return entries;
|
|
}
|
|
set(k, t) {
|
|
if (this.get(k)) {
|
|
return false; // already present!
|
|
}
|
|
this.push(k, t);
|
|
return true;
|
|
}
|
|
delete(k) {
|
|
let value = this.get(k);
|
|
if (value) {
|
|
this.pop(k);
|
|
return value;
|
|
}
|
|
return null;
|
|
}
|
|
has(k) {
|
|
return !!this.get(k);
|
|
}
|
|
clear() {
|
|
this.map = Object.create(null);
|
|
this._size = 0;
|
|
}
|
|
push(key, value) {
|
|
const entry = { key, value };
|
|
this.map[key.toString()] = entry;
|
|
this._size++;
|
|
}
|
|
pop(k) {
|
|
delete this.map[k.toString()];
|
|
this._size--;
|
|
}
|
|
peek(k) {
|
|
const entry = this.map[k.toString()];
|
|
return entry ? entry.value : null;
|
|
}
|
|
}
|
|
exports.LinkedMap = LinkedMap;
|
|
/**
|
|
* A simple Map<T> that optionally allows to set a limit of entries to store. Once the limit is hit,
|
|
* the cache will remove the entry that was last recently added. Or, if a ratio is provided below 1,
|
|
* all elements will be removed until the ratio is full filled (e.g. 0.75 to remove 25% of old elements).
|
|
*/
|
|
class BoundedLinkedMap {
|
|
limit;
|
|
map;
|
|
head;
|
|
tail;
|
|
_size;
|
|
ratio;
|
|
constructor(limit = Number.MAX_VALUE, ratio = 1) {
|
|
this.limit = limit;
|
|
this.map = Object.create(null);
|
|
this._size = 0;
|
|
this.ratio = limit * ratio;
|
|
}
|
|
get size() {
|
|
return this._size;
|
|
}
|
|
set(key, value) {
|
|
if (this.map[key]) {
|
|
return false; // already present!
|
|
}
|
|
const entry = { key, value };
|
|
this.push(entry);
|
|
if (this._size > this.limit) {
|
|
this.trim();
|
|
}
|
|
return true;
|
|
}
|
|
get(key) {
|
|
const entry = this.map[key];
|
|
return entry ? entry.value : null;
|
|
}
|
|
getOrSet(k, t) {
|
|
const res = this.get(k);
|
|
if (res) {
|
|
return res;
|
|
}
|
|
this.set(k, t);
|
|
return t;
|
|
}
|
|
delete(key) {
|
|
const entry = this.map[key];
|
|
if (entry) {
|
|
this.map[key] = void 0;
|
|
this._size--;
|
|
if (entry.next) {
|
|
entry.next.prev = entry.prev; // [A]<-[x]<-[C] = [A]<-[C]
|
|
}
|
|
else {
|
|
this.head = entry.prev; // [A]-[x] = [A]
|
|
}
|
|
if (entry.prev) {
|
|
entry.prev.next = entry.next; // [A]->[x]->[C] = [A]->[C]
|
|
}
|
|
else {
|
|
this.tail = entry.next; // [x]-[A] = [A]
|
|
}
|
|
return entry.value;
|
|
}
|
|
return null;
|
|
}
|
|
has(key) {
|
|
return !!this.map[key];
|
|
}
|
|
clear() {
|
|
this.map = Object.create(null);
|
|
this._size = 0;
|
|
this.head = null;
|
|
this.tail = null;
|
|
}
|
|
push(entry) {
|
|
if (this.head) {
|
|
// [A]-[B] = [A]-[B]->[X]
|
|
entry.prev = this.head;
|
|
this.head.next = entry;
|
|
}
|
|
if (!this.tail) {
|
|
this.tail = entry;
|
|
}
|
|
this.head = entry;
|
|
this.map[entry.key] = entry;
|
|
this._size++;
|
|
}
|
|
trim() {
|
|
if (this.tail) {
|
|
// Remove all elements until ratio is reached
|
|
if (this.ratio < this.limit) {
|
|
let index = 0;
|
|
let current = this.tail;
|
|
while (current.next) {
|
|
// Remove the entry
|
|
this.map[current.key] = void 0;
|
|
this._size--;
|
|
// if we reached the element that overflows our ratio condition
|
|
// make its next element the new tail of the Map and adjust the size
|
|
if (index === this.ratio) {
|
|
this.tail = current.next;
|
|
this.tail.prev = null;
|
|
break;
|
|
}
|
|
// Move on
|
|
current = current.next;
|
|
index++;
|
|
}
|
|
}
|
|
// Just remove the tail element
|
|
else {
|
|
this.map[this.tail.key] = void 0;
|
|
this._size--;
|
|
// [x]-[B] = [B]
|
|
this.tail = this.tail.next;
|
|
this.tail.prev = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
exports.BoundedLinkedMap = BoundedLinkedMap;
|
|
/**
|
|
* A subclass of Map<T> that makes an entry the MRU entry as soon
|
|
* as it is being accessed. In combination with the limit for the
|
|
* maximum number of elements in the cache, it helps to remove those
|
|
* entries from the cache that are LRU.
|
|
*/
|
|
class LRUCache extends BoundedLinkedMap {
|
|
constructor(limit) {
|
|
super(limit);
|
|
}
|
|
get(key) {
|
|
// Upon access of an entry, make it the head of
|
|
// the linked map so that it is the MRU element
|
|
const entry = this.map[key];
|
|
if (entry) {
|
|
this.delete(key);
|
|
this.push(entry);
|
|
return entry.value;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
exports.LRUCache = LRUCache;
|
|
// --- trie'ish datastructure
|
|
class Node {
|
|
element;
|
|
children = new Map();
|
|
}
|
|
/**
|
|
* A trie map that allows for fast look up when keys are substrings
|
|
* to the actual search keys (dir/subdir-problem).
|
|
*/
|
|
class TrieMap {
|
|
static PathSplitter = (s) => s.split(/[\\/]/).filter(s => !!s);
|
|
_splitter;
|
|
_root = new Node();
|
|
constructor(splitter) {
|
|
this._splitter = splitter;
|
|
}
|
|
insert(path, element) {
|
|
const parts = this._splitter(path);
|
|
let i = 0;
|
|
// find insertion node
|
|
let node = this._root;
|
|
for (; i < parts.length; i++) {
|
|
let child = node.children[parts[i]];
|
|
if (child) {
|
|
node = child;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
// create new nodes
|
|
let newNode;
|
|
for (; i < parts.length; i++) {
|
|
newNode = new Node();
|
|
node.children[parts[i]] = newNode;
|
|
node = newNode;
|
|
}
|
|
node.element = element;
|
|
}
|
|
lookUp(path) {
|
|
const parts = this._splitter(path);
|
|
let { children } = this._root;
|
|
let node;
|
|
for (const part of parts) {
|
|
node = children[part];
|
|
if (!node) {
|
|
return undefined;
|
|
}
|
|
children = node.children;
|
|
}
|
|
return node.element;
|
|
}
|
|
findSubstr(path) {
|
|
const parts = this._splitter(path);
|
|
let lastNode;
|
|
let { children } = this._root;
|
|
for (const part of parts) {
|
|
const node = children[part];
|
|
if (!node) {
|
|
break;
|
|
}
|
|
if (node.element) {
|
|
lastNode = node;
|
|
}
|
|
children = node.children;
|
|
}
|
|
// return the last matching node
|
|
// that had an element
|
|
if (lastNode) {
|
|
return lastNode.element;
|
|
}
|
|
return undefined;
|
|
}
|
|
findSuperstr(path) {
|
|
const parts = this._splitter(path);
|
|
let { children } = this._root;
|
|
let node;
|
|
for (const part of parts) {
|
|
node = children[part];
|
|
if (!node) {
|
|
return undefined;
|
|
}
|
|
children = node.children;
|
|
}
|
|
const result = new TrieMap(this._splitter);
|
|
result._root = node;
|
|
return result;
|
|
}
|
|
}
|
|
exports.TrieMap = TrieMap;
|
|
//# sourceMappingURL=map.js.map
|