Convert array to multiple arrays

Viewed 221

I'm trying to manipulate an array but I can't seem to get it into the form I want.

Here's the array:

const children = [
  { type: 'span' },
  { type: 'br' },
  { type: 'span' },
  { type: 'br' },
  { type: 'br' },
  { type: 'span' },
  { type: 'br' },
  { type: 'br' },
  { type: 'br' },
  { type: 'span' },
  { type: 'br' },
  { type: 'span' },
]

So the challenge is to create an array of arrays. Whenever there are 2 or more consecutive br values then this signifies where one array ends and the next begins. So I'm trying to get to this:

[
  [{type: 'span'}, {type: 'br'}, {type: 'span'}],
  [{type: 'span'}],
  [{type: 'span'}, {type: 'br'}, {type: 'span'}]
]

Note There could be any amount of consecutive br tags.

Wondering whether reduce would be a better approach than forEach though?

6 Answers

It's a bit lazy, and you might want to choose a separator other than \n depending on your data, but

const children = [
  'span',
  'br',
  'span',
  'br',
  'br',
  'span',
  'br',
  'br',
  'br',
  'br',
  'br',
  'br',
  'br',
  'br',
  'br',
  'span',
  'br',
  'span'
]

console.log(children.join("\n").split(/(?:\nbr){2,}\n/).map(p => p.split("\n")))

certainly works...

console.log(
  children
  .map(({ type: t }) => t) // to array of strings
  .map(t => t === 'br' ? ' ' : t) // br => ' ' for using split() and trim()
  .join('') // to long string, 
  .split('  ') // split by 2 'br'
  .map(r => r.trim()) // remove leading & trailing 'br's 
  .map(r => r
    .replace(' ', '|br|') // restore 'br'
    .split('|') // split to array
    .map(r => ({type : r})) // restore structure
    )
  );

I'm not 100% sure this works and I feel like it could be a bit neater, but here's an attempt with reduce:

const children = [{"type":"span"},{"type":"br"},{"type":"span"},{"type":"br"},{"type":"br"},{"type":"span"},{"type":"br"},{"type":"br"},{"type":"br"},{"type":"span"},{"type":"br"},{"type":"span"}];

const is_br = (item) => item?.type === 'br';

const result = children.reduce(
  (acc, val, i, { [i - 1]: prev, [i + 1]: next }) => {
    if(!is_br(prev) && is_br(val) && is_br(next))
      acc.push([]);
  
    if(is_br(val) && (is_br(prev) || is_br(next)))
      return acc;
    
    acc[acc.length - 1].push(val);
    
    return acc;
  },
  [[]]
);

console.log(result);

A possibly working (or close to) version of your code:

const children = [{"type":"span"},{"type":"br"},{"type":"span"},{"type":"br"},{"type":"br"},{"type":"span"},{"type":"br"},{"type":"br"},{"type":"br"},{"type":"span"},{"type":"br"},{"type":"span"}];

let paragraphs = []
let elements = []
let flag = false
children.forEach((child, idx) => {
  if (child.type !== 'br') {
    flag = false
    elements.push(child)
  } else {
    if (flag) {
      if(elements.length > 1) {
        paragraphs.push(elements.splice(0, elements.length - 1))
      }
      elements = []
    } else {
      elements.push(child)
      flag = true
    }
  }
  
  if(idx === children.length - 1 && elements.length > 0)
    paragraphs.push(elements)
});

console.log(paragraphs);

Solution Using While Loops.

  • There are two variables first is the counter i and second is temp that will help store the different array slices.

  • Next, there is a nested while loop. The first one is simply for looping, the second loop is to skip all consecutive "br".

  • There's a flag to check whether the second while loop ran.

    • If it did that means we now need to push the contents of temp into the resultant array res and empty temp.

    • If it didn't, we need to keep pushing to temp.

There are also a few checks here to handle some edge cases.

const children = [{type:"span"},{type:"br"},{type:"span"},{type:"br"},{type:"br"},{type:"span"},{type:"br"},{type:"br"},{type:"br"},{type:"span"},{type:"br"},{type:"span"}];

const res = [];

let i = 0;
let temp = [];

while (i < children.length) {
  let flag = false;
  while (i < children.length && children[i].type === "br" && children[i + 1]?.type === "br") {
    i++;
    flag = true;
  }
  if (flag) { res.push(temp); temp = []; i++; }
  if (i >= children.length) break;
  temp.push(children[i]);
  i++;
}

if (temp.length > 0) res.push(temp);

console.log(JSON.stringify(res));

Solution Using RegEx

(Inspired by Ralkov's solution)

const 
 children = [{type:"span"},{type:"br"},{type:"span"},{type:"br"},{type:"br"},{type:"span"},{type:"br"},{type:"br"},{type:"br"},{type:"span"},{type:"br"},{type:"span"}],

 res = children
  .map(({ type }) => `${type} `)
  .join("")
  .replace(/(?:br\s){2,}/g, " ")
  .trim()
  .split("  ")
  .map((s) => s.split(",").map((type) => ({ type })));
  
console.log(JSON.stringify(res));

You could reduce the array.

const
    children = [{ type: 'span' }, { type: 'br' }, { type: 'span' }, { type: 'br' }, { type: 'br' }, { type: 'span' }, { type: 'br' }, { type: 'br' }, { type: 'br' }, { type: 'span' }, { type: 'br' }, { type: 'span' }],
    separator = 'br',
    result = children.reduce((r, o, i, a) => {
        if (o.type === separator) {
            if (a[i - 1]?.type === separator) {
                if (r[r.length - 1].length) r.push([]);
                return r;
            } else if (a[i + 1]?.type === separator) return r;
        }
        r[r.length - 1].push(o.type);
        return r;
    }, [[]]);
    
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Here's one that 1) converts your array of objects into a string, 2) splits on multiple b's and 3) converts the split array back into objects:

const children = [
  { type: "span" },
  { type: "br" },
  { type: "span" },
  { type: "br" },
  { type: "br" },
  { type: "span" },
  { type: "br" },
  { type: "br" },
  { type: "br" },
  { type: "span" },
  { type: "br" },
  { type: "span" },
];

const map = { s: "span", b: "br" };

console.log(
  children
    .reduce((s, o) => (s += o.type[0]), "")   // convert obj arr to string
    .split(/b{2,}/)                           // splits string on 2 or more 'b's
    .map((s) => {                             // convert split strings back into obj arr
      const res = [];
      for (const ch of s) {
        res.push({ type: map[ch] });
      }
      return res;
    })
);
Related