Solution
class Node{
constructor(data){
// a Node starts with a given data property
// a Node also has a .next property initialized as null
this.data = data;
this.next = null;
}
}
class LinkedList{
constructor(){
// a Linked List starts with a "head" property intialized as null
this.head = null;
}
appendNode(data){
// creates a new node with the given data and adds it to back of the list
// let's go ahead and create a node first
let node = new Node(data);
// if the linked list doesn't have any nodes yet, node becomes this.head
if (!this.head) return this.head = node;
// otherwise, we need to step into the list
// intialze a walker to be this.head
let walker = this.head;
// let's keep on walking down the list until there is no walker.next
while (walker.next) {
walker = walker.next;
}
// if walker.next is null, we've reached the end of the linked list
// so add the node to it
return walker.next = node;
}
prependNode(data){
// creates a new node with the given data and adds it to the front of the list
// first make the node
let node = new Node(data);
// if there was no node, we can go ahead and add the node as the head
if (!this.head) return this.head = node;
// otherwise, we need to switch the heads
let oldHead = this.head;
node.next = oldHead;
return this.head = node;
}
pop(){
// removes the last node from the list and returns it
// if there is no node at the front, return it
if (!this.head) return null;
// we have to account for a special case in which the head is the only element
// because in that case, you will have to set this.head to null;
if (!this.head.next) {
let poppedNode = this.head;
this.head = null;
return poppedNode;
}
// if the special cases don't happen, we can do a traversal
let walker = this.head;
// but this time, we want to stop traversing when we reach the second from the end
// this is because singularly linked lists can't look backwards
while (walker.next.next) {
walker = walker.next;
}
// now the popped node would be walker.next
let poppedNode = walker.next;
// delete it by setting walker.next to null
walker.next = null;
// and return the popped node
return poppedNode;
}
removeFromFront(){
// remove the head node from the list and return it
// the next node in the list is the new head node
// if there is no this.head, return null
if (!this.head) return null;
// if there is nothing after this.head, we have to clean up after ourselves
if (!this.head.next) {
let removed = this.head;
this.head = null;
return removed;
}
// now we can do the vanilla case
let removed = this.head;
this.head = removed.next;
return removed;
}
insertAt(X, data){
// insert a new node into the list with the given data
// place it after X nodes in the list
// if X exceeds the bounds of the list, put the node at the end
// insertAt(0, 7) would add the new node as the head
let node = new Node(data);
// if this.head is null, make it the new node
if (!this.head) return this.head = node;
// if they want to insert a 0, node becomes this.head
if (X === 0) {
let next = this.head;
node.next = next;
return this.head = node
}
// otherwise, it's time to traverse;
let walker = this.head;
let iterator = 1;
// keep traversing until you either reach the end,
// or until you hit X
while (walker.next && iterator < X) {
walker = walker.next;
iterator++;
}
// let's take care of the case that you've reached the end first
if (!walker.next) return walker.next = node;
// if we insert, first, let's keep track of what comes after walker
let next = walker.next;
node.next = next;
walker.next = node;
}
removeAt(X){
// remove the Xth node from the list, considering 0 to be the first node
// return the node that has been removed
// as usual, take care of the case where there is no head first
if (!this.head) return null;
// now let's see what to do if we have to remove from the front
if (X === 0) {
// we know this.head has to go away, so let's save a reference to it for now
let removed = this.head;
// this.head will become whatever this.head.next is. It's either a node or null
this.head = removed.next
// then let's return what we got rid of
return removed;
}
// otherwise, we have to traverse
let walker = this.head;
let iterator = 1;
// have we reached the end or where we want to remove from?
while (walker.next.next && iterator < X) {
walker = walker.next;
iterator++;
}
// save a reference to what we will return
let removed = walker.next;
// walker.next will be walker.next.next, which will either be a node or null
walker.next = removed.next;
// then return what was removed
return removed;
}
search(data){
// searches the list for a node with the given data
// if it is found, return the "index" of the node, considering 0 to be the first node
// if not, return false
// if there's nothing at head, return false
if (!this.head) return false;
// we need to return an index, so let's initialize it to 0
let index = 0;
// and let's set up our walker
let walker = this.head;
// let's keep looping while walker is truthy
while (walker) {
// if walker's data matches, return index and call it a day
if (walker.data === data) return index;
// "otherwise", let's increment
walker = walker.next;
index++;
}
// if we've reached the end of the linked list, that means it's not there
return false;
}
sort(){
// sort the Linked List in ascending order of data values
if (!this.head) return null;
// do you remember bubble sorts?
// initialize our checker to say if a swap happened
let swapped = true;
while (swapped) {
// if no swappeds happen, we can exit out of the while loop
// "default" it so no swapped happens
swapped = false;
// let's start traversing
let walker = this.head;
// we need to store what came before walker, as we will need it later
let previous;
// now it's time to do our pretend "for loop"
// we just keep on going until there isn't a next property to swap
while (walker && walker.next) {
// if we need to swap
if (walker.data > walker.next.data) {
// set the swapped to true, because we are about to swap
// this will make the "while(swapped)" loop run again
swapped = true;
// if the head needs to swapped, we have to make separate logic because you have to mess with this.head
if (walker === this.head) {
// this.head is eventually going to become this.head.next
// let's call it newSecond
let newSecond = this.head;
// what was this.head.next will become the new this.head
// let's call it newHead
let newHead = this.head.next;
// now time to make the newSecond's next thing whatever was the "third" next
newSecond.next = this.head.next.next;
// the new head's next property should be newSecond
newHead.next = newSecond;
// and now let's offically make the newHead this.head
this.head = newHead;
} else {
// what if walker wasn't this.head?
// since walker is bigger than whatever comes after it, make it the newSecond
let newSecond = walker;
// whatever came after it is going to become the newFirst
let newFirst = walker.next;
// newSecond's next should be the second thing to come after wallker
// remember, newFirst is walker.next
newSecond.next = newFirst.next;
// newFist's next should be newSecond
newFirst.next = newSecond;
// what came before walker should now point to the newFirst
previous.next = newFirst;
}
}
// now let's do the traversing
// previous becomes walker
previous = walker;
// and walker becomes walker.next
walker = walker.next;
}
}
}
}
module.exports = {
Node,
LinkedList
}