Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Array of Articles split into subarrays based on topic with Javascript reduce

I'm trying to split an array of articles into subarrays based on the first key value pair, and it's order.

I have looked into many Stack Overflow posts, and I think this is the one that closest suits what I'm trying to accomplish:

break array of objects into separate arrays based on a property

I am aware that questions around reduce are commonly asked, but I'm getting tripped up and this is what I can't find the answer to:

What's different: I don't want to filter the arrays into 2 separate categories (ie. "markup" and "video"), but rather have the first array be all of the "markup" items UNTIL a "video" item, make an array with all of the "video" items until next "markup" item, make a new array of all of the "markup" items until next "video" item, etc.

Here is a REPL reproducing what I'm trying to do: REPL reproducing problem

The data looks like this:

export default [{
  "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"
},
]

What I would like it to look like after using JavaScript reduce is:

[
  [
    {type: 'markup', element: 'p', html: 'blah' /*...*/ },
    {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' /*...*/ },
    {type: 'markup', element: 'p', html: 'blah' /*...*/ }
  ]
]

What I have so far is:

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)

This code simply created 2 arrays (one with markup, one with video), it doesn't keep in mind the order of the original data, nor does it create separate arrays of all the groups of data based on order.

What is the most elegant way to solve this problem? I would be very appreciative if you could even guide me in the right direction.

like image 239
Chris Bradshaw Avatar asked May 21 '26 02:05

Chris Bradshaw


2 Answers

You could use Array#reduce and check the last element of the accumulator.

var array = [{ 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" }],
    grouped = array.reduce((r, o, i, a) => {
        var last = r[r.length - 1];
        if (!last || last[0].type !== o.type) {
            r.push([o]);
        } else {
            last.push(o);
        }
        return r;
    }, []);

console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }
like image 199
Nina Scholz Avatar answered May 22 '26 14:05

Nina Scholz


I know for is not as cool as reduce, but its certainly more readable:

 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;
  }
}
like image 36
Jonas Wilms Avatar answered May 22 '26 14:05

Jonas Wilms



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!