Javascript recursive array flattening
Asked Answered
J

28

28

I'm exercising and trying to write a recursive array flattening function. The code goes here:

function flatten() {
    var flat = [];
    for (var i = 0; i < arguments.length; i++) {
        if (arguments[i] instanceof Array) {
            flat.push(flatten(arguments[i]));
        }
        flat.push(arguments[i]);
    }
    return flat;
}

The problem is that if I pass there an array or nested arrays I get the "maximum call stack size exceeded" error. What am I doing wrong?

Jhansi answered 5/5, 2015 at 8:56 Comment(3)
Just as a side-note: #10865525Compensable
flatten.apply(this, arguments[i]); but it's not the only issue.Primer
Does this answer your question? Merge/flatten an array of arraysCombe
A
40

The problem is how you are passing the processing of array, if the value is an array then you are keep calling it causing an infinite loop

function flatten() {
    var flat = [];
    for (var i = 0; i < arguments.length; i++) {
        if (arguments[i] instanceof Array) {
            flat.push.apply(flat, flatten.apply(this, arguments[i]));
        } else {
            flat.push(arguments[i]);
        }
    }
    return flat;
}

Demo: Fiddle

Here's a more modern version:

function flatten(items) {
  const flat = [];

  items.forEach(item => {
    if (Array.isArray(item)) {
      flat.push(...flatten(item));
    } else {
      flat.push(item);
    }
  });

  return flat;
}
Amorist answered 5/5, 2015 at 9:7 Comment(4)
Here is another modern one: function flatten(array, accu = []) { array.forEach(a => { if(Array.isArray(a)) flatten(a, accu) else accu.push(a) }) return accu }Erzurum
Why don't the declarations of flat in the recursive calls of flatten() mask the outermost flat variable?Thissa
@Thissa I don't mean to sound sarcastic, but its because it is in fact, the outermost flat variable. The underlying flats in each successive frame are ultimately returned and then pushed into the top level flat where it is finally returned.Madalynmadam
why are you using forEach() instead of for...of?Secretory
G
28

The clean way to flatten an Array in 2019 with ES6 is flat()

Short Answer:

array.flat(Infinity)

Detailed Answer:

const array = [1, 1, [2, 2], [[3, [4], 3], 2]]

// All layers
array.flat(Infinity) // [1, 1, 2, 2, 3, 4, 3, 2]

// Varying depths
array.flat() // [1, 1, 2, 2, Array(3), 2]

array.flat(2) // [1, 1, 2, 2, 3, Array(1), 3, 2]
array.flat().flat() // [1, 1, 2, 2, 3, Array(1), 3, 2]

array.flat(3) // [1, 1, 2, 2, 3, 4, 3, 2]
array.flat().flat().flat() // [1, 1, 2, 2, 3, 4, 3, 2]

Mozilla Docs

Can I Use - 96% Oct '23

Grapefruit answered 26/9, 2019 at 22:20 Comment(0)
A
8

If the item is array, we simply add all the remaining items to this array

function flatten(array, result) {
  if (array.length === 0) {
    return result
  }
  var head = array[0]
  var rest = array.slice(1)
  if (Array.isArray(head)) {
    return flatten(head.concat(rest), result)
  }
  result.push(head)
  return flatten(rest, result)
}

console.log(flatten([], []))
console.log(flatten([1], []))
console.log(flatten([1,2,3], []))
console.log(flatten([1,2,[3,4]], []))
console.log(flatten([1,2,[3,[4,5,6]]], []))
console.log(flatten([[1,2,3],[4,5,6]], []))
console.log(flatten([[1,2,3],[[4,5],6,7]], []))
console.log(flatten([[1,2,3],[[4,5],6,[7,8,9]]], []))
Anikaanil answered 18/3, 2018 at 5:34 Comment(1)
This should be the chosen answer. It does not use any iterative loops.Saccharide
A
6
[...arr.toString().split(",")]

Use the toString() method of the Object. Use a spread operator (...) to make an array of string and split it by ",".

Example:

let arr =[["1","2"],[[[3]]]]; // output : ["1", "2", "3"]
Alidia answered 31/7, 2019 at 13:50 Comment(1)
This will turn numbers into strings and will break apart strings that include commas.Goatish
C
5

