Javascript recursive array flattening - javascript

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?

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;
}

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 - 95% Jul '22

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]]], []))

[...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"]

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);

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.

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))

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]

Modern but not crossbrowser
function flatten(arr) {
return arr.flatMap(el => {
if(Array.isArray(el)) {
return flatten(el);
} else {
return el;
}
});
}

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());

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

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]));

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 ]

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]]]]));

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.

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;
}

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]

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)
}

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".

There are few ways to do this:
using the flat method and Infinity keyword:
const flattened = arr.flat(Infinity);
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/

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:
loop over "arr" with "map"
arr.map((el) => ...)
on each iteration we'll use a ternary to check whether each "el" is an array or not
(Array.isArray(el))
if "el" is an array, then invoke "flatten" recursively and pass in "el" as its argument
flatten(el)
if "el" is not an array, then simply return "el"
: el
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"

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

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];
}
}, []);
}

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]));

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))];
}

you should add stop condition for the recursion .
as an example
if len (arguments[i]) ==0 return

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

Related

Get specific data from array and put in other array

I have this result in javascript and i want to get data that has value more that 3 and i want to put in other array .
"availableDates": {
"2020-01-24": 1,
"2020-01-23": 3,
"2020-01-22": 2,
"2020-01-21": 1,
"2020-01-25": 4,
"2021-01-07": 1
}
I group here :
const formattedDate = x.reduce((acc,el) => {
const date = el.split(" ")[0];
acc[date] = (acc[date] || 0) + 1;
return acc;
}, {});
now I want to put in other array all that date that has value more than 3 . For example
newarray = [ "2020-01-23", "2020-01-25" ]
Why don't use a simple .filter() over keys of "availableDates":
const grouped = {
"availableDates": {
"2020-01-24": 1,
"2020-01-23": 3,
"2020-01-22": 2,
"2020-01-21": 1,
"2020-01-25": 4,
"2021-01-07": 1
}
};
const newArray = Object.keys(grouped.availableDates).filter((key) => grouped.availableDates[key] >= 3);
console.log(newArray);
You can simply use a for...in loop to iterate over object keys and filter them:
const data = {
"2020-01-24": 1,
"2020-01-23": 3,
"2020-01-22": 2,
"2020-01-21": 1,
"2020-01-25": 4,
"2021-01-07": 1
};
const reducer = (obj, val) => {
const result = [];
for(key in obj) {
if(obj[key] >= val)
result.push(key);
};
return result;
};
console.log(reducer(data, 3));
You could have something like this. I write a complete bunch of the code to make you able to copy/past to test
var availableDates = new Array()
var availableDates = {
"2020-01-24": 1,
"2020-01-23": 3,
"2020-01-22": 2,
"2020-01-21": 1,
"2020-01-25": 4,
"2021-01-07": 1
}
var results = new Array();
for (date in availableDates){
if (availableDates[date] >= 3){
results.push(date)
}
}
console.log(results)

Multidimensional array comparison in Javascript

