Efficient approach for combining list elements

I have a collection of content blocks structured like this:

interface Content{
  type: string,
  content: string | string[]
}
const content: Content[] = [
  {
    type: "heading"
    content: "whatever"
  },
  {
    type: "para"
    content: "whatever"
  },
  {
    type: "para"
    content: "whatever"
  },
  {
    type: "list_item"
    content: "whatever"
  },
  {
    type: "list_item"
    content: "whatever"
  },
  {
    type: "list_item"
    content: "whatever"
  },
  {
    type: "para"
    content: "whatever"
  },
  {
    type: "heading"
    content: "whatever"
  },
  {
    type: "para"
    content: "whatever"
  },
  {
    type: "para"
    content: "whatever"
  },
  {
    type: "list_item"
    content: "whatever"
  },
  {
    type: "list_item"
    content: "whatever"
  },
  {
    type: "list_item"
    content: "whatever"
  },
  {
    type: "list_item"
    content: "whatever"
  },
  {
    type: "para"
    content: "whatever"
  },
]

I am attempting to develop a function that will consolidate these list_item's into a single list with an array for the content of each item. Therefore, the result of the function for the given input should be:

[
  {
    type: "heading"
    content: "whatever"
  },
  {
    type: "para"
    content: "whatever"
  },
  {
    type: "para"
    content: "whatever"
  },
  {
    type: "list"
    content: ["whatever","whatever","whatever"]
  },
  {
    type: "para"
    content: "whatever"
  },
  {
    type: "heading"
    content: "whatever"
  },
  {
    type: "para"
    content: "whatever"
  },
  {
    type: "para"
    content: "whatever"
  },
  {
    type: "list"
    content: ["whatever", "whatever", "whatever", "whatever"]
  },
  {
    type: "para"
    content: "whatever"
  },
]

I have been experimenting with a three-pointer approach, iterating through the array from i=1 to i < length - 1, keeping track of the prev, curr, and next blocks. However, I am struggling with the logic and how to handle different scenarios.

It seems like a relatively straightforward problem for seasoned algorithm designers, so I am seeking some guidance.

Answer №1

Within this code snippet, there is a simple loop that iterates over the elements in the 'content' array. It checks if the type of the current element is 'list_item' and either creates a new object with the type 'list' or appends the content to the existing object if one is already created.

const content = [{ type: "heading", content: "whatever" }, { type: "para", content: "whatever" }, { type: "para", content: "whatever" }, { type: "list_item", content: "list1 1" }, { type: "list_item", content: "list1 2" }, { type: "list_item", content: "list1 3" }, { type: "para", content: "whatever" }, { type: "heading", content: "whatever" }, { type: "para", content: "whatever" }, { type: "para", content: "whatever" }, { type: "list_item", content: "list2 1" }, { type: "list_item", content: "list2 2" }, { type: "list_item", content: "list2 3" }, { type: "list_item", content: "list2 4" }, { type: "para", content: "whatever" },];

const res = [];
for (const item of content) {
  if (item.type === 'list_item') {
    if (res[res.length - 1]?.type !== 'list') {
      res.push({ type: 'list', content: [] });
    }
    res[res.length - 1].content.push(item.content);
  } else {
    res.push({ ...item });
  }
}

console.log(res);

Another approach that eliminates the need to repeatedly access the end of the array is to store the active list array in a temporary variable and reset it every time a sequence of 'list_item's concludes. (TSPlayground)

const content = [{ type: "heading", content: "whatever" }, { type: "para", content: "whatever" }, { type: "para", content: "whatever" }, { type: "list_item", content: "list1 1" }, { type: "list_item", content: "list1 2" }, { type: "list_item", content: "list1 3" }, { type: "para", content: "whatever" }, { type: "heading", content: "whatever" }, { type: "para", content: "whatever" }, { type: "para", content: "whatever" }, { type: "list_item", content: "list2 1" }, { type: "list_item", content: "list2 2" }, { type: "list_item", content: "list2 3" }, { type: "list_item", content: "list2 4" }, { type: "para", content: "whatever" },];

