Enhance the efficiency of the algorithm for combining text nodes that contain decorations

I'm seeking assistance in merging two arrays of nodes along with their decorations. My current algorithm is functional but highly inefficient. I would appreciate any suggestions for improvement or new ideas.

Each node within the arrays contains text, decorations (such as color and font styles), as well as 'from' and 'to' indexes. The goal is to combine these arrays while keeping the text intact and merging the decorations.

The existing algorithm involves iterating through each letter, checking for decorations, and creating a separate node for each letter with all applicable decorations:

const getDecorationsByIndex = (nodes, index) => {
  const foundNodes: any[] = [];

  nodes.forEach(node => {
    if (index >= node.from && index < node.to) {
      foundNodes.push(node);
    }
  });

  return foundNodes.flatMap(node => node.textData?.decorations || []);
};

const mergeNodes = (nodes1, nodes2, content) => {
  const mergedNodes = [...content].map((s, index) => {
    const decorations1 = getDecorationsByIndex(nodes1, index);
    const decorations2 = getDecorationsByIndex(nodes2, index);

    return {
      id: '',
      nodes: [],
      type: Node_Type.TEXT,
      textData: {
        decorations: [...decorations1, ...decorations2],
        text: s,
      },
    };
  });

  return mergedNodes;
};

For example, consider a simple text "def" with various decorations ranging from color to font weight.

One array adds boldness to part of the text:

[
  {
      "type": "TEXT",
      "id": "",
      "nodes": [],
      "textData": {
          "text": "de",
          "decorations": [
              {
                  "type": "BOLD",
                  "fontWeightValue": 700
              }
          ]
      },
      "from": 0,
      "to": 2
  },
  {
      "type": "TEXT",
      "id": "",
      "nodes": [],
      "textData": {
          "text": "f",
          "decorations": []
      },
      "from": 2,
      "to": 3
  }
]

The second array introduces colors:

    [
  {
      "id": "",
      "nodes": [],
      "type": "TEXT",
      "textData": {
          "decorations": [
              {
                  "type": "COLOR",
                  "colorData": {
                      "foreground": "#d73a49"
                  }
              }
          ],
          "text": "def"
      },
      "from": 0,
      "to": 3
  }
]

The desired result should be:

[
  {
      "type": "TEXT",
      "id": "",
      "nodes": [],
      "textData": {
          "text": "de",
          "decorations": [
              {
                  "type": "BOLD",
                  "fontWeightValue": 700
              },{
                  "type": "COLOR",
                  "colorData": {
                      "foreground": "#d73a49"
                  }
              }
          ]
      },
      "from": 0,
      "to": 2
  },
  {
      "type": "TEXT",
      "id": "",
      "nodes": [],
      "textData": {
          "text": "f",
          "decorations": [{
                  "type": "COLOR",
                  "colorData": {
                      "foreground": "#d73a49"
                  }
              }]
      },
      "from": 2,
      "to": 3
  }
]

The final output should be one consolidated array of nodes preserving the original text along with all decorations. I'm open to suggestions on optimizing my algorithm or guidance on alternative approaches to tackle this node merging process. Thanks in advance!

Answer №1

One way to organize nodes is by using the from/to data to sort them based on their index. This involves duplicating the nodes: once for extracting their from value and once for their to value. By sorting these duplicates and iterating in that sequence, you can effectively keep track of active decorations.

Prior to beginning this process, you may opt to gather substrings and construct a string from them. This assembled string can then be utilized within the main algorithm to segment it once more.

const Node_Type = { TEXT: 3 };

// Function to toggle set member
const toggle = (set, member) => !set.delete(member) && set.add(member);

const extractText = (nodes) =>
    // Rebuild text from nodes without any inconsistencies
    nodes.reduce((acc, node) => {
        acc.length = Math.max(acc.length, node.from);
        acc.splice(node.from, node.to - node.from, ...node.textData.text);
        return acc;
    }, []).map(ch => ch ?? " ").join("");

const mergeNodes = (nodes1, nodes2) => {
    const nodes = nodes1.concat(nodes2);
    const text = extractText(nodes);
    const activeNodes = new Set;
    return nodes.flatMap(node => [[node.from, node], [node.to, node]])
                 .sort(([a], [b]) => a - b)
                 .map(([from, node], i, toggles) => {
        toggle(activeNodes, node); // Activate or deactivate a decoration
        const to = toggles[i+1]?.[0] ?? text.length;
        if (from === to || !activeNodes.size) return; // No generation needed here
        return {
            id: '',
            nodes: [],
            type: Node_Type.TEXT,
            textData: {
               decorations: Array.from(activeNodes, ({textData: {decorations}}) => decorations).flat(), 
               text: text.slice(from, to),
            },
            from,
            to
        };
    }).filter(Boolean); // Remove undefined entries
}

// Example input arrays
const a = [{
    "type": "TEXT", "id": "", "nodes": [],
    "textData": { "text": "de", "decorations": [{"type": "BOLD","fontWeightValue": 700}] },
    "from": 0, "to": 2
}, {
    "type": "TEXT", "id": "", "nodes": [],
    "textData": { "text": "f someFoo", "decorations": []},
    "from": 2, "to": 11
}];

const b = [{
    "id": "", "nodes": [], "type": "TEXT",
    "textData": {
        "decorations": [{"type": "COLOR","colorData": {"foreground": "#d73a49"}}],
        "text": "def"
    },
    "from": 0, "to": 3
}, {
    "id": "", "nodes": [], "type": "TEXT",
    "textData": { "decorations": [], "text": " "},
    "from": 3, "to": 4
}, {
    "id": "", "nodes": [], "type": "TEXT",
    "textData": {
        "decorations": [{"type": "COLOR", "colorData": {"foreground": "#6f42c1"}}],
        "text": "someFoo"
    },
    "from": 4, "to": 11
}];

