12

I have nested array data and I would like to extract all nested arrays to be siblings of their parent. I am pretty close, but I am getting an extra empty array in the results and I cannot figure out where it is coming from or how to get rid of it.

Note: I would really like to understand why this is happening and how to get rid of it in my function, and not just a .filter(arr => arr.length) on my results list.

This is my attempt so far:

var arrs = [
  [1, 2, [3, 4], 5],
  [6, [7, 8, 9, [10, 11]]],
  [12, 13],
  [[14, 15], [16, 17]],
  [[1], 4, [1, 1], 4]
];

// Desired Output
// [
//   [1, 2, 5],
//   [3, 4],
//   [6],
//   [7, 8, 9],
//   [10, 11],
//   [12, 13],
//   [14, 15],
//   [16, 17],
//   [4, 4]
//   [1]
//   [1, 1]
// ]

function extractArrays (arr) {
  return arr.reduce((res, curr) => {
    if (Array.isArray(curr)) {
      res = res.concat(extractArrays(curr));
    }
    else {
      res[0].push(curr);
    }
    return res;
  }, [[]]);
}

console.log(extractArrays(arrs));
// Results:
// [ 
//   [],  <-- Where is this coming from?
//   [ 1, 2, 5 ],
//   [ 3, 4 ],
//   [ 6 ],
//   [ 7, 8, 9 ],
//   [ 10, 11 ],
//   [ 12, 13 ],
//   [],  <-- Also here
//   [ 14, 15 ],
//   [ 16, 17 ],
//   [ 4, 4 ],
//   [ 1 ],
//   [ 1, 1 ]
// ]
.as-console-wrapper {
  max-height: 100% !important;
}

5
  • 3
    you have: [[]] as the second arg to reduce, so your first item in the array, is an empty array. Commented Jun 29, 2018 at 20:26
  • @Fallenreaper Yeah I thought so, too, but then my output becomes [1, 2, 3, 4, 5, 6, ...] Commented Jun 29, 2018 at 20:27
  • res[0].push(curr); never happens on the top level. Commented Jun 29, 2018 at 20:27
  • @ASDFGerte I see what you mean. So basically I will have to do the child arrays individually on the top level and then stitch the results together? Commented Jun 29, 2018 at 20:31
  • That would at least be a possibility. As long as you take empty levels as empty arrays into the output, your current output is not "wrong" - the top level just contains no numbers. Commented Jun 29, 2018 at 20:52

5 Answers 5

5

Element like [[14, 15], [16, 17]] will introduce a [] after recursion. This should be handled by checking length.

var arrs = [
  [1, 2, [3, 4], 5],
  [6, [7, 8, 9, [10, 11]]],
  [12, 13],
  [[14, 15], [16, 17]],
  [[1], 4, [1, 1], 4]
];

function extractArrays (arr, acc=[]) {
  if (arr.length == 0 ) return acc;
  let pure = arr.filter(elm => !Array.isArray(elm));
  if (pure.length > 0) {
    acc.push(pure);
  }
    
  acc.concat(arr.filter(elm => Array.isArray(elm)).map(elm => extractArrays(elm, acc)));

  return acc;
}

console.log(extractArrays(arrs));

Sign up to request clarification or add additional context in comments.

Comments

4

You can try the following code

var arrs = [
  [1, 2, [3, 4], 5],
  [6, [7, 8, 9, [10, 11]]],
  [12, 13],
  [
    [14, 15],
    [16, 17]
  ], // <-- added additional test case
  [
    [1], 4, [1, 1], 4
  ]
];

function extractArrays(arr) {
  return arr.reduce((res, curr, i) => {
    if (Array.isArray(curr)) {
      res = res.concat(extractArrays(curr));
    } else {
        let index = 0;
        for (let j = 0; j <= i; j++) {
          if (!Array.isArray(arr[j])) {
            res[index] ? res[index].push(curr) : res.push([curr]);
            break;
          } else {
            index++;
          }
        }          
    }
    return res;
  }, []); // <-- no initial empty array inside here
}

console.log(extractArrays(arrs));

1 Comment