A Haskellesque approach...

function flatArray([x,...xs]){
  return x !== undefined ? [...Array.isArray(x) ? flatArray(x) : [x],...flatArray(xs)]
                         : [];
}

var na = [[1,2],[3,[4,5]],[6,7,[[[8],9]]],10],
    fa = flatArray(na);
console.log(fa);

So i think the above code snippet could be made easier to understand with proper indenting;

function flatArray([x,...xs]){
  return x !== undefined ? [ ...Array.isArray(x) ? flatArray(x)
                                                 : [x]
                           , ...flatArray(xs)
                           ]
                         : [];
}

var na = [[1,2],[3,[4,5]],[6,7,[[[8],9]]],10],
    fa = flatArray(na);
console.log(fa);
Coarse answered 1/6, 2017 at 15:42 Comment(0)
C
4

If you assume your first argument is an array, you can make this pretty simple.

function flatten(a) {
    return a.reduce((flat, i) => {
      if (Array.isArray(i)) {
        return flat.concat(flatten(i));
      }
      return flat.concat(i);
    }, []);
  }

If you did want to flatten multiple arrays just concat them before passing.

Containerize answered 15/3, 2018 at 22:30 Comment(0)
M
3

If someone looking for flatten array of objects (e.g. tree) so here is a code:

function flatten(items) {
  const flat = [];

  items.forEach(item => {
    flat.push(item)
    if (Array.isArray(item.children) && item.children.length > 0) {
      flat.push(...flatten(item.children));
      delete item.children
    }
    delete item.children
  });

  return flat;
}

var test = [
	{children: [
      {children: [], title: '2'}
      ],
  title: '1'},
	{children: [
      {children: [], title: '4'},
      {children: [], title: '5'}
      ],
  title: '3'}
]

console.log(flatten(test))
Myrwyn answered 3/8, 2017 at 12:54 Comment(0)
M
2

Your code is missing an else statement and the recursive call is incorrect (you pass the same array over and over instead of passing its items).

Your function could be written like this:

function flatten() {
    // variable number of arguments, each argument could be:
    // - array
    //   array items are passed to flatten function as arguments and result is appended to flat array
    // - anything else
    //   pushed to the flat array as-is
    var flat = [],
        i;
    for (i = 0; i < arguments.length; i++) {
        if (arguments[i] instanceof Array) {
            flat = flat.concat(flatten.apply(null, arguments[i]));
        } else {
            flat.push(arguments[i]);
        }
    }
    return flat;
}

// flatten([[[[0, 1, 2], [0, 1, 2]], [[0, 1, 2], [0, 1, 2]]], [[[0, 1, 2], [0, 1, 2]], [[0, 1, 2], [0, 1, 2]]]]);
//            [0, 1, 2,   0, 1, 2,     0, 1, 2,   0, 1, 2,       0, 1, 2,   0, 1, 2,     0, 1, 2,   0, 1, 2]
Maas answered 5/5, 2015 at 9:7 Comment(2)
Why don't the declarations of flat in the recursive calls of flatten() mask the outer flat variable?Thissa
@Thissa (if I understand your question correctly) var flat creates a local variable so it won't overwrite the outer flat variable.Maas
C
2

Modern but not crossbrowser

function flatten(arr) {
  return arr.flatMap(el => {
    if(Array.isArray(el)) {
        return flatten(el);
    } else {
      return el;
    }
  });
}
Chrissychrist answered 24/9, 2019 at 9:0 Comment(0)
A
1

This is a Vanilla JavaScript solution to this problem

var _items = {'keyOne': 'valueOne', 'keyTwo': 'valueTwo', 'keyThree': ['valueTree', {'keyFour': ['valueFour', 'valueFive']}]};

// another example
// _items = ['valueOne', 'valueTwo', {'keyThree': ['valueTree', {'keyFour': ['valueFour', 'valueFive']}]}];

// another example
/*_items = {"data": [{
    "rating": "0",
    "title": "The Killing Kind",
    "author": "John Connolly",
    "type": "Book",
    "asin": "0340771224",
    "tags": "",
    "review": "i still haven't had time to read this one..."
}, {
    "rating": "0",
    "title": "The Third Secret",
    "author": "Steve Berry",
    "type": "Book",
    "asin": "0340899263",
    "tags": "",
    "review": "need to find time to read this book"
}]};*/

