Arranging a connected list of elements

Background:

I have a LinkedList class that organizes and inserts nodes automatically in the correct order. This linked list data structure represents RAM, with the array holding the nodes/elements and the pointers - head, tail, next, and prev representing addresses in RAM (although here they are indexes of the node array).

Example:

myLinkedList.insert(2);
myLinkedList.insert(1);
myLinkedList.output(); // => [{value:2, next:null, prev:1}, {value:1,next:0,prev:null]}, head = 1, tail = 0

When I call my printInOrder function now, it will display 1, then 2, and stop.

Note: Upon inserting a new node, it is added to the end of the array, but the pointers of its neighboring nodes are adjusted (specifically next and prev) to maintain the desired order from head to tail. The position of the inserted node is indicated solely by its pointers.

The Issue at Hand:

My challenge lies within re-ordering the linked list without changing the index of each node; instead only altering their pointers (next and prev) along with the global head and tail variables. How can I utilize a sorting algorithm like merge or bubble sort to rearrange the nodes based on their values while preserving their original order?

My Code Attempt:

Below is my function for re-sorting the linked list using bubble sorting, which currently does not yield the expected results:

class LinkedList {
  constructor(sortingFunction) {
    this.head;
    this.tail;
    this.list = [];
    this.sortingFunction = sortingFunction || ((a, b) => {
      return a < b
    });
  }
  sort(sortingFunction) {
    if (!sortingFunction) {
      return false;
    }
    this.head = null;
    this.tail = null;
    const arr = this.list.map(x => x);
    for (let i = 0; i < arr.length; i++) {
      for (let j = 0; j < arr.length; j++) {
        if (!arr[j + 1]?.value) {
          console.log("no");
          continue;
        }
        if (sortingFunction(arr[j].value, arr[j + 1].value)) {
          let tmp_next = arr[j].next;
          let tmp_prev = arr[j].previous;
          arr[j].next = arr[j + 1].next;
          arr[j].previous = arr[j + 1].previous;
          arr[j + 1].next = tmp_next;
          arr[j + 1].previous = tmp_prev;
        }
      }
    }
    this.list = arr;
  }
}

Furthermore, below you will find the complete code snippet for my LinkedList class, providing a chance to replicate the issue - as the nodes fail to properly sort themselves despite adjustments made to their pointers. The reason behind this anomaly remains elusive to me.

class LinkedList {
   constructor(sortingFunction) {
      this.head;
      this.tail;
      this.list = [];
      this.sortingFunction = sortingFunction ?? ((a,b) => {return a < b});
   }

   some(func) {
      let currentNode = this.list[this.head];
      let index = this.head;
      while(!func(currentNode)) {
         index = currentNode.next;
         currentNode = this.list[index];
         if(index == undefined || index == null) { return -1; }
      }
      return index;
   }
   forEachInOrder(func) {
      let current = this.head;
      while(current != undefined) {
         const node = this.list[current];
         func(node);
         current = node.next;
      }
   }
   * iterator() {
      let current = this.head;
      while(current != undefined) {
         const node = this.list[current];
         yield node;
         current = node.next;
      }
   }
   
   insert(value) {
      if(!this.list.length) {
         this.head = 0;
         this.tail = 0;
         this.list.push({value, next:null,previous:null});
         return 0;
      }
      let nodeToInsert = {value, next:null,previous:null};
      let indexToInsert = this.head;
      let nthnode = this.list[this.head];
      while(nthnode && this.sortingFunction(nthnode.value, value)) {
         indexToInsert = nthnode.next;
         nthnode = this.list[indexToInsert];
      }
      if(indexToInsert === null) {
         // new tail (biggest)
         nodeToInsert.previous = this.tail;
         this.list[this.tail].next = this.list.length;
         this.tail = this.list.length;
      } else if(indexToInsert === this.head) {
         // new head (smallest)
         nodeToInsert.next = this.head;
         this.list[this.head].previous = this.list.length;
         this.head = this.list.length;
      } else {
         nodeToInsert.next = indexToInsert;
         if(this.list[this.list[indexToInsert].previous]?.next != undefined) {
            this.list[this.list[indexToInsert].previous].next = this.list.length;
         }
         nodeToInsert.previous = this.list[indexToInsert].previous;
         this.list[indexToInsert].previous = this.list.length;
      }
      this.list.push(nodeToInsert);
      return 1;
   }