According to my desired output, [..., [1], [4, 4], [1, 1]] should be [..., [4, 4], [1], [1, 1]], but I think in my case, your solution should still work. Thank you!
1

I just wanted to share my approach to this problem, I enjoyed trying to solve it, in my case I also passed an array to the extractArrays method, in order to make easier to capture and filter every array inside the arrs param.

let result = [];
extractArrays(arrs, result);
console.log(result);

function extractArrays(arr, result) {
  let newResult = arr.reduce((acc, curr) => {
    if (Array.isArray(curr)) {
      extractArrays(curr, result);
    } else {
      acc.push(curr);
    }

    return acc;
  }, []);

  newResult.length && result.push(newResult);
}

Comments

1

You can check it when you return from function. stackblitz

function extractArray(arr) {
  const res = arr.reduce((res, curr) => {
    if(!Array.isArray(curr)){
      return [[...res[0], curr], ...res.slice(1)]
    }
    return [...res, ...extractArray(curr)]
  }, [[]]);

  return res[0].length ? res : res.slice(1);
}

EDIT: More performant function (check stackblitz link)

function extractFaster(arr) {
  let res = [0];
  function recExtract(arr) {
    let hasNonArrayElm = false;
    let index = res.length -1;
    arr.forEach(curr => {
      if (!Array.isArray(curr)) {
        hasNonArrayElm ? res[index].push(curr) : res.splice(index, 0, [curr]);
        hasNonArrayElm = true;
        return;
      }
      recExtract(curr);
    });
  }

  recExtract(arr);
  res.splice(-1, 1)
  return res;
}

2 Comments

This is a creative use of the spread operator, but checking length in the return value and slicing the first elem off is not much different than just calling extractArray(data).filter(arr => arr.length). +1'd anyway
@mhodges you are right, it was almost same. Added new faster function.
0

EDIT: The answer below the line is a great way to flatten arrays, but I suggested it because I misunderstood this question. I will leave it in case it benefits someone to know, but in order to keep an accurate record, I'll also update my answer to address the problem posed in the question.

The accepted answer seems sufficient enough, but I'll try my hand at it. I would use Array.reduce to cover everything with one swoop, and inside use Array.filter to separate the normal items from the array items, then use the spread ... operator on the nested arrays so everything gets pushed to the same level, after recursively calling the same extract function on all nested arrays. Honestly, the explanation might be harder to understand than the code, have a look:

const data = [
  [1, 2, [3, 4], 5],
  [6, [7, 8, 9, [10, 11]]],
  [12, 13],
  [[14, 15], [16, 17]],
  [[1], 4, [1, 1], 4]
]

const extractChildArrays = arrs => arrs.reduce((acc, cur) => {
  const nestedArrs = cur.filter(a => Array.isArray(a))
  const normalItems = cur.filter(a => !Array.isArray(a))
  acc.push(normalItems, ...extractChildArrays(nestedArrs))
  return acc
}, [])

console.log(extractChildArrays(data))


UPDATE: Array.flat() is now accepted as part of the spec and it's supported by all modern browsers except Edge.

In ES6 there is actually an experimental array method called flat(). As of the writing of this answer, it's only compatible with Chrome, but a polyfill might be worth looking into, because it's SO EASY!

The first parameter of flat() is depth, so with the help of another answer, you can easily figure that out dynamically.

const data = arrs = [
  [1, 2, [3, 4], 5],
  [6, [7, 8, 9, [10, 11]]],
  [12, 13],
  [[14, 15], [16, 17]],
  [[1], 4, [1, 1], 4]
]

const flattenArray = arr => {
  const getDepth = a => Array.isArray(a) ? 
    1 + Math.max(...a.map(getDepth)) : 0
  return arr.flat(getDepth(arr))
}

console.log(flattenArray(data))

7 Comments

This isn't the output I'm looking for. Thanks, anyway.
@mhodges Ah, my bad, you can still accomplish that with this. The first parameter of flat is depth!
@JonDeWitt but you don't know the depth beforehand
@mhodges I was looking through my old answers today and I figured I would update this.
This is still not the output i'm looking for. I want to keep the arrays intact, I just want them all at the top level. See the output of the accepted answer.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.