const res = [];
let temp = [];
for (const item of content) {
  if (item.type === 'list_item') {
    if (!temp.length) {
      res.push({ type: 'list', content: temp });
    }
    temp.push(item.content);
  } else {
    if (temp.length) {
      temp = [];
    }
    res.push({ ...item });
  }
}

console.log(res);

Answer №2

If you want to transform the array using the reduce method, here's how you can do it:

const content = [{ type: "heading", content: "whatever" }, { type: "para", content: "whatever" }, { type: "para", content: "whatever" }, { type: "list_item", content: "list1 1" }, { type: "list_item", content: "list1 2" }, { type: "list_item", content: "list1 3" }, { type: "para", content: "whatever" }, { type: "heading", content: "whatever" }, { type: "para", content: "whatever" }, { type: "para", content: "whatever" }, { type: "list_item", content: "list2 1" }, { type: "list_item", content: "list2 2" }, { type: "list_item", content: "list2 3" }, { type: "list_item", content: "list2 4" }, { type: "para", content: "whatever" },];

const result = content.reduce(function (acc, item) {
    if (item.type !== "list_item") {
        acc.push({...item });
    } else if (acc.at(-1)?.type !== 'list') {
        acc.push({ type: 'list', content: [item.content] });
    } else {
        acc.at(-1).content.push(item.content);
    }
    return acc;
}, []);

console.log(result);

The at(-1) function is utilized to access the last element in the accumulating array, and the ?. syntax is employed to prevent errors when the accumulating array is empty.

Answer №3

Utilizing JavaScript's built-in array methods appears to be the most optimized approach in this scenario

const a = [
      {
        type: "heading",
        content: "whatever"
      },
      {
        type: "para",
        content: "whatever"
      },
      {
        type: "para",
        content: "whatever"
      },
      {
        type: "list_item",
        content: "whatever 1"
      },
      {
        type: "list_item",
        content: "whatever 2"
      },
      {
        type: "list_item",
        content: "whatever 3"
      },
      {
        type: "para",
        content: "whatever"
      },
      {
        type: "heading",
        content: "whatever"
      },
      {
        type: "para",
        content: "whatever"
      },
      {
        type: "para",
        content: "whatever"
      },
      {
        type: "list_item",
        content: "whatever 4"
      },
      {
        type: "list_item",
        content: "whatever 5"
      },
      {
        type: "list_item",
        content: "whatever 6"
      },
      {
        type: "list_item",
        content: "whatever 7"
      },
      {
        type: "para",
        content: "whatever"
      },
    ]
    let listItemContent = [];
    const res = a.reduce((total, v) => {
      if(v.type === "list_item") {
        listItemContent.push(v.content);
      }
      else if(listItemContent.length > 0) {
        total.push({type: "list", content: listItemContent});
        listItemContent = [];
      } else {
        total.push(v);
      }
      return total;
    }, []);
    if(listItemContent.length > 0) {
      res.push({type: "list", content: listItemContent});
    }
    console.log(res)

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

The correct usage of && and || operators in javascript

I've developed a small web application and I'm facing an issue where I want to trigger an alert when the 'enter' key is pressed if there is an empty or space value. Currently, the alert appears not only on hitting 'enter' but ...

Having trouble with my JSFiddle code not functioning properly in a standard web browser for a Google Authenticator

Here is the code snippet that I am having trouble with: http://jsfiddle.net/sk1hg4h3/1/. It works fine in jsfiddle, but not in a normal browser. I have included the necessary elements in the head to call all the external files. <head> <scri ...

Requiring addresses for Google Maps in order to display directions

To display the directions between two addresses based on the chosen transportation mode, I need to pass the addresses to the code in page 2. After the user selects two cities from a Dropdown box on page 1, they will be sent to the code to show their locati ...

Utilize modules within the AppModule to promote modularization and maintain separation of concerns in Angular 2

When using templates like those from the ASP.NET Core JavaScript services, typically a single module named AppModule is included. However, in my application, which is divided into two logical areas (AreaA and AreaB), I thought it would be better to use two ...

Is there a way to replicate Twitter's "what's happening" box on our website?

