arangojs
Version:
The official ArangoDB JavaScript driver.
599 lines • 18 kB
JavaScript
/**!
* x3-linkedlist
*
* MIT License
*
* Copyright (c) 2019 Benno Dreißig
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
export class LinkedListItem {
value;
unlinkCleanup;
/**
* Item behind this item
* ```
* A -> ThisItem -> C
* ^
* ```
*/
behind;
/**
* Item before this item
* ```
* A -> ThisItem -> C
* ^
* ```
*/
before;
constructor(
/**
* Value of this item
*/
value,
/**
*Function to run on unlink() call. Usually used by LinkedList to fix first and last pointers and reduce length.
*/
unlinkCleanup) {
this.value = value;
this.unlinkCleanup = unlinkCleanup;
}
/**
* This will link given LinkListItem behind this item.
* If there's already a LinkedListItem linked behind, it will be relinked accordingly
*/
insertBehind(
/** LinkListItem to be inserted behind this one */
item) {
item.insertBefore(this);
if (this.behind) {
let itemChainEnd = item;
while (itemChainEnd.behind)
itemChainEnd = itemChainEnd.behind;
this.behind.insertBefore(itemChainEnd);
itemChainEnd.insertBehind(this.behind);
}
this.behind = item;
}
/**
* Unlinks this LinkedListItem and calls unlinkCleanup
* @see LinkedListItem#unlinkCleanup
*/
unlink(
/** If true, additionally removes the reference to the item before and behind */
unchain = false) {
if (this.before)
this.before.behind = this.behind;
if (this.behind) {
this.behind.before = this.before;
}
if (this.unlinkCleanup) {
this.unlinkCleanup(this);
}
this.unlinkCleanup = undefined;
if (unchain) {
this.before = this.behind = undefined;
}
}
/**
* Item given will be inserted before this item.
* unlinkCleanup will be copied if neccessary.
* This function is protected, because LinkedListItem's can only be attached behind.
* @see insertBehind
*/
insertBefore(
/** LinkListItem to be inserted before this one */
before) {
this.before = before;
if (!this.unlinkCleanup) {
this.unlinkCleanup = before.unlinkCleanup;
}
}
}
/**
* Implements a linked list structure
* @typeparam T - Type of values within this LinkedList
*/
export class LinkedList {
/**
* First item in list
*/
first;
/**
* Last item in list
*/
last;
/**
* Current length of this LinkedList.
* Note that this does not work anymore if you for some reason add your own LinkedListItems to LinkedList by hand
*/
length = 0;
constructor(
/** Values to be added initially into list */
values) {
if (values) {
if (values instanceof LinkedList)
values = values.values();
for (const value of values) {
this.push(value);
}
}
}
/**
* Clears this LinkedList.
* The default complexity is O(1), because it only removes links to the first and last item and resets the length.
* Note that if any LinkedListItem is still referenced outside the LinkedList, their before and behind fields might
* still reference the chain, not freeing space.
*
* You can set the unchain parameter to true, so every item in the linked list will be unchained,
* meaning all references to before and behind items will be removed.
* This increases complexity to O(n), but removes accidental outside references to the full chain.
*/
clear(
/** If `true`, remove link info from every item. Changes complexity to O(n)! */
unchain = false) {
if (unchain) {
while (this.first) {
this.first.unlink(true);
}
}
this.first = this.last = undefined;
this.length = 0;
}
/**
* As Array#every() given callback is called for every element until one call returns falsy or all elements had been processed
* @returns `false` if there was a falsy response from the callback, `true` if all elements have been processed "falselesly"
* @see Array#every
*/
every(
/** Runs for every item in the LinkedList */
callback,
/** If given, callback function will be bound to thisArg */
thisArg) {
if (thisArg) {
callback = callback.bind(thisArg);
}
for (const item of this.keys()) {
if (!callback(item.value, item, this)) {
return false;
}
}
return true;
}
/**
* Filters values into a new LinkedList
* @see Array#filter
*/
filter(
/** decides wether given element should be part of new LinkedList */
callback,
/** If given, callback function will be bound to thisArg */
thisArg) {
if (thisArg) {
callback = callback.bind(thisArg);
}
const newList = new LinkedList();
for (const [item, value] of this) {
if (callback(value, item, this)) {
newList.push(value);
}
}
return newList;
}
/**
* Returns value for which given callback returns truthy
* @see Array#find
*/
find(
/** runs for every value in LinkedList. If it returns truthy, current value is returned. */
callback,
/** If given, callback function will be bound to thisArg */
thisArg) {
if (thisArg) {
callback = callback.bind(thisArg);
}
for (const [item, value] of this) {
if (callback(value, item, this)) {
return value;
}
}
return undefined;
}
/**
* Returns the LinkedListItem for which given callback returns truthy
* @see Array#findIndex
*/
findItem(
/** runs for every LinkedListItem in LinkedList. If it returns truthy, current LinkedListItem is returned. */
callback,
/** If given, callback function will be bound to thisArg */
thisArg) {
if (thisArg) {
callback = callback.bind(thisArg);
}
for (const [item, value] of this) {
if (callback(value, item, this)) {
return item;
}
}
return undefined;
}
/**
* Iterates this LinkedList's items and values
* @see Array#forEach
*/
forEach(
/** Gets every value in LinkedList once with corresponding LinkedListItem and LinkedList */
callback,
/** If given, callback function will be bound to thisArg */
thisArg) {
if (thisArg) {
callback = callback.bind(thisArg);
}
for (const [item, value] of this) {
callback(value, item, this);
}
}
/**
* Checks if value can be found within LinkedList, starting from fromIndex, if given.
* @returns true if value could be found in LinkedList (respecting fromIndex), false otherwhise
* @see Array#includes
*/
includes(
/** value to be found in this */
value,
/** Starting index. Supports negative values for which `this.size - 1 + fromIndex` will be used as starting point. */
fromIndex = 0) {
let current = this.getItemByIndex(fromIndex);
while (current) {
if (current.value === value) {
return true;
}
current = current.behind;
}
return false;
}
/**
* Searches forward for given value and returns the first corresponding LinkedListItem found
* @see Array#indexOf
*/
itemOf(
/** Value to be found */
searchedValue,
/** Index to start from */
fromIndex = 0) {
let current = this.getItemByIndex(fromIndex);
while (current) {
if (current.value === searchedValue) {
return current;
}
current = current.behind;
}
return;
}
/**
* Searches backwards for given value and returns the first corresponding LinkedListItem found
* @see Array#indexOf
*/
lastItemOf(
/** Value to be found */
searchedValue,
/** Index to start from */
fromIndex = -1) {
let current = this.getItemByIndex(fromIndex);
while (current) {
if (current.value === searchedValue) {
return current;
}
current = current.before;
}
return;
}
/**
* Creates a new LinkedList with each of its itesm representing the output of the callback with each item in current LinkedList.
* @see Array#map
*/
map(
/** Gets value, LinkedListeItem and LinkedList. The response will be used as value in the new LinkedList */
callback,
/** If given, callback function will be bound to thisArg */
thisArg) {
if (thisArg) {
callback = callback.bind(thisArg);
}
const newList = new LinkedList();
for (const [item, value] of this) {
newList.push(callback(value, item, this));
}
return newList;
}
reduce(callback, initialValue) {
let current = this.first;
if (!current) {
if (!initialValue) {
throw new TypeError("Empty accumulator on empty LinkedList is not allowed.");
}
return initialValue;
}
if (initialValue === undefined) {
initialValue = current.value;
if (!current.behind) {
return initialValue;
}
current = current.behind;
}
do {
initialValue = callback(initialValue, current.value, current, this);
current = current.behind;
} while (current);
return initialValue;
}
reduceRight(callback, initialValue) {
let current = this.last;
if (!current) {
if (!initialValue) {
throw new TypeError("Empty accumulator on empty LinkedList is not allowed.");
}
return initialValue;
}
if (initialValue === undefined) {
initialValue = current.value;
if (!current.before) {
return initialValue;
}
current = current.before;
}
do {
initialValue = callback(initialValue, current.value, current, this);
current = current.before;
} while (current);
return initialValue;
}
/**
* Runs callback for every entry and returns true immediately if call of callback returns truthy.
* @returns `true` once a callback call returns truthy, `false` if none returned truthy.
*/
some(
/** called for every element. If response is truthy, this currentvalue will be returned by `.some()`. */
callback,
/** If given, callback function will be bound to thisArg */
thisArg) {
if (thisArg) {
callback = callback.bind(thisArg);
}
for (const [item, value] of this) {
if (callback(value, item, this)) {
return true;
}
}
return false;
}
/**
* Joins values within this by given separator. Uses Array#join directly.
* @see Array#join
*/
join(
/** separator between items in the resulting string */
separator) {
return [...this.values()].join(separator);
}
/**
* Concats given values and returns a new LinkedList with all given values.
* If LinkedList's are given, they will be spread.
* @see Array#concat
*/
concat(
/** Other values or lists to be concat'ed together */
...others) {
const newList = new LinkedList(this);
for (const other of others) {
if (other instanceof LinkedList) {
newList.push(...other.values());
}
else {
newList.push(other);
}
}
return newList;
}
/**
* Removes the last LinkedListItem and returns its inner value
*/
pop() {
if (!this.last) {
return;
}
const item = this.last;
item.unlink();
return item.value;
}
/**
* Adds given values on the end of this LinkedList
*/
push(
/** Values to be added */
...values) {
for (const value of values) {
const item = new LinkedListItem(value, this.unlinkCleanup);
if (!this.first || !this.last) {
this.first = this.last = item;
}
else {
this.last.insertBehind(item);
this.last = item;
}
this.length++;
}
return this.length;
}
/**
* Adds given values to the beginning of this LinkedList
*/
unshift(
/** Values to be added */
...values) {
for (const value of values) {
const item = new LinkedListItem(value, this.unlinkCleanup);
if (!this.last || !this.first) {
this.first = this.last = item;
}
else {
item.insertBehind(this.first);
this.first = item;
}
this.length++;
}
return this.length;
}
/**
* Removes first occurrence of value found.
*/
remove(
/** value to remove once */
value) {
for (const item of this.keys()) {
if (item.value === value) {
item.unlink();
return true;
}
}
return false;
}
/**
* Removes every occurrance of value within this.
*/
removeAllOccurrences(
/** value to remove completely */
value) {
let foundSomethingToDelete = false;
for (const item of this.keys()) {
if (item.value === value) {
item.unlink();
foundSomethingToDelete = true;
}
}
return foundSomethingToDelete;
}
/**
* Returns and removes first element from LinkedList
*/
shift() {
if (!this.first) {
return;
}
const item = this.first;
item.unlink();
return item.value;
}
/**
* Returns LinkedListItem and value for every entry of this LinkedList
*/
*[Symbol.iterator]() {
let current = this.first;
if (!current) {
return;
}
do {
yield [current, current.value];
current = current.behind;
} while (current);
}
/**
* Returns LinkedListItem and value for every entry of this LinkedList
* @see LinkedList#Symbol.iterator
*/
entries() {
return this[Symbol.iterator]();
}
/**
* Iterates the LinkedListItem's of this LinkedList
*/
*keys() {
let current = this.first;
if (!current) {
return;
}
do {
yield current;
current = current.behind;
} while (current);
}
/**
* Returns a value for every entry of this LinkedList
*/
*values() {
let current = this.first;
if (!current) {
return;
}
do {
yield current.value;
current = current.behind;
} while (current);
}
/**
* Returns the item by given index.
* Supports negative values and will return the item at `LinkedList.size - 1 + index` in that case.
*/
getItemByIndex(
/** Index of item to get from list */
index) {
if (index === undefined) {
throw new Error("index must be a number!");
}
if (!this.first) {
return;
}
let current;
if (index > 0) {
current = this.first;
while (current && index--) {
current = current.behind;
}
}
else if (index < 0) {
current = this.last;
while (current && ++index) {
current = current.before;
}
}
else {
return this.first;
}
return current;
}
/**
* Given to own LinkedListItem's for following jobs regarding an unlink:
* - If item is first item, set the next item as first item
* - If item is last item, set the previous item as last item
* - Decrease length
*/
unlinkCleanup = (
/** Item that has been unlinked */
item) => {
if (this.first === item) {
this.first = this.first.behind;
}
if (this.last === item) {
this.last = this.last.before;
}
this.length--;
};
}
//# sourceMappingURL=linkedList.js.map