Files
asciidisco.com/build/node_modules/dbly-linked-list/index.js
2023-08-01 13:49:46 +02:00

475 lines
15 KiB
JavaScript

/**
* @fileOverview Implementation of a doubly linked-list data structure
* @author Jason S. Jones
* @license MIT
*/
(function() {
'use strict';
var isEqual = require('lodash.isequal');
var Node = require('./lib/list-node');
var Iterator = require('./lib/iterator');
/**************************************************
* Doubly linked list class
*
* Implementation of a doubly linked list data structure. This
* implementation provides the general functionality of adding nodes to
* the front or back of the list, as well as removing node from the front
* or back. This functionality enables this implemention to be the
* underlying data structure for the more specific stack or queue data
* structure.
*
***************************************************/
/**
* Creates a LinkedList instance. Each instance has a head node, a tail
* node and a size, which represents the number of nodes in the list.
*
* @constructor
*/
function DoublyLinkedList() {
this.head = null;
this.tail = null;
this.size = 0;
// add iterator as a property of this list to share the same
// iterator instance with all other methods that may require
// its use. Note: be sure to call this.iterator.reset() to
// reset this iterator to point the head of the list.
this.iterator = new Iterator(this);
}
/* Functions attached to the Linked-list prototype. All linked-list
* instances will share these methods, meaning there will NOT be copies
* made for each instance. This will be a huge memory savings since there
* may be several different linked lists.
*/
DoublyLinkedList.prototype = {
/**
* Creates a new Node object with 'data' assigned to the node's data
* property
*
* @param {object|string|number} data The data to initialize with the
* node
* @returns {object} Node object intialized with 'data'
*/
createNewNode: function(data) {
return new Node(data);
},
/**
* Returns the first node in the list, commonly referred to as the
* 'head' node
*
* @returns {object} the head node of the list
*/
getHeadNode: function() {
return this.head;
},
/**
* Returns the last node in the list, commonly referred to as the
* 'tail'node
*
* @returns {object} the tail node of the list
*/
getTailNode: function() {
return this.tail;
},
/**
* Determines if the list is empty
*
* @returns {boolean} true if the list is empty, false otherwise
*/
isEmpty: function() {
return (this.size === 0);
},
/**
* Returns the size of the list, or number of nodes
*
* @returns {number} the number of nodes in the list
*/
getSize: function() {
return this.size;
},
/**
* Clears the list of all nodes/data
*/
clear: function () {
while (!this.isEmpty()) {
this.remove();
}
},
//################## INSERT methods ####################
/**
* Inserts a node with the provided data to the end of the list
*
* @param {object|string|number} data The data to initialize with the
* node
* @returns {boolean} true if insert operation was successful
*/
insert: function(data) {
var newNode = this.createNewNode(data);
if (this.isEmpty()) {
this.head = this.tail = newNode;
} else {
this.tail.next = newNode;
newNode.prev = this.tail;
this.tail = newNode;
}
this.size += 1;
return true;
},
/**
* Inserts a node with the provided data to the front of the list
*
* @param {object|string|number} data The data to initialize with the
* node
* @returns {boolean} true if insert operation was successful
*/
insertFirst: function(data) {
if (this.isEmpty()) {
this.insert(data);
} else {
var newNode = this.createNewNode(data);
newNode.next = this.head;
this.head.prev = newNode;
this.head = newNode;
this.size += 1;
}
return true;
},
/**
* Inserts a node with the provided data at the index indicated.
*
* @param {number} index The index in the list to insert the new node
* @param {object|string|number} data The data to initialize with the node
*/
insertAt: function (index, data) {
var current = this.getHeadNode(),
newNode = this.createNewNode(data),
position = 0;
// check for index out-of-bounds
if (index < 0 || index > this.getSize() - 1) {
return false;
}
// if index is 0, we just need to insert the first node
if (index === 0) {
this.insertFirst(data);
return true;
}
while (position < index) {
current = current.next;
position += 1;
}
current.prev.next = newNode;
newNode.prev = current.prev;
current.prev = newNode;
newNode.next = current;
this.size += 1;
return true;
},
/**
* Inserts a node before the first node containing the provided data
*
* @param {object|string|number} nodeData The data of the node to
* find to insert the new node before
* @param {object|string|number} dataToInsert The data to initialize with the node
* @returns {boolean} true if insert operation was successful
*/
insertBefore: function (nodeData, dataToInsert) {
var index = this.indexOf(nodeData);
return this.insertAt(index, dataToInsert);
},
/**
* Inserts a node after the first node containing the provided data
*
* @param {object|string|number} nodeData The data of the node to
* find to insert the new node after
* @param {object|string|number} dataToInsert The data to initialize with the node
* @returns {boolean} true if insert operation was successful
*/
insertAfter: function (nodeData, dataToInsert) {
var index = this.indexOf(nodeData);
var size = this.getSize();
// check if we want to insert new node after the tail node
if (index + 1 === size) {
// if so, call insert, which will append to the end by default
return this.insert(dataToInsert);
} else {
// otherwise, increment the index and insert there
return this.insertAt(index + 1, dataToInsert);
}
},
//################## REMOVE methods ####################
/**
* Removes the tail node from the list
*
* There is a significant performance improvement with the operation
* over its singly linked list counterpart. The mere fact of having
* a reference to the previous node improves this operation from O(n)
* (in the case of singly linked list) to O(1).
*
* @returns the node that was removed
*/
remove: function() {
if (this.isEmpty()) {
return null;
}
// get handle for the tail node
var nodeToRemove = this.getTailNode();
// if there is only one node in the list, set head and tail
// properties to null
if (this.getSize() === 1) {
this.head = null;
this.tail = null;
// more than one node in the list
} else {
this.tail = this.getTailNode().prev;
this.tail.next = null;
}
this.size -= 1;
return nodeToRemove;
},
/**
* Removes the head node from the list
*
* @returns the node that was removed
*/
removeFirst: function() {
if (this.isEmpty()) {
return null;
}
var nodeToRemove;
if (this.getSize() === 1) {
nodeToRemove = this.remove();
} else {
nodeToRemove = this.getHeadNode();
this.head = this.head.next;
this.head.prev = null;
this.size -= 1;
}
return nodeToRemove;
},
/**
* Removes the node at the index provided
*
* @param {number} index The index of the node to remove
* @returns the node that was removed
*/
removeAt: function (index) {
var nodeToRemove = this.findAt(index);
// check for index out-of-bounds
if (index < 0 || index > this.getSize() - 1) {
return null;
}
// if index is 0, we just need to remove the first node
if (index === 0) {
return this.removeFirst();
}
// if index is size-1, we just need to remove the last node,
// which remove() does by default
if (index === this.getSize() - 1) {
return this.remove();
}
nodeToRemove.prev.next = nodeToRemove.next;
nodeToRemove.next.prev = nodeToRemove.prev;
nodeToRemove.next = nodeToRemove.prev = null;
this.size -= 1;
return nodeToRemove;
},
/**
* Removes the first node that contains the data provided
*
* @param {object|string|number} nodeData The data of the node to remove
* @returns the node that was removed
*/
removeNode: function (nodeData) {
var index = this.indexOf(nodeData);
return this.removeAt(index);
},
//################## FIND methods ####################
/**
* Returns the index of the first node containing the provided data. If
* a node cannot be found containing the provided data, -1 is returned.
*
* @param {object|string|number} nodeData The data of the node to find
* @returns the index of the node if found, -1 otherwise
*/
indexOf: function(nodeData) {
this.iterator.reset();
var current;
var index = 0;
// iterate over the list (keeping track of the index value) until
// we find the node containg the nodeData we are looking for
while (this.iterator.hasNext()) {
current = this.iterator.next();
if (isEqual(current.getData(), nodeData)) {
return index;
}
index += 1;
}
// only get here if we didn't find a node containing the nodeData
return -1;
},
/**
* Returns the fist node containing the provided data. If a node
* cannot be found containing the provided data, -1 is returned.
*
* @param {object|string|number} nodeData The data of the node to find
* @returns the node if found, -1 otherwise
*/
find: function(nodeData) {
// start at the head of the list
this.iterator.reset();
var current;
// iterate over the list until we find the node containing the data
// we are looking for
while (this.iterator.hasNext()) {
current = this.iterator.next();
if (isEqual(current.getData(), nodeData)) {
return current;
}
}
// only get here if we didn't find a node containing the nodeData
return -1;
},
/**
* Returns the node at the location provided by index
*
* @param {number} index The index of the node to return
* @returns the node located at the index provided.
*/
findAt: function(index) {
// if idx is out of bounds or fn called on empty list, return -1
if (this.isEmpty() || index > this.getSize() - 1) {
return -1;
}
// else, loop through the list and return the node in the
// position provided by idx. Assume zero-based positions.
var node = this.getHeadNode();
var position = 0;
while (position < index) {
node = node.next;
position += 1;
}
return node;
},
/**
* Determines whether or not the list contains the provided nodeData
*
* @param {object|string|number} nodeData The data to check if the list
* contains
* @returns the true if the list contains nodeData, false otherwise
*/
contains: function (nodeData) {
if (this.indexOf(nodeData) > -1) {
return true;
} else {
return false;
}
},
//################## UTILITY methods ####################
/**
* Utility function to iterate over the list and call the fn provided
* on each node, or element, of the list
*
* @param {object} fn The function to call on each node of the list
* @param {bool} reverse Use or not reverse iteration (tail to head), default to false
*/
forEach: function(fn, reverse) {
reverse = reverse || false;
if (reverse) {
this.iterator.reset_reverse();
this.iterator.each_reverse(fn)
} else {
this.iterator.reset();
this.iterator.each(fn);
}
},
/**
* Returns an array of all the data contained in the list
*
* @returns {array} the array of all the data from the list
*/
toArray: function() {
var listArray = [];
this.forEach(function(node) {
listArray.push(node.getData());
});
return listArray;
},
/**
* Interrupts iteration over the list
*/
interruptEnumeration: function() {
this.iterator.interrupt();
}
};
module.exports = DoublyLinkedList;
}());