osr-mono/packages/core/collections.js
2025-01-29 17:48:22 +01:00

559 lines
16 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.insert = exports.index = exports.fill = exports.range = exports.flatten = exports.commonPrefixLength = exports.first = exports.firstIndex = exports.uniqueFilter = exports.distinct = exports.isFalsyOrEmpty = exports.move = exports.coalesce = exports.top = exports.findFirst = exports.binarySearch = exports.equals = exports.tail = exports.ArraySet = 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;
}
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;
}
}
findSuperstr(path) {
const parts = this._splitter(path);
let { children } = this._root;
let node;
for (const part of parts) {
node = children[part];
if (!node) {
return;
}
children = node.children;
}
const result = new TrieMap(this._splitter);
result._root = node;
return result;
}
}
exports.TrieMap = TrieMap;
class ArraySet {
_elements;
constructor(elements = []) {
this._elements = elements.slice();
}
get size() {
return this._elements.length;
}
set(element) {
this.unset(element);
this._elements.push(element);
}
contains(element) {
return this._elements.indexOf(element) > -1;
}
unset(element) {
const index = this._elements.indexOf(element);
if (index > -1) {
this._elements.splice(index, 1);
}
}
get elements() {
return this._elements.slice();
}
}
exports.ArraySet = ArraySet;
/**
* Returns the last element of an array.
* @param array The array.
* @param n Which element from the end (default ist zero).
*/
function tail(array, n = 0) {
return array[array.length - (1 + n)];
}
exports.tail = tail;
function equals(one, other, itemEquals = (a, b) => a === b) {
if (one.length !== other.length) {
return false;
}
for (let i = 0, len = one.length; i < len; i++) {
if (!itemEquals(one[i], other[i])) {
return false;
}
}
return true;
}
exports.equals = equals;
function binarySearch(array, key, comparator) {
let low = 0, high = array.length - 1;
while (low <= high) {
let mid = ((low + high) / 2) | 0;
let comp = comparator(array[mid], key);
if (comp < 0) {
low = mid + 1;
}
else if (comp > 0) {
high = mid - 1;
}
else {
return mid;
}
}
return -(low + 1);
}
exports.binarySearch = binarySearch;
/**
* Takes a sorted array and a function p. The array is sorted in such a way that all elements where p(x) is false
* are located before all elements where p(x) is true.
* @returns the least x for which p(x) is true or array.length if no element fullfills the given function.
*/
function findFirst(array, p) {
let low = 0, high = array.length;
if (high === 0) {
return 0; // no children
}
while (low < high) {
let mid = Math.floor((low + high) / 2);
if (p(array[mid])) {
high = mid;
}
else {
low = mid + 1;
}
}
return low;
}
exports.findFirst = findFirst;
/**
* Returns the top N elements from the array.
*
* Faster than sorting the entire array when the array is a lot larger than N.
*
* @param array The unsorted array.
* @param compare A sort function for the elements.
* @param n The number of elements to return.
* @return The first n elemnts from array when sorted with compare.
*/
function top(array, compare, n) {
if (n === 0) {
return [];
}
const result = array.slice(0, n).sort(compare);
for (let i = n, m = array.length; i < m; i++) {
const element = array[i];
if (compare(element, result[n - 1]) < 0) {
result.pop();
const j = findFirst(result, e => compare(element, e) < 0);
result.splice(j, 0, element);
}
}
return result;
}
exports.top = top;
/**
* @returns a new array with all undefined or null values removed. The original array is not modified at all.
*/
function coalesce(array) {
if (!array) {
return array;
}
return array.filter(e => !!e);
}
exports.coalesce = coalesce;
/**
* Moves the element in the array for the provided positions.
*/
function move(array, from, to) {
array.splice(to, 0, array.splice(from, 1)[0]);
}
exports.move = move;
/**
* @returns {{false}} if the provided object is an array
* and not empty.
*/
function isFalsyOrEmpty(obj) {
return !Array.isArray(obj) || obj.length === 0;
}
exports.isFalsyOrEmpty = isFalsyOrEmpty;
/**
* Removes duplicates from the given array. The optional keyFn allows to specify
* how elements are checked for equalness by returning a unique string for each.
*/
function distinct(array, keyFn) {
if (!keyFn) {
return array.filter((element, position) => {
return array.indexOf(element) === position;
});
}
const seen = Object.create(null);
return array.filter((elem) => {
const key = keyFn(elem);
if (seen[key]) {
return false;
}
seen[key] = true;
return true;
});
}
exports.distinct = distinct;
function uniqueFilter(keyFn) {
const seen = Object.create(null);
return element => {
const key = keyFn(element);
if (seen[key]) {
return false;
}
seen[key] = true;
return true;
};
}
exports.uniqueFilter = uniqueFilter;
function firstIndex(array, fn) {
for (let i = 0; i < array.length; i++) {
const element = array[i];
if (fn(element)) {
return i;
}
}
return -1;
}
exports.firstIndex = firstIndex;
function first(array, fn, notFoundValue = null) {
const index = firstIndex(array, fn);
return index < 0 ? notFoundValue : array[index];
}
exports.first = first;
function commonPrefixLength(one, other, equals = (a, b) => a === b) {
let result = 0;
for (let i = 0, len = Math.min(one.length, other.length); i < len && equals(one[i], other[i]); i++) {
result++;
}
return result;
}
exports.commonPrefixLength = commonPrefixLength;
function flatten(arr) {
return arr.reduce((r, v) => r.concat(v), []);
}
exports.flatten = flatten;
function range(to, from = 0) {
const result = [];
for (let i = from; i < to; i++) {
result.push(i);
}
return result;
}
exports.range = range;
function fill(num, valueFn, arr = []) {
for (let i = 0; i < num; i++) {
arr[i] = valueFn();
}
return arr;
}
exports.fill = fill;
function index(array, indexer, merger = t => t) {
return array.reduce((r, t) => {
const key = indexer(t);
r[key] = merger(t, r[key]);
return r;
}, Object.create(null));
}
exports.index = index;
/**
* Inserts an element into an array. Returns a function which, when
* called, will remove that element from the array.
*/
function insert(array, element) {
array.push(element);
return () => {
const index = array.indexOf(element);
if (index > -1) {
array.splice(index, 1);
}
};
}
exports.insert = insert;
//# sourceMappingURL=collections.js.map