   reverse() {
      let _temp;
      for(let i = 0; i < this.list.length; i++) {
         _temp = this.list[i].next;
         this.list[i].next = this.list[i].previous;
         this.list[i].previous = _temp;
      }
      _temp = this.head;
      this.head = this.tail;
      this.tail = _temp;
   }
   sort(sortingFunction) {
      if(!sortingFunction) {return false;}
      this.head = null;
      this.tail = null;
      const arr = this.list.map(x=>x);
      for (let i = 0; i < arr.length; i++) {
         for (let j = 0; j < arr.length; j++) {
            if(!arr[j+1]?.value) {continue;}
            if (sortingFunction(arr[j].value, arr[j+1].value)) {
               let tmp_next = arr[j].next;
               let tmp_prev = arr[j].previous;
               arr[j].next = arr[j+1].next;
               arr[j].previous = arr[j+1].previous;
               arr[j+1].next = tmp_next;
               arr[j+1].previous = tmp_prev;
            }
         }
      }
      this.list = arr;
   }

   print() {
      console.log(this.list);
      console.log("Head:",this.head,"\nTail:",this.tail, "\nDefault is ascending order.");
   }
   printInOrder() {
      let current = this.list[this.head];
      while(current) {
         console.log(current.value);
         current = this.list[current.next];
      }
   }
}


const list = new LinkedList();

list.insert(100);
list.insert(30);
list.insert(50);
list.insert(400);
list.insert(10);
list.insert(200);
list.insert(-90);



console.log("Nodes sorted upon insertion:")

list.print();

list.sort((a, b) => {
  return a > b;
});

console.log("Now, post-reordering:");

list.print();

Answer №1

There are a few issues with your `sort` function:

  • The inner loop examines pairs that are consecutive by index, but it should actually compare pairs that are consecutive by links, as that is where the decision for a swap should be made.

  • Your code's swap entails 4 link assignments, but in reality, there are 6 links involved:

https://i.sstatic.net/yRm6b.png

  • this.head and this.tail are set to null but not reassigned to their correct values afterwards.

Your code includes a useful `iterator()` method, which could be utilized in your bubble sort; however, to prevent potential disruptions caused by recent swaps, I propose a slight modification to that generator:

   * iterator() {
      let current = this.head;
      while(current != undefined) {
         const node = this.list[current];
         current = node.next; // <--- move this here!
         yield node; // ...for safe alteration of node.next by the consumer
      }
   }

With this adjustment, your `sort` method can now be updated as follows:

   sort(sortingFunction) {
      if (!sortingFunction) return;
      
      for (let i = 0; i < this.list.length; i++) {
         let prevNode = null;
         // Iterate in the current order, following the links:
         for (let currNode of this.iterator()) { // Requires the modification in the generator 
            if (prevNode && sortingFunction(currNode.value, prevNode.value)) {
               // Obtain the indexes of the current node pair:
               let currIndex = prevNode.next;
               let prevIndex = currNode.previous;
               // Update links from outer neighbors to these two nodes
               if (currNode.next != null) this.list[currNode.next].previous = prevIndex;
               else this.tail = prevIndex;
               if (prevNode.previous != null) this.list[prevNode.previous].next = currIndex;
               else this.head = currIndex;
               // Update links from these two nodes to outer neighbors:
               currNode.previous = prevNode.previous;
               prevNode.next = currNode.next;
               // Update links between the two nodes themselves:
               prevNode.previous = currIndex;
               currNode.next = prevIndex;
            } else {
               prevNode = currNode;
            }
         }
      }
   }

I've also used your generator function in new contexts like `forEachInOrder`. Here's the full script:

// Your script goes here

Your `sortingFunction` currently returns a boolean, unlike the comparator function used in native methods like `Array#sort`, which expects a number to indicate order. Consider aligning your implementation with this standard.

Optimizing Sorting Algorithm

Instead of the O(n²) bubble sort, consider using the more efficient O(nlogn) merge sort algorithm.

I've added a `mergeSort` method and a helper function `splice` to partition the list efficiently:

// More functionality added here

Answer №2

Here is a clever and simple solution using the Array#sort() method.

const list = [
  { value: { num: 1, str: 'a' }, next: 3, prev: 2 },
  { value: { num: 2, str: 'e' }, next: 2, prev: null },
  { value: { num: 3, str: 'v' }, next: 0, prev: 1 },
  { value: { num: 4, str: 'g' }, next: null, prev: 0 }];
let head = 1, tail = 3;