function flatten() {
    var results = [],
        arrayFlatten;

    arrayFlatten = function arrayFlattenClosure(items) {
        var key;
        
        for (key in items) {
            if ('object' === typeof items[key]) {
                arrayFlatten(items[key]);
            } else {
                results.push(items[key]);
            }
        }
    };

    arrayFlatten(_items);
    
    return results;
}

console.log(flatten());
Armure answered 8/7, 2015 at 4:43 Comment(0)
C
1

Here's a recursive reduce implementation taken from absurdum that mimics lodash's _.concat()

It can take any number of array or non-array arguments. The arrays can be any level of depth. The resulting output will be a single array of flattened values.

export const concat = (...arrays) => {
  return flatten(arrays, []);
}

function flatten(array, initial = []) {
  return array.reduce((acc, curr) => {
    if(Array.isArray(curr)) {
      acc = flatten(curr, acc);
    } else {
      acc.push(curr);
    }
    return acc;
  }, initial);
}

It can take any number of arrays or non-array values as input.

Source: I'm the author of absurdum

Calends answered 7/8, 2019 at 4:26 Comment(0)
T
1

Here you are my functional approach:

const deepFlatten = (array => (array, start = []) => array.reduce((acc, curr) => {
    return Array.isArray(curr) ? deepFlatten(curr, acc) : [...acc, curr];
}, start))();

console.log(deepFlatten([[1,2,[3, 4, [5, [6]]]],7]));
Thalassa answered 11/11, 2019 at 15:1 Comment(1)
A late response, but I'm curious why you would choose the extra IIFE wrapper and your start parameter. Why not just const deepFlatten = (xs) => xs .reduce ((a, x) => [...a, ... (Array .isArray (x) ? deepFlatten (x) : [x])], []) ?Goatish
M
1

A recursive approach to flatten an array in JavaScript is as follows.

function flatten(array) {
    let flatArray = [];
    for (let i = 0; i < array.length; i++) {
        if (Array.isArray(array[i])) {
            flatArray.push(...flatten(array[i]));
        } else {
            flatArray.push(array[i]);
        }
    }
    return flatArray;
}

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

let array2 = [1, 2, [3, [4, 5, 6]]];
console.log(flatten(array2));    
// Output = [ 1, 2, 3, 4, 5, 6 ]
Mislead answered 19/3, 2020 at 10:21 Comment(0)
I
1

The function below flat the array and mantains the type of every item not changing them to a string. It is usefull if you need to flat arrays that not contains only numbers like items. It flat any kind of array with free of side effect.

function flatten(arr) {
  for (let i = 0; i < arr.length; i++) {
    arr = arr.reduce((a, b) => a.concat(b),[])
  }
  return arr
}

console.log(flatten([1, 2, [3, [[4]]]]));
console.log(flatten([[], {}, ['A', [[4]]]]));
Innocency answered 2/3, 2021 at 3:32 Comment(0)
S
1

Another answer in the list of answers, flattening an array with recursion:

let arr = [1, 2, [3, 4, 5, [6, 7, [[8], 9, [10]], [11, 13]], 15], [16, [17]]];
let newArr = [];

function steamRollAnArray(list) {
  for (let i = 0; i < list.length; i++) {
    if (Array.isArray(list[i])) {
      steamRollAnArray(list[i]);
    } else {
      newArr.push(list[i]);
    }
  }
}

steamRollAnArray(arr);
console.log(newArr);

To simplify, check whether the element at an index is an array itself and if so, pass it to the same function. If its not an array, push it to the new array.

Soinski answered 15/3, 2022 at 7:18 Comment(0)
M
0

This should work

function flatten() {
    var flat = [
    ];
    for (var i = 0; i < arguments.length; i++) {
        flat = flat.concat(arguments[i]);
    }
    var removeIndex = [
    ];
    for (var i = flat.length - 1; i >= 0; i--) {
        if (flat[i] instanceof Array) {
            flat = flat.concat(flatten(flat[i]));
            removeIndex.push(i);
        }
    }
    for (var i = 0; i < removeIndex.length; i++) {
        flat.splice(removeIndex - i, 1);
    }
    return flat;
}
Mamie answered 5/5, 2015 at 9:29 Comment(0)
C
0