My input is
let data = [
[1,2,3],
[1,3,2,4],
[3,2,1,5],
[1,2,3],
[3,2,1]
];
after this peace of code:
var dataUnique = data.reduce(function (out, item) {
return out.concat(out.filter(function (comp) {
return item.toString() == comp.toString();
}).length ? [] : [item])
}, []);
console.log(data, dataUnique);
Output give me array of 4 element
[1,2,3],
[1,3,2,4],
[3,2,1,5],
[3,2,1]
but expected output would be
[1,2,3],
[1,3,2,4],
[3,2,1,5]
Can anyone suggest any solution.
Thanks.
You can create some sort of hash — on object, Map, Set, etc and use a stringified version of your input as keys. Here's an example using a Set:
let data = [
[1,2,3],
[1,3,2,4],
[3,2,1,5],
[1,2,3],
[3,2,1]
];
let set = new Set()
let result = data.reduce((a, i) => {
let k = i.concat().sort().join('_')
if (!set.has(k)) {
set.add(k)
a.push(i)
}
return a
}, [])
console.log(result)
This could be a little simpler if you didn't mind the output having sorted versions of your input.
This is an alternative using the functions reduce, every and includes.
Basically, this approach checks if one number doesn't exist within the previously checked arrays.
let data = [ [1, 2, 3], [1, 3, 2, 4], [3, 2, 1, 5], [1, 2, 3], [3, 2, 1]],
result = data.reduce((a, c) => {
c.forEach(n => {
if (a.length == 0 || !a.every(arr => arr.includes(n))) a.push(c);
});
return a;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Trying to avoid duplicates when creating new array from comparing value of two others

I have an app where I need to create a new array by pushing values from two other arrays after comparing what values in one array exist in another.
Example:
From these two arrays...
sel[1,4];
bus[1,2,3,4,5,6];
The desired result is a new object array which will populate a repeater of checkboxes in my view...
newList[{1:true},{2:false},{3:false},{4:true},{5:false},{6:false}];
The problem I'm running into, is that my code is creating duplicates and I'm not seeing why.
Here is my code:
var newList = [];
var bus = self.businesses;
var sel = self.campaign.data.businesses;
for( var b = 0; b < bus.length; b++ ){
if(sel.length > -1){
for( var s = 0; s < sel.length; s++){
if( bus[b]._id === sel[s].business_id){
newList.push({'business_id':bus[b]._id, 'name':bus[b].business_name, 'selected':true});
} else {
newList.push({'business_id':bus[b]._id, 'name':bus[b].business_name, 'selected':false});
}
}
} else {
console.log('hit else statement');
newList.push({'business_id':bus[b]._id, 'name':bus[b].business_name, 'selected':false});
}
}
I need fresh eyes on this as it looks correct to me... but obviously I'm missing something. :-)
Your code produces duplicates because you push selected: false objects into your newList every time the inner loop is run and the ids don't match:
for( var s = 0; s < sel.length; s++){
if( bus[b]._id === sel[s].business_id){
newList.push({'business_id':bus[b]._id, 'name':bus[b].business_name, 'selected':true});
} else {
// THIS LINE CAUSES THE DUPLICATES:
newList.push({'business_id':bus[b]._id, 'name':bus[b].business_name, 'selected':false});
}
}
To fix your code, move this line out of the inner loop into the outer loop below and add a continue outer; to the inner loop's if body. Then you need to place the outer label directly in front of the outer loop: outer: for( var b = 0; b < bus.length; b++ ) ....
However, I recommend a simpler implementation as follows:
let selection = [{_id: 1, business_name: 'A'}];
let businesses = [{_id: 1, business_name: 'A'}, {_id: 2, business_name: 'B'}];
let result = businesses.map(business => ({
'business_id': business._id,
'name': business.business_name,
'selected': selection.some(selected => business._id == selected._id)
}));
console.log(result);
Appendix: Same implementation with traditional functions:
var selection = [{_id: 1, business_name: 'A'}];
var businesses = [{_id: 1, business_name: 'A'}, {_id: 2, business_name: 'B'}];
var result = businesses.map(function(business) {
return {
'business_id': business._id,
'name': business.business_name,
'selected': selection.some(function(selected) { return business._id == selected._id })
};
});
console.log(result);
I suggest to use a different approach by using an object for sel and the just iterate bus for the new array with the values.
function getArray(items, selected) {
var hash = Object.create(null);
selected.forEach(function (a) {
hash[a] = true;
});
return items.map(function (a) {
var temp = {};
temp[a] = hash[a] || false;
return temp;
});
}
console.log(getArray([1, 2, 3, 4, 5, 6], [1, 4]));
ES6 with Set
function getArray(items, selected) {
return items.map((s => a => ({ [a]: s.has(a) }))(new Set(selected)));
}
console.log(getArray([1, 2, 3, 4, 5, 6], [1, 4]));
You can use map() method on bus array and check if current value exists in sel array using includes().
var sel = [1,4];
var bus = [1,2,3,4,5,6];
var result = bus.map(e => ({[e] : sel.includes(e)}))
console.log(result)
This combines both Nina Scholz elegant ES6 approach with le_m's more specific solution to give you something that is shorter, versatile, and repurposable.
function getArray(items, selected, [...id] = selected.map(selector => selector._id)) {
return [items.map((s => a => ({
[a._id + a.business_name]: s.has(a._id)
}))(new Set(id)))];
}
console.log(...getArray([{
_id: 1,
business_name: 'A'
}, {
_id: 2,
business_name: 'B'
}, {
_id: 3,
business_name: 'C'
}, {
_id: 4,
business_name: 'D'
}, {
_id: 5,
business_name: 'E'
}, {
_id: 6,
business_name: 'F'
}], [{
_id: 1,
business_name: 'A'
}, {
_id: 2,
business_name: 'B'
}]));

Remove duplicates from arrays using reduce

I am trying to remove duplicates from a list of arrays. The way I was trying to do this is by using reduce to create an empty array that pushes all undefined indexes onto that array. I am getting errors though that
if(acc[item]===undefined){
^
TypeError: Cannot read property '1' of undefined
my function below:
function noDuplicates(arrays) {
var arrayed = Array.prototype.slice.call(arguments);
return reduce(arrayed, function(acc, cur) {
forEach(cur, function(item) {
if (acc[item] === undefined) {
acc.push(item);
}
return acc;
});
}, []);
}
console.log(noDuplicates([1, 2, 2, 4], [1, 1, 4, 5, 6]));
First concatenate the two arrays, next use filter() to filter out only the unique items-
var a = [1, 2, 2, 4], b = [1, 1, 4, 5, 6];
var c = a.concat(b);
var d = c.filter(function (item, pos) {return c.indexOf(item) == pos});
console.log(d);
There are a number of issues with how you're calling methods and where you return acc from:
function noDuplicates(arrays) {
var arrayed = Array.prototype.slice.call(arguments);
// reduce is a method of an array, so call it as a method
// return reduce(arrayed, function(acc, cur) {
return arrayed.reduce(function(acc, cur) {
// Same with forEach
cur.forEach(function(item) {
if (acc[item] === undefined) {
acc.push(item);
}
// Return acc from the reduce callback, forEach returns undefined always
// return acc;
});
return acc;
}, []);
}
console.log(noDuplicates([1, 2, 2, 4], [1, 1, 4, 5, 6]));
You could also call reduce directly on arguments using call:
Array.prototype.reduce.call(arguments, function(acc, curr) {
// ...
});
The above makes your code run, but it doesn't produce the correct output as the test:
if (acc[item] === undefined)
doesn't do what you want. What you need to do is remember each value and only push it to acc if it's not been seen before:
function noDuplicates(arrays) {
var arrayed = Array.prototype.slice.call(arguments);
var seen = {};
return arrayed.reduce(function(acc, cur) {
cur.forEach(function(item) {
if (!seen[item]) {
acc.push(item);
seen[item] = true;
}
});
return acc;
}, []);
}
console.log(noDuplicates([1, 2, 2, 4], [1, 1, 4, 5, 6]));
Some other approaches:
// A more concise version of the OP
function noDupes() {
return [].reduce.call(arguments, function(acc, arr) {
arr.forEach(function(value) {
if (acc.indexOf(value) == -1) acc.push(value);
});
return acc;
},[]);
}
console.log(noDupes([1, 2, 2, 4], [1, 1, 4, 5, 6]));
// Some ECMAScript 2017 goodness
function noDupes2(...args){
return [].concat(...args).filter((v, i, arr) => arr.indexOf(v)==i);
}
console.log(noDupes2([1, 2, 2, 4], [1, 1, 4, 5, 6]));
My Solution is -
var numbers = [1, 1, 2, 3, 4, 4];
function unique(array){
return array.reduce(function(previous, current) {
if(!previous.find(function(prevItem){
return prevItem === current;
})) {
previous.push(current);
}
return previous;
}, []);
}
unique(numbers);
Any reason of using reduce? because we can do this easily by first merging these two arrays then by using Set to remove the duplicate keys.
Check this:
function noDuplicates(a, b){
var k = a.concat(b);
return [...new Set(k)];
}
console.log(noDuplicates([1,2,2,4],[1,1,4,5,6]));
Check the DOC, how Set works.
looking for a smoother solution for MDN's exact same problem, I've came up with that solution, I find it simple and nice. I have also just updated it in MDN and wanted to share it here (I'm really new to that stuff, so sorry if did something wrong)
let myArray = ['a', 'b', 'a', 'b', 'c', 'e', 'e', 'c', 'd', 'd', 'd', 'd'];
var myOrderedArray = myArray.reduce(function (accumulator, currentValue) {
if (accumulator.indexOf(currentValue) === -1) {
accumulator.push(currentValue);
}
return accumulator
}, [])
console.log(myOrderedArray);
(I'm really new to this, hope it'd help)
The previous answers are not optimized for large arrays. The following allows for linear big O notation:
const dedupWithReduce = (arr) =>
arr.reduce(
(acc, cur) => {
if (!acc.lookupObj[cur]) {
return {
lookupObj: {
...acc.lookupObj,
[cur]: true
},
dedupedArray: acc.dedupedArray.concat(cur)
};
} else {
return acc;
}
},
{ lookupObj: {}, dedupedArray: [] }
).dedupedArray;

JavaScript flattening an array of arrays of objects

I have an array which contains several arrays, each containing several objects, similar to this.
[[object1, object2],[object1],[object1,object2,object3]]
Here is a screenhot of the object logged to the console.
What would be the best approach to flattening this out so it just an array of objects?
I've tried this with no luck:
console.log(searchData);
var m = [].concat.apply([],searchData);
console.log(m);
searchData logs out the screenshot above, but m logs out [ ]
Here is the actual contents of searchData:
[[{"_id":"55064111d06b96d974937a6f","title":"Generic Title","shortname":"generic-title","contents":"<p>The Healing Center offers practical, social, and spiritual support to individuals and families. Services include, but are not limited to: food and clothing, job skills training and job search assistance, auto repair (Saturdays only), mentoring, financial counseling, tutoring, prayer, life skills training, and helpful information about local community services.</p><p>Stay in touch with us:</p>","__v":0},{"_id":"5508e1405c621d4aad2d2969","title":"test english","shortname":"test-page","contents":"<h2>English Test</h2>","__v":0}],[{"_id":"550b336f33a326aaee84f883","shortname":"ok-url","title":"now english","contents":"<p>okokko</p>","category":"Transportation","__v":0}]]
You can use Array.concat like bellow:-
var arr = [['object1', 'object2'],['object1'],['object1','object2','object3']];
var flattened = [].concat.apply([],arr);
flattened will be your expected array.
ES 2020 gives the flat, also flatMap if you want to iterate over, to flat lists of lists:
[['object1'], ['object2']].flat() // ['object1', 'object2']
A recursive solution for deep (nested) flattening:
function flatten(a) {
return Array.isArray(a) ? [].concat.apply([], a.map(flatten)) : a;
}
A bit more compactly with ES6:
var flatten = a => Array.isArray(a) ? [].concat(...a.map(flatten)) : a;
For fun, using a generator named F for "flatten", to lazily generate flattened values:
function *F(a) {
if (Array.isArray(a)) for (var e of a) yield *F(e); else yield a;
}
>> console.log(Array.from(F([1, [2], 3])));
<< [ 1, 2, 3 ]
For those not familiar with generators the yield * syntax yields values from another generator. Array.from takes an iterator (such as results from invoking the generator function) and turns it into an array.
If you only need simple flatten, this may works:
var arr = [['object1', 'object2'],['object1'],['object1','object2','object3']];
var flatenned = arr.reduce(function(a,b){ return a.concat(b) }, []);
For more complex flattening, Lodash has the flatten function, which maybe what you need: https://lodash.com/docs#flatten
//Syntax: _.flatten(array, [isDeep])
_.flatten([1, [2, 3, [4]]]);
// → [1, 2, 3, [4]];
// using `isDeep` to recursive flatten
_.flatten([1, [2, 3, [4]]], true);
// → [1, 2, 3, 4];
you can use flat() :
const data = [ [{id:1}, {id:2}], [{id:3}] ];
const result = data.flat();
console.log(result);
// you can specify the depth
const data2 = [ [ [ {id:1} ], {id:2}], [{id:3}] ];
const result2 = data2.flat(2);
console.log(result2);
in your case :
const data = [[{"_id":"55064111d06b96d974937a6f","title":"Generic Title","shortname":"generic-title","contents":"<p>The Healing Center offers practical, social, and spiritual support to individuals and families. Services include, but are not limited to: food and clothing, job skills training and job search assistance, auto repair (Saturdays only), mentoring, financial counseling, tutoring, prayer, life skills training, and helpful information about local community services.</p><p>Stay in touch with us:</p>","__v":0},{"_id":"5508e1405c621d4aad2d2969","title":"test english","shortname":"test-page","contents":"<h2>English Test</h2>","__v":0}],[{"_id":"550b336f33a326aaee84f883","shortname":"ok-url","title":"now english","contents":"<p>okokko</p>","category":"Transportation","__v":0}]]
const result = data.flat();
console.log(result);
Using ES6 Spread Operator
Array.prototype.concat(...searchData)
OR
[].concat(...searchData)
You can use this custom recursive method to flattened any nested array
const arr = [
[1, 2],
[3, 4, 5],
[6, [7, 8], 9],
[10, 11, 12]
]
const flatenedArray = arr => {
let result = [];
if(!arr.constructor === Array) return;
arr.forEach(a => {
if(a.constructor === Array) return result.push(...flatenedArray(a));
result.push(a);
});
return result;
}
console.log(flatenedArray(arr)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Recursively flatten an array:
function flatten(array) {
return !Array.isArray(array) ? array : [].concat.apply([], array.map(flatten));
}
var yourFlattenedArray = flatten([[{"_id":"55064111d06b96d974937a6f","title":"Generic Title","shortname":"generic-title","contents":"<p>The Healing Center offers practical, social, and spiritual support to individuals and families. Services include, but are not limited to: food and clothing, job skills training and job search assistance, auto repair (Saturdays only), mentoring, financial counseling, tutoring, prayer, life skills training, and helpful information about local community services.</p><p>Stay in touch with us:</p>","__v":0},{"_id":"5508e1405c621d4aad2d2969","title":"test english","shortname":"test-page","contents":"<h2>English Test</h2>","__v":0}],[{"_id":"550b336f33a326aaee84f883","shortname":"ok-url","title":"now english","contents":"<p>okokko</p>","category":"Transportation","__v":0}]]
);
log(yourFlattenedArray);
function log(data) {
document.write('<pre>' + JSON.stringify(data, null, 2) + '</pre><hr>');
}
* {font-size: 12px; }
let functional = {
flatten (array) {
if (Array.isArray(array)) {
return Array.prototype.concat(...array.map(this.flatten, this));
}
return array;
}
};
functional.flatten([0, [1, 2], [[3, [4]]]]); // 0, 1, 2, 3, 4
I've noticed that people are using recursions which are not cost friendly, especially with new ES6 standards giving us the power of spread operators. When you're pushing the items into the master array just use ... and it will automatically add flattened objects. Something like
array.push(...subarray1) // subarray1 = [object1, object2]
array.push(...subarray2) // subarray2 = [object3]
array.push(...subarray3) // subarray3 = [object4,object5, object6]
// output -> array = [object1, object2, object3, object4, object5, object6]
My solution to flatten an array of objects and return a single array.
flattenArrayOfObject = (arr) => {
const flattened = {};
arr.forEach((obj) => {
Object.keys(obj).forEach((key) => {
flattened[key] = obj[key];
});
});
return flattened;
};
Example
const arr = [
{
verify: { '0': 'xyzNot verified', '1': 'xyzVerified' },
role_id: { '1': 'xyzMember', '2': 'xyzAdmin' },
two_factor_authentication: { '0': 'No', '1': 'Yes' }
},
{ status: { '0': 'xyzInactive', '1': 'Active', '2': 'xyzSuspend' } }
]
flattenArrayOfObject(arr)
// {
// verify: { '0': 'xyzNot verified', '1': 'xyzVerified' },
// status: { '0': 'xyzInactive', '1': 'Active', '2': 'xyzSuspend' },
// role_id: { '1': 'xyzMember', '2': 'xyzAdmin' },
// two_factor_authentication: { '0': 'No', '1': 'Yes' }
// }
If each object has an array and continues in the same way nested :
function flatten(i,arrayField){
if(Array.isArray(i)) return i.map(c=>flatten(c,arrayField));
if(i.hasOwnProperty(arrayField)) return [{...i,[arrayField]:null},...i[arrayField].map(c=>flatten(c,arrayField))];
return {...i,[arrayField]:null};
}
let data=flatten(myData,'childs');
mydata like this :
[
{
"id": 1,
"title": "t1",
"sort_order": 200,
"childs": [
{
"id": 2,
"title": "t2",
"sort_order": 200,
"childs": []
},
{
"id": 3,
"title":"mytitle",
"sort_order": 200,
"childs": []
},
{
"id": 4,
"title":"mytitle",
"sort_order": 200,
"childs": []
},
{
"id": 5,
"title":"mytitle",
"sort_order": 200,
"childs": []
},
{
"id": 6,
"title":"mytitle",
"sort_order": 200,
"childs": []
}
]
},
{
"id": 7,
"title": "راهنما",
"sort_order":"mytitle",
"childs": [
{
"id": 8,
"title":"mytitle",
"sort_order": 200,
"childs": []
},
{
"id": 9,
"title":"mytitle",
"sort_order": 200,
"childs": []
},
{
"id": 10,
"title":"mytitle",
"sort_order": 200,
"childs": []
}
]
}
]
let nestedArray = [[1, 2], [3, 4], [5, 6]];
let flattenArray = function(nestedArray) {
let flattenArr = [];
nestedArray.forEach(function(item) {
flattenArr.push(...item);
});
return flattenArr;
};
console.log(flattenArray(nestedArray)); // [1, 2, 3, 4, 5, 6]
var arr = [1,[9,22],[[3]]];
var res = [];
function flatten(arr){
for(let i=0;i<arr.length;i++){
if(typeof arr[i] == "number"){
res.push(arr[i]);
}
else if(typeof arr[i] == "object"){
fatten(arr[i]);
}
}
}
Calling function
flatten(arr);
console.log(res);
Result
 
[1, 9, 22, 3]
// Polyfill flat method
var flatten = a => Array.isArray(a) ? [].concat(...a.map(flatten)) : a;
var deepFlatten = (arr, depth = 1) => {
return depth > 0 ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? deepFlatten(val, depth - 1) : val), [])
: arr.slice();
}
console.log(deepFlatten([0, 1, 2, [[[3, 4]]]], Infinity));
// You can pass label in place of 'Infinity'

Categories

Resources