function sort(sortingFunction) {
  const sorted = list
    .map((node, idx) => ({ node, idx }))
    .sort(({ node: { value: a } }, { node: { value: b } }) => sortingFunction(a, b));

  for (const [i, { node, idx }] of sorted.entries()) {
    if (!i) {
      node.prev = null;
      head = idx;
    } else node.prev = sorted[i - 1].idx;

    if (i === sorted.length - 1) {
      node.next = null;
      tail = idx;
    } else node.next = sorted[i + 1].idx;

  }
}

const log_list = () => { let node = list[head]; while (node !== undefined) { console.log(node.value); node = list[node.next]; } };

// initial order logging
console.log('initial - ', 'head: ', head, ' tail: ', tail);
log_list();
console.log('list order: ', ...list.map((o) => o.value.num));

// sort in descending order
sort((a, b) => b.num - a.num);

// after sorting log
console.log('\nsorted descending - ', 'head: ', head, ' tail: ', tail);
log_list();
console.log('list order: ', ...list.map((o) => o.value.num));

// sort alphabetically
sort((a, b) => a.str.localeCompare(b.str));

// after sorting log
console.log('\nsorted alphabetic - ', 'head: ', head, ' tail: ', tail);
log_list();
console.log('list order: ', ...list.map((o) => o.value.num));
.as-console-wrapper { max-height: 100% !important; top: 0; }

This approach takes advantage of the optimized sorting algorithm provided by the JavaScript engine (timsort in V8). However, if you prefer to implement your own sorting logic, it may be more efficient to sort within the list itself rather than manipulating the surrounding array.

Your specific requirement of maintaining nodes in order by their values makes this more challenging, as swapping elements will require updating neighboring nodes to ensure the list remains contiguous.

Here is a quick implementation of bubble sort that iterates through the list from head to tail until no more swaps are needed. It accepts a callback function that should return a positive value to indicate that element b should come before element a.

const list = [
  { value: { num: 1, str: 'a' }, next: 3, prev: 2 },
  { value: { num: 2, str: 'e' }, next: 2, prev: null },
  { value: { num: 3, str: 'v' }, next: 0, prev: 1 },
  { value: { num: 4, str: 'g' }, next: null, prev: 0 }];
let head = 1, tail = 3;

function sort(sortingFunction) {
  let flag = true, node = list[head];
  while (flag) {
    flag = false;
    while (node.next !== null) {
      if (sortingFunction(node.value, list[node.next].value) > 0) {
        const prev_node = list[node.prev];
        const _node = list[node.next];
        const next_node = list[_node.next];

        const temp = node.prev;

        if (prev_node) prev_node.next = node.next;
        node.prev = node.next;
        node.next = _node.next;

        if (next_node) next_node.prev = _node.prev;
        _node.next = _node.prev;
        _node.prev = temp;

        if (_node.prev === null) head = node.prev;
        if (node.next === null) tail = _node.next;

        flag = true;
      } else {
        node = list[node.next];
      }
    }
    node = list[head];
  }
}

const log_list = () => { let node = list[head]; while (node !== undefined) { console.log(node.value); node = list[node.next]; } };

// initial order logging
console.log('initial - ', 'head: ', head, ' tail: ', tail);
log_list();
console.log('list order: ', ...list.map((o) => o.value.num));

// sort by number
sort((a, b) => b.num - a.num);

// after sorting log
console.log('\nsorted descending - ', 'head: ', head, ' tail: ', tail);
log_list();
console.log('list order: ', ...list.map((o) => o.value.num));

// sort by string
sort((a, b) => a.str.localeCompare(b.str));

// after sorting log
console.log('\nsorted alphabetic - ', 'head: ', head, ' tail: ', tail);
log_list();
console.log('list order: ', ...list.map((o) => o.value.num));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Struggling with sending data to a modal in AngularJS?

