sangja
Version:
JavaScript data structures library
697 lines (624 loc) • 17 kB
JavaScript
const Utils = require('./utils');
/**
* @class
* @memberof sangja
*/
class LinkedList {
/**
* Creates a new LinkedList.
* Iterable parameter is optional.
* @constructor
* @param {iterable} [iterable] - Iterator for initialize the list.
* @throws {TypeError} When given parameter is not iterable.
*/
constructor(iterable = []) {
this._linkedList = {
size: 0,
front: null,
end: null,
};
if (!Utils.isIterable(iterable)) {
throw TypeError();
}
[...iterable].forEach(v => this.addLast(v));
}
/**
* Return the node at the given index of the linked list.
* @param {number} index - The index to get node.
* @private
* @return {node} The node at the given index.
* When the given index < 0 or index >= size of the linked list, return undefined.
*/
_getNode(index) {
if (typeof index !== 'number' || index < 0 || index >= this._linkedList.size) {
return undefined;
}
if (index < this._linkedList.size / 2) {
let now = this._linkedList.front;
for (let i = 0; i < index; i += 1) {
now = now.next;
}
return now;
}
let now = this._linkedList.end;
for (let i = this._linkedList.size - 1; i > index; i -= 1) {
now = now.prev;
}
return now;
}
/**
* Add value at the given index of the linked list.
* @param {number} [index] - The index to add the value.
* @param {*} value - The value to add.
* @throws {RangeError} When the given index < 0 or index > size of the linked list.
*/
add(index, value) {
// Index not given. index is the value to add.
if (value === undefined) {
this.addLast(index);
return;
}
if (typeof index !== 'number' || index < 0 || index > this._linkedList.size) {
throw RangeError();
}
if (index === 0) {
const item = {
prev: null,
next: this._linkedList.front,
value,
};
if (this._linkedList.front) {
this._linkedList.front.prev = item;
} else {
// No elements until now.
this._linkedList.end = item;
}
this._linkedList.front = item;
} else if (index === this._linkedList.size) {
const item = {
prev: this._linkedList.end,
next: null,
value,
};
if (this._linkedList.end) {
this._linkedList.end.next = item;
}
this._linkedList.end = item;
} else {
const original = this._getNode(index);
const item = {
prev: original.prev,
next: original,
value,
};
item.prev.next = item;
item.next.prev = item;
}
this._linkedList.size += 1;
}
/**
* Add values in given iterator at the given index of the linked list.
* @param {number} [index] - The index to add the value.
* @param {iterable} iterable - The iterable object that contain values to add.
* @throws {RangeError} When the given index < 0 or index > size of the linked list.
*/
addAll(index, iterable) {
// Index not given. index is iterable object.
if (iterable === undefined) {
this.addAllLast(index);
return;
}
const values = [...iterable];
for (let i = values.length - 1; i >= 0; i -= 1) {
this.add(index, values[i]);
}
}
/**
* Add value at the front of the linked list.
* @param {*} value - The value to add.
*/
addFirst(value) {
this.add(0, value);
}
/**
* Add values in given iterator at the front of the linked list.
* @param {iterable} iterable - The iterable object that contain values to add.
*/
addAllFirst(iterable) {
const values = [...iterable];
for (let i = values.length - 1; i >= 0; i -= 1) {
this.add(0, values[i]);
}
}
/**
* Add value at the end of the linked list.
* @param {*} value - The value to add.
*/
addLast(value) {
this.add(this.size(), value);
}
/**
* Add values in given iterator at the end of the linked list.
* @param {iterable} iterable - The iterable object that contain values to add.
*/
addAllLast(iterable) {
const values = [...iterable];
for (let i = 0; i < values.length; i += 1) {
this.addLast(values[i]);
}
}
/**
* Removes the given index of the linked list and
* returns the value at the given index of the linked list.
* @param {number} [index] - The index to remove the value.
* @return {*} The value at the given index of the linked list. If not found, return undefined.
*/
pop(index) {
if (index === undefined) {
return this.popLast();
}
if (typeof index !== 'number' || index < 0 || index >= this._linkedList.size) {
return undefined;
}
const now = this._getNode(index);
if (now.prev) {
now.prev.next = now.next;
} else {
this._linkedList.front = now.next;
}
if (now.next) {
now.next.prev = now.prev;
} else {
this._linkedList.end = now.prev;
}
this._linkedList.size -= 1;
return now.value;
}
/**
* Removes the first of the linked list and
* returns the value at the first of the linked list.
* @return {*} The value at the front of the linked list. If empty, return undefined.
*/
popFirst() {
return this.pop(0);
}
/**
* Removes the end of the linked list and
* returns the value at the end of the linked list.
* @return {*} The value at the end of the linked list. If empty, return undefined.
*/
popLast() {
return this.pop(this.size() - 1);
}
/**
* Same with pop(i) except that index is required.
* @param {number} index - The index to remove the value.
* @return {(*|undefined)} The value at the given index of the linked list.
* If not found, return undefined.
*/
removeAt(index) {
if (typeof index !== 'number' || index < 0 || index >= this._linkedList.size) {
return undefined;
}
return this.pop(index);
}
/**
* Removes the first occurance of the given value in the linked list and
* returns true if the given value is in the linked list.
* @param {*} value - The value to remove
* @return {boolean} True if the given value is in the linked list else false.
*/
remove(value) {
let now = this._linkedList.front;
while (now) {
if (now.value === value) {
break;
}
now = now.next;
}
if (!now) {
return false;
}
if (now.prev) {
now.prev.next = now.next;
} else {
// now is front
this._linkedList.front = now.next;
}
if (now.next) {
now.next.prev = now.prev;
} else {
// now is end
this._linkedList.end = now.prev;
}
this._linkedList.size -= 1;
return true;
}
/**
* Removes the every occurance of the given value in the linked list and
* returns the number of removed values.
* @param {*} value - The value to remove
* @return {number} The number of removed values.
*/
removeAll(value) {
let now = this._linkedList.front;
let result = 0;
while (now) {
if (now.value === value) {
if (now.prev) {
now.prev.next = now.next;
} else {
// now is front
this._linkedList.front = now.next;
}
if (now.next) {
now.next.prev = now.prev;
} else {
// now is end
this._linkedList.end = now.prev;
}
this._linkedList.size -= 1;
result += 1;
}
now = now.next;
}
return result;
}
/**
* Same with remove(value) if value is given.
* If value is not given, same with popFirst().
* @param {*} [value] - The value to remove
* @return {(*|undefined|boolean)} If remove success, return true.
* If value not given, return popFirst().
*/
removeFirst(value) {
if (value === undefined) {
return this.popFirst();
}
return this.remove(value);
}
/**
* Same with remove(value) but find from start searching from last.
* @param {*} [value] - The value to remove
* @return {(*|undefined|boolean)} If remove success, return true.
* If value not given, return popLast().
*/
removeLast(value) {
let now = this._linkedList.end;
while (now) {
if (now.value === value) {
break;
}
now = now.prev;
}
if (!now) {
return false;
}
if (now.prev) {
now.prev.next = now.next;
} else {
// now is front
this._linkedList.front = now.next;
}
if (now.next) {
now.next.prev = now.prev;
} else {
// now is end
this._linkedList.end = now.prev;
}
this._linkedList.size -= 1;
return true;
}
/**
* Removes the first value that matches given predicate f and
* returns value in the linked list.
* @param {Function} f - Predicate
* @return {(*|undefined)} Found value. If not found, return undefined.
* @throws {TypeError} When the given predicate is not a function.
*/
removeMatch(f) {
if (typeof f !== 'function') {
throw TypeError();
}
let now = this._linkedList.front;
while (now) {
if (f(now.value)) {
break;
}
now = now.next;
}
if (!now) {
return undefined;
}
if (now.prev) {
now.prev.next = now.next;
} else {
// now is front
this._linkedList.front = now.next;
}
if (now.next) {
now.next.prev = now.prev;
} else {
// now is end
this._linkedList.end = now.prev;
}
this._linkedList.size -= 1;
return now.value;
}
/**
* Removes all values that matches given predicate f and
* returns the removed values.
* @param {Function} f - Predicate
* @return {any[]} Removed values.
* @throws {TypeError} When the given predicate is not a function.
*/
removeMatchAll(f) {
if (typeof f !== 'function') {
throw TypeError();
}
let now = this._linkedList.front;
const result = [];
while (now) {
if (f(now.value)) {
if (now.prev) {
now.prev.next = now.next;
} else {
// now is front
this._linkedList.front = now.next;
}
if (now.next) {
now.next.prev = now.prev;
} else {
// now is end
this._linkedList.end = now.prev;
}
this._linkedList.size -= 1;
result.push(now.value);
}
now = now.next;
}
return result;
}
/**
* Same with removeMatch(f)
* @param {Function} f - Predicate
* @return {(*|undefined)} Found value. If not found, return undefined.
* @throws {TypeError} When the given predicate is not a function.
*/
removeMatchFirst(f) {
return this.removeMatch(f);
}
/**
* Same with removeMatch(f) but find from start searching from last.
* @param {Function} f - Predicate
* @return {(*|undefined)} Found value. If not found, return undefined.
* @throws {TypeError} When the given predicate is not a function.
*/
removeMatchLast(f) {
if (typeof f !== 'function') {
throw TypeError();
}
let now = this._linkedList.end;
while (now) {
if (f(now.value)) {
break;
}
now = now.prev;
}
if (!now) {
return undefined;
}
if (now.prev) {
now.prev.next = now.next;
} else {
// now is front
this._linkedList.front = now.next;
}
if (now.next) {
now.next.prev = now.prev;
} else {
// now is end
this._linkedList.end = now.prev;
}
this._linkedList.size -= 1;
return now.value;
}
/**
* Removes all values in the linked list.
*/
clear() {
this._linkedList = {
size: 0,
front: null,
end: null,
};
}
/**
* Returns the value at the given index of the linked list.
* @param {number} index - The index to get the value.
* @return {*} The value at the given index of the linked list.
* When the given index < 0 or index >= size of the linked list, return undefined.
*/
get(index) {
if (typeof index !== 'number' || index < 0 || index >= this._linkedList.size) {
return undefined;
}
const now = this._getNode(index);
return now.value;
}
/**
* Returns the value at the first of the linked list.
* @return {*} The value at the front of the linked list. If empty, return undefined.
*/
getFirst() {
return this.get(0);
}
/**
* Returns the value at the end of the linked list.
* @return {*} The value at the end of the linked list. If empty, return undefined.
*/
getLast() {
return this.get(this.size() - 1);
}
/**
* Updates the value at the given index of the linked list and
* returns the value at the given index of the linked list.
* @param {number} index - The index to update the value.
* @param {*} value - The value to update.
* @throws {RangeError} When the given index < 0 or index >= size of the linked list.
* @return {*} The value at the given index of the linked list.
*/
set(index, value) {
if (typeof index !== 'number' || index < 0 || index >= this._linkedList.size) {
throw RangeError();
}
const now = this._getNode(index);
const ret = now.value;
now.value = value;
return ret;
}
/**
* Returns the value of the first element in the linked list
* that satisfies the provided testing function.
* @param {function} f - Testing function.
* @return {*} The the first value in the linked list
* that satisfies the provided testing function.
* When no element in the linked list satisfies the provided testing function, return undefined.
*/
find(f) {
let now = this._linkedList.front;
while (now && !f(now.value)) {
now = now.next;
}
if (now) {
return now.value;
}
return undefined;
}
/**
* Returns the number of elements in the linked list.
* @return {number} The number of elements in the linked list.
*/
size() {
return this._linkedList.size;
}
/**
* Returns whether the linked list is empty.
* @return {boolean} True if the linked list is empty.
*/
isEmpty() {
return this._linkedList.size === 0;
}
/**
* For each values in the list, execute the given procedure f.
* @param {function} f - Procedure to execute
*/
forEach(f) {
let now = this._linkedList.front;
while (now) {
f(now.value);
now = now.next;
}
}
/**
* Returns a new LinkedList whose values are mapped with given function f.
* @param {function} f - Function to map values
* @return {LinkedList} LinkedList([mapped values])
*/
map(f) {
const list = new LinkedList();
this.forEach(v => list.addLast(f(v)));
return list;
}
/**
* Returns a new LinkedList whose values are mapped with given function f and flattened.
* @param {function} f - Function (this.value) => iterable
* @return {LinkedList} LinkedList([mapped and flattened values])
*/
flatMap(f) {
const list = new LinkedList();
this.forEach(v => list.addAllLast([...f(v)]));
return list;
}
/**
* Returns a new LinkedList whose values are filtered with given predicate f.
* @param {function} f - Predicate (this.value) => boolean
* @return {LinkedList} LinkedList([filtered values])
*/
filter(f) {
const list = new LinkedList();
this.forEach((v) => {
if (f(v)) {
list.addLast(v);
}
});
return list;
}
/**
* Returns a new LinkedList whose values are reversed in order.
* @return {LinkedList} LinkedList([reversed values])
*/
reversed() {
const list = new LinkedList();
this.forEach(v => list.addFirst(v));
return list;
}
/**
* If any of containing values satisfies given f, return true.
* If none of values satisfy f or not contain any value, return false.
* @param {function} f - Predicate
* @return {boolean}
*/
some(f) {
if (this.size() === 0) {
return false;
}
let now = this._linkedList.front;
while (now) {
if (f(now.value)) {
return true;
}
now = now.next;
}
return false;
}
/**
* If all of containing values satisfies given f or not contain any value, return true.
* If any of value doesn't satisfy f, return false.
* @param {function} f - Predicate
* @return {boolean}
*/
every(f) {
let now = this._linkedList.front;
while (now) {
if (!f(now.value)) {
return false;
}
now = now.next;
}
return true;
}
/**
* If contains given value v, return true.
* @param {*} v
* @return {boolean}
*/
includes(v) {
let now = this._linkedList.front;
while (now) {
if (now.value === v) {
return true;
}
now = now.next;
}
return false;
}
* [Symbol.iterator]() {
let now = this._linkedList.front;
while (now) {
yield now.value;
now = now.next;
}
}
}
module.exports = LinkedList;