console.log(mergeNodes(a, b));

Note that shared decorations are not cloned, potentially leading to nodes sharing references in their decorations array. Consequently, any changes made to a decoration would affect all nodes sharing it. To alter this behavior, consider cloning decorations during processing.

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

Issues with npm installation not capturing all required dependencies

After running npm install to install all dependencies, I encountered an issue where the dependencies were not being installed in my current project. The following message was displayed: https://i.sstatic.net/RafcR.png I then attempted to audit the projec ...

Using React to create a fadeout effect and decrease the length of the photo

Is there a way to create a fade-out effect for each photo card and then shorten the length of the photos following the fade out? I have tried implementing a solution, but it doesn't seem to work when I click on each photo. Any suggestions or insights ...

Check to see if modifying the HTML using jQuery results in any errors

Although it may sound straightforward, let me clarify. I am utilizing ajax calls to update the content of the body and I aim to trigger an alert if the updating process fails on the client side, specifically after receiving a response from ajax in case of ...

What causes the difference between object[key] and Object.key in JavaScript?

After running the following code snippet, I observed that "typeof object[key]" is displaying as a number while "typeof object.key" is showing undefined. Can anyone explain why this unusual behavior is occurring? var object = {a:3,b:4}; for (var key in o ...

Troubleshooting TypeScript Node.js Compilation Issue

In my quest to establish a debugging environment for a project from 2019, I included the following script in my package.json: "dev:debug": "tsc-watch --onFirstSuccess \"node --inspect -r ts-node/register src/app.ts\"", Executing this script pro ...

When incorporating "<" or ">" into a parameter value in Angular Translate and then showcasing it in a textarea with ng-model

In my Angular Translate string, I am using a parameter that can be in the form of <test>. However, when this translate is displayed in a textarea, it shows up as &lt;test&gt; instead of <test>. Is there a way to ensure it appears correc ...

The error message "Property 'DecalGeometry' is not found in the type 'typeof "..node_modules/@types/three/index"'."

Within my Angular6 application, I am utilizing 'three.js' and 'three-decal-geometry'. Here is a snippet of the necessary imports: import * as THREE from 'three'; import * as OBJLoader from 'three-obj-loader'; import ...

Retrieve all users along with their respective posts, ensuring that each post is also accompanied by its corresponding comments in

In my Laravel project, I have set up Eloquent models for User, Post, and Comment. The relationships are as follows: User model public function posts(){ return $this->hasMany('App\Post'); } public function comments(){ return $t ...

conceal the x-axis labels in the highchart

Recently, I started implementing highchart and I'm interested in hiding the x-axis categories (the numbers on the bottom of the chart). You can refer to the image below for clarification. Can anyone provide guidance on how to accomplish this? ...

Having difficulty retrieving the value of a dynamically selected radio button in AngularJS

Having trouble with selecting radio options in my code, need some assistance to fix it. Check out my Plnkr Code - http://plnkr.co/edit/MNLOxKqrlN5ccaUs5gpT?p=preview Although I am able to retrieve names for the 'classes' object, the selections ...

Utilize React's useState hook in combination with TypeScript to effectively set a typed nested object

In my project, I have a burger menu component that will receive two props: 1) isOpen, and 2) a file object { name, type, size, modifiedAt, downloadUrl } I'm attempting to implement the following code snippet, but I am encountering issues with Typescr ...

Is there a way for me to directly download the PDF from the API using Angular?

I'm trying to download a PDF from an API using Angular. Here's the service I've created to make the API call: getPDF(id:any) { return this.http.get( `url?=${id}`, { responseType: 'blob' as 'json', obs ...

Moving the parent of a ThreeJS object to align with the geometry of its child

I am working with a 3D object that includes a cube mesh as a child. I am trying to adjust the position of the 3D object within the mesh in order to manipulate the pivot points of the cube mesh. You can find the code snippet on this codepen link. ...

Issue: Unable to access API target map in AGM using Google Map API

I attempted to integrate a Google map with multiple locations. While I was successful in using an Iframe for one location, I struggled to implement multiple locations within the Iframe. As a result, I tried utilizing AGM and used the same APIKey that I ha ...

The Document.querySelector() method is not displaying every element

As a beginner, I am currently exploring the world of relative CSS selectors and JSPath for my automation scripts. During my journey, I noticed that there are differences in the return statements between these two methods. Below is an example demonstrating ...

Shifting the entire page from left to right and back again

I am in search for a template that moves the page from left to right. If anyone can guide me on how to create this effect or provide a JavaScript example, I would greatly appreciate it. ...

Oops! Looks like there was an error. The text strings need to be displayed within a <Text> component in the BottomTabBar. This occurred at line 132 in the

My app includes a bottomTab Navigation feature, but I encountered an error: Error: Text strings must be rendered within a <Text> component. This error is located at: in BottomTabBar (at SceneView.tsx:132) in StaticContainer in StaticCont ...

What is the reason behind Express exporting a function instead of an object in the initial stages?

In Node.js, when utilizing express, we start by using const express = require('express') to bring in the express module, which will then yield a function. Afterward, we proceed with const app = express() My inquiry is as follows: What exactly ...

Convert the button element to an image

Can someone please explain how to dynamically change a button element into an image using javascript when it is clicked? For instance, changing a "Submit" button into an image of a check mark. ...

Resolving typing complications with Typescript in React higher order components for utilizing an alias for components

Attempting to devise a Higher Order Component (HOC) that can also double as a decorator for the following purpose: Let's say there is a component named "counter" interface ICounterProps { count: number; } interface ICounter<T> extends React ...