0

Input:

[1,2,[3,4,[5,6]]]

Output:

[[1,2],[3,4],[5,6]]

Here is the solution:

function convert(a,res=[]) {
  const group = (arr) => {
    res.push(arr.slice(0,2));
    arr.map((v) => Array.isArray(v) && group(v));
  }
  group(a);
  return res;
}

console.log(convert([1,2,[3,4]])); // [[1,2],[3,4]]
console.log(convert([1,2,[3,4,[5,6]]])); // [[1,2],[3,4],[5,6]]
console.log(convert([1,2,[3,4,[5,6,[7,8]]]])); // [[1,2],[3,4],[5,6],[7,8]];

Although the problem is solved, For learning purposes, I've always wonder how to solve this without the nesting function approach. I've tried to refactor the code as followed:

function convert(a,i=0,res=[]) {
  return i >= a.length
    ? res
    : convert(
      a,
      i+1,
      Array.isArray(a[i]) ? [...res,a.slice(0,2)] : res
    )
}

console.log(convert([1,2,[3,4]])); // [[1,2]]
console.log(convert([1,2,[3,4,[5,6]]])); // [[1,2]]
console.log(convert([1,2,[3,4,[5,6,[7,8]]]])); // [[1,2]]

As you can see, the result is less than ideal. I just couldn't quite wrap my head around it. Any feedback and pointer will be greatly appreciated :)

UPDATE:

Here is the solution which covers even more test cases:

function convert(a,res=[]) {
  return !a.length
    ? res
    : convert(
      a.filter(Array.isArray).flat(),
      [...res,a.filter((v) => !Array.isArray(v))]
    );
}

console.log(convert([1,2,[3,4]])); // [[1,2],[3,4]]
console.log(convert([1,2,[3,4,[5,6]]])); // [[1,2],[3,4],[5,6]]
console.log(convert([1,2,[3,4,[5,6,[7,8]]]])); // [[1,2],[3,4],[5,6],[7,8]];
console.log(convert([1,2,[5,6,[9,10],7,8],3,4])); // [[1,2,3,4],[5,6,7,8],[9,10]]
console.log(convert([1,5,5,[5,[1,2,1,1],5,5],5,[5]])); // [[1,5,5,5],[5,5,5,5],[1,2,1,1]]
console.log(convert([1,[2],1,[[2]],1,[[[2]]],1,[[[[2]]]]])); // [[1,1,1,1],[2],[2],[2],[2]]
noirsociety
  • 314
  • 1
  • 2
  • 9

2 Answers2

2

I'm not sure if this is what you mean, but you can use a straight recursive call, spreading each result into the previous.

const input = [1, 2, [3, 4, [5, 6]]];

function flatten(arr) {
  const res = [[]];
  for (const e of arr) {
    Array.isArray(e) ? res.push(...flatten(e)) : res[0].push(e);
  }

  return res;
}

console.log(flatten(input));

This works even if the elements on any level are split by nested levels, and for variable numbers of elements on any given level.

const input = [1, 2, [4, [5, 6, [8, 9], 7]], 3]; 
// [[ 1, 2, 3 ], [ 4 ], [ 5, 6, 7 ], [ 8, 9 ]]

function flatten(arr) {
  const res = [[]];
  for (const e of arr) {
    Array.isArray(e) ? res.push(...flatten(e)) : res[0].push(e);
  }

  return res;
}

console.log(flatten(input));

Edit

To accommodate the added conditions brought up in the comments, but maintain straight recursion without passing an accumulator to later calls, I might do something like the following.

function flatten(arr) {
  const level = [], nested = [];

  for (const e of arr) {
    Array.isArray(e) ? nested.push(...e) : level.push(e);
  }

  return [level, ...(nested.length ? flatten(nested) : [])]
}

console.log(flatten([1, 2, [3, 4, [5, 6]]]));
// [[ 1, 2 ], [ 3, 4 ], [ 5, 6 ]]

console.log(flatten([1, 2, [4, [5, 6, [8, 9], 7]], 3]));
// [[ 1, 2, 3 ], [ 4 ], [ 5, 6, 7 ], [ 8, 9 ]]

