In the below function I am attempting to get an output which resembles this:
[[1,1,1,1],[2,2,2], 4,5,10,[20,20], 391, 392,591].
I can see that the problem I have embedded is that I am always adding the temp array with a push to the functions return, as a result, all of the individual numbers apart from the last number in the for each function are being pushed into the target array with the array object also.
I feel as though I need a further conditonal check but for the life of me I am unable to come up with solution which works.
Any suggestions would be much appreciated.
const sortme = (unsortedArr)=> {
let tempArr = [];
let outputArr = [];
const reorderedArr = unsortedArr.sort((a,b) => a-b);
reorderedArr.forEach((number, i) => {
if ((i === 0) || (reorderedArr[i] === reorderedArr[i-1])) {
tempArr.push(number);
}
else {
outputArr.push(tempArr);
tempArr = [];
tempArr.push(number);
}
})
outputArr.push(tempArr[0]);
return outputArr;
}
const unsortedArr = [1,2,4,591,392,391,2,5,10,2,1,1,1,20,20];
sortme(unsortedArr);
i would make a deduped copy and .map() it to transform the values into arrays containing values from the original ( sorted ) array that you get using a .forEach :
const unsortedArr = [1, 2, 4, 591, 392, 391, 2, 5, 10, 2, 1, 1, 1, 20, 20];
const sortMe = (arr) => {
arr = arr.sort((a, b) => a - b);
// a short way to dedupe an array
// results in : 1, 2, 4, 5, 10, 20, 391, 392, 591
let dedupe = [...new Set(arr)];
let tmpArr;
return dedupe.map(e => {
tmpArr = []; // empty tmpArr on each iteration
// for each element of the deduped array, look for matching elements in the original one and push them in the tmpArr
arr.forEach(a => {
if (a === e)
tmpArr.push(e);
})
if(tmpArr.length === 1)
return tmpArr[0]; // in case you have [4] , just return the 4
else
return tmpArr; // in case you have [1,1,1,1]
// shorthand for the if/else above
// return tmpArr.length === 1 ? tmpArr[0] : tmpArr;
});
}
const result = sortMe(unsortedArr);
console.log(result);
This should work (using reduce):
const unsortedArr = [1,2,4,591,392,391,2,5,10,2,1,1,1,20,20];
let lastValue = null;
var newArr = unsortedArr.sort((a,b) => a-b).reduce((acc, value) => {
if (acc.length == 0 || ((acc.length > 0 || !acc[acc.length-1].length) && lastValue !== value)) {
acc.push(value);
} else if (acc.length > 0 && lastValue === value) {
acc[acc.length-1] = (acc[acc.length-1].length ? acc[acc.length-1].concat([value]): [value, value]);
}
lastValue = value;
return acc;
}, []);
console.log(newArr);
And another approach, just for fun:
const unsortedArr = [1,2,4,591,392,391,2,5,10,2,1,1,1,20,20];
var arr = unsortedArr.sort((a,b) => a-b).reduce((acc, value) => {
if (acc.length > 0 && acc[acc.length-1].includes(value)) {
acc[acc.length-1].push(value);
} else {
acc.push([value])
}
return acc;
}, []).map((v) => v.length > 1 ? v: v[0]);
console.log(arr);
I hope the below one is quite simple;
function findSame(pos, sortedArr){
for(let i =pos; i<sortedArr.length; i++){
if(sortedArr[i] !== sortedArr[pos]){
return i
}
}
}
function clubSameNumbers(unsortedArr){
let sortedArr = unsortedArr.sort((a,b)=>a-b)
//[ 1, 1, 1, 1, 2, 2, 2, 4, 5, 10, 20, 20, 391, 392, 591 ]
let result = []
for(let i = 0; i < sortedArr.length; i = end){
let start = i
var end = findSame(i, sortedArr)
let arr = sortedArr.slice(i, end)
arr.length > 1 ? result.push(arr) : result.push(...arr)
}
return result
}
console.log(clubSameNumbers([1,2,4,591,392,391,2,5,10,2,1,1,1,20,20]))
//[ [ 1, 1, 1, 1 ], [ 2, 2, 2 ], 4, 5, 10, [ 20, 20 ], 391, 392, 591 ]
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'
}]));
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.