Split an array of articles into subarrays according to their topics using the reduce method in Javascript

I am currently seeking a solution to divide an array of articles into subarrays based on the first key-value pair and its order.

After researching several Stack Overflow posts, I have come across one that seems to align closely with my objective:

break array of objects into separate arrays based on a property

Despite encountering many questions related to reduce functions, I am facing some difficulties. Specifically, I am struggling with:

What sets my problem apart: Rather than splitting the arrays into two distinct categories (such as "markup" and "video"), I aim to group all "markup" items together in the first array until a "video" item is encountered. Subsequently, I want to create an array containing all "video" items until the next "markup" item, followed by a new array of "markup" items until the subsequent "video" item, and so forth.

To demonstrate what I am attempting to achieve, here is a REPL example: REPL reproducing problem

The structure of the data is as follows:

export default [{
  "type": "markup",
  "element": "p",
  "html": "blah"
}, {
  "type": "markup",
  "element": "p",
  "html": "blah"
},
...
]

The desired outcome post JavaScript reduce operation:

[
  [
    {type: 'markup', element: 'p', html: 'blah' /*...*/ },
    {type: 'markup', element: 'p', html: 'blah' /*...*/ },
    ...
  ],
  [
    {type: 'embeddedVideo', /*...*/ }
  ],
  [
    {type: 'markup', element: 'p', html: 'blah' /*...*/ },
    {type: 'markup', element: 'p', html: 'blah' /*...*/ },
    ...
  ]
]

My current progress:

import articleBody from './articleBody.js';


 function groupBy(arr, property) {
  return arr.reduce((prop, x) => {
    if (!prop[x[property]]) { prop[x[property]] = []; }
    prop[x[property]].push(x);
    return prop;
  }, {});
}

let outputSample = groupBy(articleBody, "type");

console.log(outputSample)

The above code effectively generates two arrays differentiated by "markup" and "video"; however, it overlooks the original data's sequence and fails to produce distinct arrays based on the specified order.

If you could provide guidance or suggest an elegant resolution to this conundrum, it would be greatly appreciated. Thank you for your assistance.

Answer №1

If you're looking for a solution, consider utilizing the Array#reduce method to examine the last element of the accumulator array.

var data = [{ type: "markup", value: "Example 1" }, { type: "markup", value: "Example 2" }, { type: "data", value: "Data example" }, { type: "videoEmbed", value: "Embedded video widget" }, { type: "markup", value: "Sample text" }], groupedData = data.reduce((result, obj, index, arr) => {
    var lastItem = result[result.length - 1];
    if (!lastItem || lastItem[0].type !== obj.type) {
        result.push([obj]);
    } else {
        lastItem.push(obj);
    }
    return result;
}, []);

console.log(groupedData);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Answer №2

Using for may not be as trendy as using reduce, but it definitely enhances readability:

 const result = [[]];
 let current = result[0], currentType = articleBody[0].type;

 for(const content of articleBody) {
   if(content.type === currentType) {
     current.push(content);
   } else {
     result.push(current = [content]);
     currentType = content.type;
  }
}

Answer №3

If you wish to accomplish this using reduce(), one approach is to track the last element encountered and add a new array to the result if it differs, then add to the second-to-last subarray. However, for better readability, implementing a standard loop might be more visually appealing.

let tempArr = [{"type": "markup","element": "p","html": "blah"}, {"type": "markup","element": "p","html": "blah"}, {"type": "markup","element": "p","html": "blah"}, {"type": "embeddedVideo","element": "p","html": "embeddedWidget"}, {"type": "markup","element": "p","html": "blah"},{"type": "markup","element": "p","html": "blah"},]

let r = tempArr.reduce((a, c, i, self) => {
    if (i === 0 || self[i-1].type !== c.type) 
        a.push([])
    a[a.length - 1].push(c)
    
    return a
}, [])

console.log(r)

Answer №4

Check out my revised version :)

const items = [{
  "type": "text",
  "content": "lorem ipsum"
}, {
  "type": "text",
  "content": "dolor sit amet"
}, {
  "type": "image",
  "url": "https://example.com/image.jpg"
}, {
  "type": "text",
  "content": "consectetur adipiscing elit"
}, {
  "type": "video",
  "url": "https://example.com/video.mp4"
}];

const key = 'type';
const groups = [[]];
let currType = items[0][key];
items.reduce((prev, next) => {
  if (currType === next[key]) {
    prev.push(next);
    return prev;
  } else {
      groups.push([next]);
      currType = next[key];
      return groups[groups.length - 1];
  }
}, groups[0]);

console.log('Grouped Items', groups);

Answer №5

Here is an explanation of the two-step reduce function:

  1. If the type does not match the previous entry or there is no previous entry, a new row is added to the accumulator.

  2. The current object is then added to the latest row.

const data = [{
    "type": "markup",
    "element": "p",
    "html": "blah"
  }, {
    "type": "markup",
    "element": "p",
    "html": "blah"
  }, {
    "type": "markup",
    "element": "p",
    "html": "blah"
  }, {
    "type": "embeddedVideo",
    "element": "p",
    "html": "embeddedWidget"
  }, {
    "type": "markup",
    "element": "p",
    "html": "blah"
  },
  {
    "type": "markup",
    "element": "p",
    "html": "blah"
  },
];