I am dealing with the following code snippet $scope.currentTask = undefined; $scope.openModal = function (task, size, parentSelector) { var parentElem = parentSelector ? angular.element($document[0].querySelector('.modal-d ...

Explore within another map and analyze the node to spot the differences

I have some questions about iterating through a JavaScript Object and using array functions in JavaScript. Let's assume I have the following variables: var json1 = "[{"id": 1, "name":"x"}, {"id": 2, "name":"y"}]"; var json2 = "[{"id": 1, "name":"x"}, ...

Enhance Your React Progress Bar by Applying a Custom Class to Bars Exceeding 100

My goal is to add an additional class to the respective progress bar only if the count is greater than 100. Currently, the additional class is being applied to all progress bars instead of just the individual one. I have a handleProgressBar click handler ...

Mongoose fails to store a newly created document

I am trying to store a MongoDB document in my Node server using Mongoose: mongoose.connect('mongodb://localhost/appJobs'); var db = mongoose.connection; db.on('error', function() {console.log("error")}); db.once('open', funct ...

preclude any dates prior to the chosen date

I need a solution for a scenario where I have 5 datepickers in sequence. When I select a date on the first picker, all previous dates should be disabled when selecting on the next picker. Additionally, once a date is selected on one picker, the following ...

A tutorial on how to create the appearance of disabled buttons that look the same as enabled buttons through the use

My button includes a text field that is constantly disabled. I would like for the text field to appear as bright as it does when the button is enabled. After disabling the button, they both appear dimmer compared to others and the text field follows suit ...

Small-scale vue iterates through elements with v-for but fails to display them

I'm really interested in Petite-vue, but I've been struggling to get even the basic functionalities to work. Unfortunately, there isn't a lot of examples or tutorials available online for petite-vue. Can anyone suggest good resources? Right ...

Storing references to the DOM elements external to the rendering component

Just diving into the world of Electron + Typescript, so please bear with me. Currently, I'm experimenting with what can be achieved within Electron. Issue: My goal is to manipulate DOM elements outside of the renderer. I pass a button as a parameter ...

Seamless integration of Applitools with WebdriverIO

Seeking assistance to integrate Applitools with my WebdriverIO project. Find the specifications below: Node Version: v12.18.2 NPM Version: 6.14.5 WebdriverIO Version: 6.3.6 The service in my wdio file is configured as follows: services: ['selenium-s ...

translating python's color function to javascript

Can anyone help me convert the Python function below into JavaScript? Thank you! def coloring(a): #takes an integer number, returns rgb color a = a % 255 akM = a // 43 ypD = a % 43 if akM == 0: color = (255, ypD*255/43, 0) elif akM ...

Discovering the operating system and architecture type for my browser using Node.js - what steps should I take?

I am currently developing a Node.js app that serves as an API microservice. I need to retrieve the operating system and architecture type from a GET request made by my browser. One example could be: "Windows NT 10.0; Win64; x64" ...

Issue encountered when installing packages with NPM due to a missing first argument

Encountering this issue when attempting to install packages using npm install. Looks like there is a problem with npm. I am currently running Linux Mint 19.3 Cinnamon. npm ERR! Linux 5.4.0-42-generic npm ERR! argv "/usr/bin/node" "/usr/bin ...

Numerous levels of nested closures working alongside synchronous JavaScript code

After receiving an explanatory answer to my previous inquiry, I realize that my initial question lacked sufficient context to address my current situation effectively. Let me present a specific route within my Express application: var eventbriteService = ...

What is the best way to ensure a specific section of a website remains visible and fixed at the bottom of the page

I want to set up a simple toolbar with divs and uls containing both anchors and tabs. The position of the toolbar needs to be fixed at the bottom of the page. <%@ Page Language="C#" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional/ ...

Trouble with executing Mongoose find within an asynchronous function

Even after referring to this stack overflow solution, I still couldn't resolve my issue. The find method works perfectly when used inside a controller but not within an async function. Here is the cron job where I'm calling this function in my ...

What is the importance of maintaining immutability for objects in Redux?

What is the importance of immutability in Redux? While I understand that frameworks like Angular2 use onPush to leverage immutability for quicker rendering of views, I'm interested in learning about other reasons why Redux emphasizes immutability desp ...

Creating broken lines with Three.js using BufferGeometry

I find myself in a situation where I need to create a Line with holes, essentially a discontinuous line. This entails my Line being composed of multiple segments that are visually separate but conceptually connected. These segments contain more than just t ...

Generating ranges dynamically based on the values in an array

As I receive data from a server, my goal is to categorize it into various buckets for presentation. The dataset appears in the following format: Array [ Object { "name": "1.00", "value": 17, }, Object { "name": "1.01", "value": ...

Having trouble displaying dynamically generated texture from a fragment shader in ThreeJS

I've been working on creating a texture using a Three.WebGLRenderTarget and then trying to access it in a fragment shader in the following stage. The concept is to execute the first stage once to generate a complex and costly SDF map, and then access ...

ExpressJS, defining routes, locating controllers, and documenting APIs

Utilizing expressJS 4.X and nodeJS 6.x In the past, I was defining my routes in this manner : /** * @api {get} /users/:userId Get a user * @apiName GetUser * @apiGroup User * * @apiParam {Integer} userId Users unique ID. * * @apiSuccess (Success 2 ...