console.log(flatten([1, [2], 1, [[2]], 1, [[[2]]], 1, [[[[2]]]]]));
// [[ 1, 1, 1, 1 ], [ 2 ], [ 2 ], [ 2 ], [ 2 ]]

console.log(flatten([1, 5, 5, [5, [1, 2, 1, 1], 5, 5], 5, [5]]));
// [[ 1, 5, 5, 5 ], [ 5, 5, 5, 5 ], [ 1, 2, 1, 1 ]]
pilchard
  • 12,414
  • 5
  • 11
  • 23
  • Thanks for the reply. The code is elegant & work quite well except for the following test case: `[1,5,5,[5,[1,2,1,1],5,5],5,[5]]` which should rendered `[[1,5,5,5],[5,5,5,5],[1,2,1,1]].` instead, however, it rendered `[[1,5,5,5],[5,5,5],[1,2,1,1],[5]]` – noirsociety Oct 19 '22 at 11:46
  • here is another test case with funky outcome `[1,[2],1,[[2]],1,[[[2]]],1,[[[[2]]]]]` which should rendered `[[1,1,1,1],[2],[2],[2],[2]].` instead we get this: `[1,1,1,1],[2],[],[2],[],[],[2],[],[],[],[2]].` – noirsociety Oct 19 '22 at 11:51
  • You're adding conditions to your original question. There was no indication that there would be multiple nested arrays at the same level, and no mention of how to handle empty levels. You'll need to post a new question with all the conditions if you want a more complete answer. – pilchard Oct 19 '22 at 12:08
  • programming is like life. It changes all the time. If it wasn't for your second example, I would have been content with your solution. Though I appreciate you sharing your knowledge with me, a solution should definitely evolve to cover more ground if needed be. Nevertheless, here is the solution to cover all bases: `function convert(a,res=[]) { return !a.length ? res : convert(a.filter(Array.isArray).flat(),[...res,a.filter((v) => !Array.isArray(v))]); }` – noirsociety Oct 19 '22 at 12:47
0

I think your updated algorithm is fine.

It might be easier to express with a reusable utility function, though. I usually have a partition function lying around which accepts a predicate function and returns a function which splits an array into two sub-arrays, those for which the predicate returns true, and those for which it's false.

Using that, this becomes fairly simple:

const partition = (pred) => (xs) => 
  xs .reduce (([t, f], x) => pred (x) ? [t .concat (x), f]: [t, f .concat (x)], [[], []])

const convert = (xs, [rest, first] = partition (Array .isArray) (xs)) =>
  xs .length == 0 ? [] : [first, ...convert (rest)]

console.log (convert ([1, 2, [3, 4]])) //=>  [[1, 2], [3, 4]]
console.log (convert ([1, 2, [3, 4, [5, 6]]])) //=>  [[1, 2], [3, 4], [5, 6]]
console.log (convert ([1, 2, [3, 4, [5, 6, [7, 8]]]])) //=> [[1, 2], [3, 4], [5, 6], [7, 8]]
console.log (convert ([1, 2, [5, 6, [9, 10], 7, 8], 3, 4])) //=> [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]
console.log (convert ([1, 5, 5, [5, [1, 2, 1, 1], 5, 5], 5, [5]])) //=> [[1, 5, 5, 5], [5, 5, 5, 5], [1, 2, 1, 1]]
console.log (convert ([1, [2], 1, [[2]], 1, [[[2]]], 1, [[[[2]]]]])) //=> [[1, 1, 1, 1], [2], [2], [2], [2]]
.as-console-wrapper {max-height: 100% !important; top: 0}
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • This is pretty much the same as my last edit except you've split out the partition into its own utility. May as well link to [Dividing an array by filter function](https://stackoverflow.com/questions/11731072/dividing-an-array-by-filter-function) to expand on the partitioning for the OP – pilchard Oct 19 '22 at 14:47
  • @pilchard: Yes, and the same as the update in the question. As the text reads, this was just pointing out that it becomes even simpler built atop a reusable utility function. Thanks for the link! – Scott Sauyet Oct 19 '22 at 14:49