const result = data.reduce((acc, item) => {
  if (!acc[0] || item.type !== acc[acc.length-1].type) {
    acc.push([]);
  }
  
  acc[acc.length-1].push(item);      
  return acc;
}, []);

console.log(result);

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

PHP Implementing real-time dynamic categories

I am working on a project where there are multiple items listed with unique IDs. Each item has corresponding data with the same ID. I want to use JQuery, JScript, and PHP to display options based on the ID of the selected item in real-time. Below is a snip ...

Is there a way for me to send a freshly created excel file for download using link_to with remote: true?

Is there a way to download a generated excel file in a Ruby on Rails application? After a user clicks on a link, I want to generate an excel file in the backend and have it formatted correctly. However, when I use remote: true in link_to, the file doesn&a ...

The StreamingTextResponse feature is malfunctioning in the live environment

When I share my code, it's an API route in Next.js. In development mode, everything works as expected. However, in production, the response appears to be static instead of dynamic. It seems like only one part of the data is being sent. I'm puzzl ...

Having trouble establishing a connection with Mongo.Client on localhost 27017 for MongoDB

Encountering issues with connecting to MongoDB database and storing data in localhost:27017 using MongoClient. Unable to display connection results in the console for both successful and failed connections. var express = require('express'); var r ...

Mobile devices seem to be constantly refreshing website images

I have a landing page consisting of multiple sections with images as the background, each section occupying the full screen. In one specific section, the images change every 5 seconds. The website works smoothly on desktop, but I encounter issues on mobil ...

Utilizing FCKEditor to incorporate dimensions of width and height into image elements

I'm currently facing an issue while attempting to specify width and height for images within an old WYSIWYG FCKEditor. The challenge arises when trying to retrieve the naturalWidth/naturalHeight properties, as they return values of 0. What could I be ...

Issue with wireframe display on video texture in three.js

Trying to apply a video texture onto a geometry in three.js using a video texture, but encountering an issue where it only displays correctly when the wireframe is set to true on the material (video shows with wireframes). When switching it to false, only ...

jQuery not being applied to dynamically added dropdown element

I am currently utilizing bootstrap and jquery within my react project. I have a button that, when clicked, should transform into a dropdown field. The dropdown functions properly when placed statically, but the functionality is lost once it is dynamically ...

Can anyone explain the functionality of passport.initialize() in the context of Node.js and Express

I am currently working on implementing the passport module in my application. After reading some manuals, I found this: app.use(passport.initialize()); app.use(passport.session()); Can someone explain what app.use(passport.initialize()) does exactly? I ...

Retaining selected items in Jquery UI Multiselect widget even after postback

I want to implement a GridView in asp.net that allows filtering on each column of the gridview, similar to Excel. To achieve this, I created a GridView and utilized the Jquery multiselect widget on the header of each column for filtering. <cc1:GridView ...

Is there a way to seamlessly roll the camera using a button in ThreeJS alongside TrackballControls without causing any conflicts?

When working with ThreeJS, I encountered an issue with rotating the camera using a button. Here is the code snippet I used: camera = new THREE.PerspectiveCamera(...) function roll(angle) { const quaternion = new THREE.Quaternion(); const lookat = ...

Showing PHP array in the JavaScript console

I have a straightforward AJAX script that sends 3 variables to an external PHP script. The external script then adds them into an array and sends the array back. I want to output this array in the JavaScript console to check if the variables are being pass ...

Setting up scheduled MongoDB collection cleanup tasks within a Meteor application

After developing an app to streamline form submissions for my team, I encountered a problem during testing. Meteor would refresh the page randomly, causing loss of data entered in forms. To solve this, I devised a two-way data binding method by creating a ...

Configuring bitfinex-api-node with Node.js to efficiently handle data from the websocket connection

Apologies for the vague title of this question, as I am not well-versed in programming and even less so in node.js My goal is simple: I aim to utilize the bitfinex-api-node package (a node.js wrapper for the bitfinex cryptocurrency exchange) that I instal ...

Displaying sets of PHP array elements

There is a php associative array with a total of 24 items. The goal is to iterate through the array and display them in 4 rows, each containing 6 columns. Is there a way to accomplish this task effectively? ...

Creating markers from Mysql database is a simple and efficient process

On my website, I have an array of markers that I use to display locations on a Google map. The array format I currently use is: generateMarkers([['Location', lat, long], ['Location2', lat2, long2],['Location3', lat3, long]3]) ...

Ionic (Angular) experiencing crashes due to numerous HTTP requests being made

My template contains a list of items <ion-list class="ion-text-center"> <div *ngIf="farms$ | async as farmData"> <ion-item (click)="selectFarm(farm)" *ngFor="let farm of farmData" detail=&quo ...

Axio GET request in VueJS does not return a response body when using Amazon API Gateway

UPDATE: While Postman and browsers are able to receive valid response bodies from an Amazon API Gateway endpoint, other web applications are not. This is a basic GET request with no headers, and no authentication is needed for the API endpoint. The data is ...

Experiencing an infinite loop due to excessive re-renders in

I'm receiving an error and unsure of the reason. My goal is to create a button that changes colors on hover. If you have a solution or alternative approach, please share! import React, {useState} from 'react' function Login() { const ...

Angular allows for a maximum time span of 60 days between two date inputs

I am looking to implement a validation in JavaScript or TypeScript for Angular where the start date cannot be more than 60 days after the end date is entered. The requirement is to enforce a "maximum range of 60 days" between the two input dates (dateFro ...