Currently, I am trying to extract the cursor position from a content-editable box. However, when a new tag is created, the cursor appears before the tag instead of after it. Additionally, I am having trouble with merging/splitting the tags. Any suggestions ...

The function window.addEventListener('load') functions properly on desktop computers, but does not work on mobile devices

After developing a react website, I noticed that it functions correctly on PC but not on Mobile devices. componentDidMount() { window.addEventListener('scroll', this.onScroll); // This event works fine window.addEventListener('load&a ...

Can TypeScript ascertain the object's type dynamically based on the key of its proxy name?

I am creating a wrapper class that will handle various operations related to the user entity. These operations include checking if the user is a guest, verifying their permissions, and more. One of the functions I want to implement in this class is an acce ...

Finding and removing an element using Jquery

$.ajax({ type: "GET", url: "ajax/getLinks.php?url=" + thisArticleUrl }).done(function (data) { var extractedContent = $(data).find("#content > div > div.entry.clearfix"); extractedContent.find("#content > di ...

Finding tool for locating object names in JSON

JSON data: [ { "destination": "Hawaii", "Country": "U.S.A", "description": "...and so forth", "images": { "image": [ "hawaii1.jpg", "hawaii2.jpg", ...

Conceal a div while revealing the other div

I am currently trying to achieve a unique visual effect. I have three divs named div1, div2, and div3. The objective is for div1 to slide up and disappear when clicked, while div2 simultaneously slides up and becomes visible. Similarly, when div2 is click ...

Validating forms in React with Material-UI using hooks

Currently, I am working on a Next.js project that utilizes Material-UI as the primary UI framework. For validation purposes, I am implementing a React hooks form. Within my main component, I have the form structure with separate child components for vari ...

JS showcase of object literals and their corresponding properties

Just starting out with Javascript and eager to learn about arrays of objects. I'm currently exploring how to display an object along with its properties. Here's an example showcasing the colors of different fruits: var fruitColor = {'apples ...

Encountering ERR_INVALID_HTTP_RESPONSE when trying to establish a connection with a Python gRPC server using @bufbuild/connect

Attempting to establish a connection to a python grpc server developed with grpcio through a web browser using connect-query. Encountering an issue where upon making a request, an ERR_INVALID_HTTP_RESPONSE error is displayed in the browser. Below is the Re ...

Tips on sorting an array within a map function

During the iteration process, I am facing a challenge where I need to modify certain values based on specific conditions. Here is the current loop setup: response.result.forEach(item => { this.tableModel.push( new F ...

Submit a single data value to two separate models in JSON format using MongoDB

I recently developed a code with 2 essential functions: module.exports.registerAccount = (reqBody) => { let newAccount = new User({ firstName : reqBody.firstName, lastName : reqBody.lastName, email : reqBody.email, mobileNum : reqBody.m ...

What is the best way to rid ourselves of unwanted values?

In the laravel-vue-boilerplate package, there is a User CRUD feature. I duplicated this functionality to create an Item CRUD by making some changes and adjustments. Everything is working fine except for one issue: after editing an item, when trying to add ...

The component name 'Hidden' is not valid for use in JSX

Currently, I'm immersed in a personal project focused on creating a responsive website utilizing Material-UI. In this endeavor, I've leveraged React and kickstarted the project with create-react-app. To enhance the design, I incorporated code fro ...

What is the best way to modify this state without altering the original array?

I am seeking guidance on how to avoid mutating state in a specific scenario: In my React Hooks setup, I have a TodoList component that displays a list of TodoItems. Each TodoItem can be clicked to trigger a function called toggle, which switches its compl ...

Steps for filling an HTML table within a dynamically loaded DIV

I have a container in my HTML page where I dynamically load other pages using the jQuery.load() function. One of the pages requires me to populate a table from the database just after/before it loads. How can I trigger a JavaScript function to execute righ ...

Is there a way to incorporate a loading spinner into a MaterialUI DataTable without having to specify a fixed height for the parent component?

Currently, I am using a MaterialUI DataTable with the following setup: <div style = {{height: 300}}> <DataGrid loading={true} getRowHeight={() => "auto"} getEstimatedRowHeight={() => 250} ...