The other answers already did point to the source of the OP's code malfunction. Writing more descriptive code, the problem literally boils down to an "array-detection/-reduce/-concat-recursion" ...

(function (Array, Object) {


//"use strict";


  var
    array_prototype       = Array.prototype,

    array_prototype_slice = array_prototype.slice,
    expose_internal_class = Object.prototype.toString,


    isArguments = function (type) {
      return !!type && (/^\[object\s+Arguments\]$/).test(expose_internal_class.call(type));
    },
    isArray     = function (type) {
      return !!type && (/^\[object\s+Array\]$/).test(expose_internal_class.call(type));
    },

    array_from  = ((typeof Array.from == "function") && Array.from) || function (listAlike) {
      return array_prototype_slice.call(listAlike);
    },


    array_flatten = function flatten (list) {
      list = (isArguments(list) && array_from(list)) || list;

      if (isArray(list)) {
        list = list.reduce(function (collector, elm) {

          return collector.concat(flatten(elm));

        }, []);
      }
      return list;
    }
  ;


  array_prototype.flatten = function () {
    return array_flatten(this);
  };


}(Array, Object));

borrowing code from one of the other answers as proof of concept ...

console.log([
  [[[0, 1, 2], [0, 1, 2]], [[0, 1, 2], [0, 1, 2]]],
  [[[0, 1, 2], [0, 1, 2]], [[0, 1, 2], [0, 1, 2]]]
].flatten());
//[0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, ..., ..., ..., 0, 1, 2]
Carpentaria answered 15/5, 2015 at 19:0 Comment(0)
M
0

I hope you got all kind of different. One with a combination of recursive and "for loop"/high-order function. I wanted to answer without for loop or high order function.

Check the first element of the array is an array again. If yes, do recursive till you reach the inner-most array. Then push it to the result. I hope I approached it in a pure recursive way.

function flatten(arr, result = []) {
  if(!arr.length) return result;
  (Array.isArray(arr[0])) ? flatten(arr[0], result): result.push(arr[0]);
  return flatten(arr.slice(1),result)
}
Magisterial answered 22/12, 2020 at 3:4 Comment(0)
N
0

I think the problem is the way you are using arguments.

since you said when you pass a nested array, it causes "maximum call stack size exceeded" Error.

because arguments[0] is a reference pointed to the first param you passed to the flatten function. for example:

   flatten([1,[2,[3]]]) // arguments[0] will always represents `[1,[2,[3]]]`

so, you code ends up calling flatten with the same param again and again.

to solve this problem, i think it's better to use named arguments, rather than using arguments, which essentially not a "real array".

Navarra answered 6/2, 2021 at 20:11 Comment(0)
C
0

There are few ways to do this:

  1. using the flat method and Infinity keyword:

    const flattened = arr.flat(Infinity);

  2. You can flatten any array using the methods reduce and concat like this:

    function flatten(arr) { return arr.reduce((acc, cur) => acc.concat(Array.isArray(cur) ? flatten(cur) : cur), []); };

Read more at: https://www.techiedelight.com/recursively-flatten-nested-array-javascript/

Cobia answered 6/7, 2021 at 16:41 Comment(0)
C
0
const nums = [1,2,[3,4,[5]]];
const chars = ['a',['b','c',['d',['e','f']]]];
const mixed = ['a',[3,6],'c',[1,5,['b',[2,'e']]]];  

const flatten = (arr,res=[]) => res.concat(...arr.map((el) => (Array.isArray(el)) ? flatten(el) : el));

console.log(flatten(nums)); // [ 1, 2, 3, 4, 5 ]
console.log(flatten(chars)); // [ 'a', 'b', 'c', 'd', 'e', 'f' ]
console.log(flatten(mixed)); // [ 'a', 3, 6, 'c', 1, 5, 'b', 2, 'e' ]

Here is the breakdown:

  1. loop over "arr" with "map"

arr.map((el) => ...)

  1. on each iteration we'll use a ternary to check whether each "el" is an array or not

(Array.isArray(el))

  1. if "el" is an array, then invoke "flatten" recursively and pass in "el" as its argument

flatten(el)

  1. if "el" is not an array, then simply return "el"

: el

  1. lastly, concatenate the outcome of the ternary with "res"

res.concat(...arr.map((el) => (Array.isArray(el)) ? flatten(el) : el));

--> the spread operator will copy all the element(s) instead of the array itself while concatenating with "res"

Chapter answered 17/9, 2021 at 15:48 Comment(0)
H
0

var nestedArr = [1, 2, 3, [4, 5, [6, 7, [8, [9]]]], 10];
let finalArray = [];
const getFlattenArray = (array) => {

    array.forEach(element => {
        if (Array.isArray(element)) {
            getFlattenArray(element)
        } else {
            finalArray.push(element)
        }
    });

}

getFlattenArray(nestedArr);
In the finalArray you will get the flattened array
Henka answered 8/12, 2021 at 13:18 Comment(1)
Please don't add duplicate answers to several existing questions.Goatish
J
0

Solution using forEach

function flatten(arr) {
  const flat = [];
  arr.forEach((item) => {
    Array.isArray(item) ? flat.push(...flatten(item)) : flat.push(item);
  });
  return flat;  
}

Solution using reduce

function flatten(arr) {
  return arr.reduce((acc, curr) => {
    if (Array.isArray(curr)) {
      return [...acc, ...flatten(curr)];
    } else {
      return [...acc, curr];
    }
  }, []);
}
Justness answered 15/12, 2021 at 19:52 Comment(0)
H
0

I think you are very close. One of the problems are that you call the flatten function with the same arguments. We can make use of the spread operator (...) to make sure we are calling flatten on the array inside of arguments[i], and not repeating the same arguments.

We also need to make a few more adjustments so we're not pushing more items into our array than we should

function flatten() {
    var flat = [];
    for (var i = 0; i < arguments.length; i++) {
        if (arguments[i] instanceof Array) {
            flat.push(...flatten(...arguments[i]));
        } else {
            flat.push(arguments[i]);
        }
    }
    return flat;
}
console.log(flatten([1,2,3,[4,5,6,[7,8,9]]],[10,11,12]));
Hesychast answered 25/6, 2022 at 23:55 Comment(0)
R
0
function flatArray(input) {
  if (input[0] === undefined) return [];
  if (Array.isArray(input[0]))
     return [...flatArray(input[0]), ...flatArray(input.slice(1))];
  return [input[0], ...flatArray(input.slice(1))];
}
Robinrobina answered 15/12, 2022 at 18:47 Comment(1)
Your answer could be improved by adding more information on what the code does and how it helps the OP.Belsen
S
0

Here is my exampl

let arra = [1, [3,4], [5,6,4], 8]
let flatten2 = (oneArray) => {
var finalArray = [] 
for(var i=0;i<oneArray.length; i++){
if(Array.isArray(oneArray[i])){
finalArray.push(...oneArray[i])
} else{
finalArray.push(oneArray[i])
}
}
return finalArray
}
console.log(flatten2(arra))

e without recursion. Its simply using ES6

let flatten2 = (oneArray) => {
  var finalArray = [] 
  for(var i=0;i<oneArray.length; i++){
   if(Array.isArray(oneArray[i])){
     finalArray.push(...oneArray[i])
   } else {
  finalArray.push(oneArray[i])
}
}
return finalArray
 }
 console.log(flatten2(arra)
Standby answered 5/6 at 4:59 Comment(0)
T
-1

you should add stop condition for the recursion .

as an example if len (arguments[i]) ==0 return

Tel answered 5/5, 2015 at 9:0 Comment(2)
Why doesn't it just stop when i==arguments.length is true?Jhansi
so, what's the problem?(Jhansi
D
-1

I have posted my recursive version of array flattening here in stackoverflow, at this page.

Driveway answered 18/4, 2021 at 8:18 Comment(1)
This is a link to an answer, not an answer. If you are linking to a question that is the same as this question, flag this question as a duplicate of that one. If you feel the questions are different, tailor the linked answer to this question and post it here.Honestly

© 2022 - 2024 — McMap. All rights reserved.