Skipping comparison arrays when they are empty - javascript

I have a case that has to compare some arrays together and find the common element between all of them. The following code is working fine as long as all the array are loaded. But what if one (or even 5 of arrays) of the array is/are still empty and not loaded?
In case of having only two arrays I could do something like
if ((arr1.length > 0) && (arr2.length === 0)) {
newArr =arr1;
}
but it is going be a big conditional snippet to check all 6 arrays in this way! How can I fix this so the code runs comparison against arrays only when they are loaded and skip the array(s) when they are empty?
let newArr = [];
function common(arr1, arr2, arr3, arr4,arr5,arr6) {
newArr = arr1.filter(function(e) {
return arr2.indexOf(e) > -1 &&
arr3.indexOf(e) > -1 &&
arr4.indexOf(e) > -1 &&
arr4.indexOf(e) > -1 &&
arr5.indexOf(e) > -1 &&
arr6.indexOf(e) > -1;
});
}
common( [1, 2, 6, 5, 9,8],
[1, 2, 3, 6, 5, 9,8],
[6, 5, 4, 5,8],
[8, 2, 1, 6, 4],
[8, 2, 1, 6, 4],
//[8]
[]
);
$('div').text(newArr);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p> Returns Nothing Because 6th Array is Empty</p>
<div></div>

You are doing it very hard way. Below is a very simple way.You do it in following steps:
Use Rest Parameters instead of arr1,arr2,....
Remove the empty arrays using filter(). Like Array.filter(x => x.length)
Create a object which will contain keys as number. And value as their count.
Use forEach() on array of arrays.
Increment the count of the object by apply forEach() on each of array.
At last filter those keys of object which have count greater than given object.
function common(...arrays) {
let obj = {};
let temp = arrays.filter(x => x.length);
//the below line will check if all the arrays empty
if(!temp.length) console.log("All empty")
temp.forEach(arr => {
arr.forEach(x => {
obj[x] = obj[x] + 1 || 1
})
})
//console.log(temp)
//console.log(Object.keys(obj))
return Object.keys(obj).filter(a => obj[a] === temp.length).map(x => +x || x);
}
let newArr = common( [1, 2, 6, 5, 9,8],
[1, 2, 3, 6, 5, 9,8],
[6, 5, 4, 5,8,1],
[8, 2, 1, 6, 4],
[8, 2, 1, 6, 4],
//[8]
);
console.log(newArr)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div></div>

Since your code is written in ES6, there is a very ES6-way to go about your problem.
What you basically want is to check all items in the first array against n number of arrays provided in the function's parameters/arguments and only return the item that is present in all arrays.
// Use ...arrs to store all subsequent arrays in parameter
function common(arr1, ...arrs) {
return arr1.map(item => {
const nonEmptyArrs = arrs.filter(arr => arr.length);
// Go through all OTHER arrays and see if your item is present in them
const isItemFound = nonEmptyArrs.forEach(arr => {
return arr.indexOf(item) > -1;
});
// Now filter the array to remove all `false` entries
const itemFoundCount = isItemFound.filter(i => i).length;
// Only return item if it is found in ALL arrays
// i.e. itemFoundCount must match the number of arrays
if (itemFoundCount === nonEmptyArrs.length) {
return item;
}
})
.filter(item => item); // Remove undefined entries
}
See proof-of-concept below:
function common(arr1, ...arrs) {
return arr1.map(item => {
const nonEmptyArrs = arrs.filter(arr => arr.length);
const itemFoundCount = nonEmptyArrs
.map(arr => arr.includes(item))
.filter(i => i)
.length;
// Only return item if it is found in ALL arrays
if (itemFoundCount === nonEmptyArrs.length) {
return item;
}
}).filter(item => item);
}
const newArr = common([1, 2, 6, 5, 9, 8], [1, 2, 3, 6, 5, 9, 8], [6, 5, 4, 5, 8], [8, 2, 1, 6, 4], [8, 2, 1, 6, 4], []);
console.log(newArr);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Related

For two arrays find items that are present in one array only (symmetric difference)

I need to compare two arrays and return a new array with any items only found in one of the two given arrays, but not both. In other words, return the symmetric difference of the two arrays.
My algorithm consists of using the map() method on the first array and compare each of element of that array with the elements of the second array using every(). If this method returns true, the element gets returned on the block level of map (which will eventually add it to the returned array) if not it's discarded.
I'm not sure why my code is not working. This is an example of a wrong output using my code:
function diffArray(arr1, arr2) {
var newArr = arr1
.map(elem1 => {
if (arr2.every(elem2 => elem2 != elem1)) {
return elem1;
}
});
return newArr;
}
console.log(diffArray([1, 2, 3, 5], [1, 2, 3, 4, 5]));
This is the wrong output:
[ undefined, undefined, undefined, undefined ]
The expected output is : [4]
Your approach iterates the first array and because of using map along with the check for the value, you get undefined for every element of arr1.
If you take filter and the other array as well, you could get the wanted result.
function diffArray(arr1, arr2) {
return [
...arr1.filter(elem1 => arr2.every(elem2 => elem2 != elem1)),
...arr2.filter(elem1 => arr1.every(elem2 => elem2 != elem1))
];
}
console.log(diffArray([1, 2, 3, 5], [1, 2, 3, 4, 5]));
Another approach takes an array of all values of both arrays and filter by checking if the value is not included in both arrays.
function diffArray(arr1, arr2) {
return [...arr1, ...arr2].filter(v => arr1.includes(v) !== arr2.includes(v));
}
console.log(diffArray([1, 2, 3, 5], [1, 2, 3, 4, 5]));
Use _.difference(array, [values])
from lodash
or
own solution :
const diffArray = (arrayA, arrayB) => {
const output = []
const setA = new Set(arrayA);
arrayB.forEach((n) =>{
if(!setA.has(n)){
output.push(n)
}
})
const setB = new Set(arrayB);
arrayA.forEach(n =>{
if(!setB.has(n)){
output.push(n)
}
})
return output;
}
console.log(diffArray([1, 2, 3, 5, 6], [1, 2, 3, 4, 5])); //4, 6
This algorithm works even when numbers appear multiple times in both arrays.
It uses a Map created from the items of both arrays. The map contains the item as the key, and the value is the amount of times it's found in the 1st array - the number of times it's found it the 2nd array.
After creating the Map, it's converted to an array of [item, count]. The array is then filtered, and all items with a count of 0 are removed (they exist equally in both arrays), and then we map the array to an array of items.
const getCounts = (arr, init = new Map(), inc = 1) =>
arr.reduce((acc, item) => acc.set(item, (acc.get(item) || 0) + inc), init);
function diffArray(arr1, arr2) {
// create a Map that adds 1 for all items in arr1, and substructs 1 for every item in arr2
const counts = getCounts(arr2, getCounts(arr1), -1);
// convert to an array of pairs [item, count]
return Array.from(counts)
.filter(([, v]) => v) // remove all items with count 0
.map(([k]) => k); // map to the original item
}
console.log(diffArray([1, 2, 3, 5], [1, 2, 3, 4, 5]));
console.log(diffArray([1, 2, 3, 3, 5], [1, 2, 3, 4, 5]));
console.log(diffArray([5, 1, 2, 3, 5], [1, 2, 3, 4, 5, 5]));
console.log(diffArray([1, 1, 2, 2, 3, 3, 5], [1, 2, 2, 3, 4, 5]));
Your function returns elements of the first array that are not present in the second array.
Its return is explained by the fact that .map() returns the array of exact same size as your input (arr1), but since all items of arr1 are present in arr2 you don't enter if(-statement body hence undefined is getting returned.
If your intention was to return items that are present in one array only (regardless of the order they're passed in), you may leverage the Map object together with Array.prototype.reduce():
combine arrays-arguments into common array of arrays
loop through those inner arrays with .reduce(), building up the Map, showing how many times each item is seen within combined array
for each item of combined array remove duplicates and increment respective counter
spread resulting Map into .entries() and .filter() those to find out uniques
const arr1 = [1, 2, 3, 5],
arr2 = [1, 2, 3, 4, 5],
getUniques = (...arrays) =>
[...arrays
.reduce((acc, arr) => {
[...new Set(arr)]
.forEach(item =>
acc.set(item, (acc.get(item)||0)+1))
return acc
}, new Map)
.entries()]
.reduce((acc, [item, repetitions]) =>
(repetitions == 1 && acc.push(item), acc), [])
console.log(getUniques(arr1, arr2))
.as-console-wrapper {min-height:100%;}
Above approach is of O(n)-time complexity, as opposed to your initial attempt and the answer you have currently accepted (both having O(n²)-time complexity). As a result it may perform much faster on large arrays (arbitrary number of arrays, as a bonus).

How to transpose an m*n matrix using recursion?

I'm trying to transpose a matrix using recursion. Now, I know that under normal circumstances this isn't a good idea and a nested loop/nested map, or a similar approach is superior, but I need to learn this for educational purposes.
To show that I did my homework, here is the nested loop approach:
const arrMatrix = [
[3, 6, 7, 34],
[6, 3, 5, 2],
[2, 6, 8, 3]
];
const transposedMatrix = []
for (let i = 0; i < arrMatrix[0].length; i++) {
const tempCol = [];
for (let j = 0; j < arrMatrix.length; j++) {
tempCol.push(arrMatrix[j][i]);
}
transposedMatrix.push(tempCol);
}
console.log(transposedMatrix);
Here's another approach using nested maps:
const arrMatrix = [
[3, 6, 7, 34],
[6, 3, 5, 2],
[2, 6, 8, 3]
];
const transposedMatrix = arrMatrix[0].map((_, i) =>
arrMatrix.map((_, j) => arrMatrix[j][i])
);
console.log(transposedMatrix);
Here are some resources that I went through but didn't manage to come up with a solution using them.
If possible, in addition to the algorithm/code, please give me some explanation and resources to learn more about it.
const map = ([head, ...tail], mapper) => tail.length ? [mapper(head), ...map(tail, mapper)] : [mapper(head)];
const transpose = matrix =>
matrix[0].length
? [map(matrix, row => row.shift()), ...transpose(matrix)]
: [];
How it works:
From a given matrix, we always take out the first column (matrix.map(row => row.shift()), then we continue recursively:
[[1, 1, 1], -> [[1, 1], -> [[1], -> [[],
[2, 2, 2], [2, 2], [2], [],
[3, 3, 3]] [3, 3]] [3]] []]
Then the base case gets reached, the matrix is empty (matrix[0].length is 0 = falsy) and an empty array gets returned. Now at every step, the column taken out gets added to that array, and thus it's a row now:
[[1, 2, 3], <- [[1, 2, 3], <- [[1, 2, 3]] <- []
[1, 2, 3], [1, 2, 3]]
[1, 2, 3]]
Note: This destroys the original array.
const transpose = matrix => {
const row = (x) => x >= matrix[0].length ? [] : [col(x, 0), ...row(x + 1)];
const col = (x, y) => y >= matrix.length ? [] : [matrix[y][x], ...col(x, y + 1)];
return row(0);
};
That version does not mutate the original array. You can take that even a step further, than it is purely functional, but thats a bit overkill:
const transpose = matrix => (
(row, col) => row(row)(col)(0)
)(
row => col => (x) => x >= matrix[0].length ? [] : [col(col)(x, 0), ...row(row)(col)(x + 1)],
col => (x, y) => y >= matrix.length ? [] : [matrix[y][x], ...col(col)(x, y + 1)]
);
This is very similar to hitmands' solution, and would likely be less performant, but I think it's slightly cleaner to avoid working with column indices:
const head = xs => xs[0]
const tail = xs => xs.slice(1)
const transpose = (m) => head(m).length
? [m.map(head), ...transpose(m.map(tail))]
: []
const matrix = [
[3, 6, 7, 34],
[6, 3, 5, 2],
[2, 6, 8, 3]
]
console .log (
transpose (matrix)
)
This version transposes the first column into a row (via .map(head)) and then recurs on the remaining matrix (via .map(tail)), bottoming out when the first row is empty.
You can inline those helper functions if you choose, so that it looks like this:
const transpose = (m) => m[0].length
? [m.map(xs => xs[0]), ...transpose(m.map(xs => xs.slice(1)))]
: []
..but I wouldn't recommend it. The first version seems more readable, and head and tail are easily reusable.
Update
user633183 suggests an alternative escape condition. It's a good question whether or not it's a better result for ill-formed data, but it's certainly a useful possible variant:
const head = xs => xs[0]
const tail = xs => xs.slice(1)
const empty = xs => xs.length == 0
const transpose = (m) => m.some(empty)
? []
: [m.map(head), ...transpose(m.map(tail))]
(This could also be done with m.every(nonempty) by reversing the consequent and alternative in the conditional expression, but I think it would be slightly harder to read.)
I would write it like this,
assuming that all the rows inside the matrix have the same length:
check if there still are rows to process
create a row from each colum at the given index
increment column index by 1
call transpose with the new index
const transpose = (m, ci = 0) => ci >= m[0].length
? []
: [m.map(r => r[ci]), ...transpose(m, ci + 1)]
;
const matrix = [
[3, 6, 7, 34],
[6, 3, 5, 2],
[2, 6, 8, 3]
];
console.log(
transpose(matrix),
);
I had an idea to write transpose using the Maybe monad. I'll start using functional operations and then refactor to clean up the code -
Dependencies -
const { Just, Nothing } =
require("data.maybe")
const safeHead = (a = []) =>
a.length
? Just(a[0])
: Nothing()
const tail = (a = []) =>
a.slice(1)
Without refactors -
const column = (matrix = []) =>
matrix.reduce
( (r, x) =>
r.chain(a => safeHead(x).map(x => [ ...a, x ]))
, Just([])
)
const transpose = (matrix = []) =>
column(matrix)
.map(col =>
[ col, ...transpose(matrix.map(tail)) ]
)
.getOrElse([])
Refactor column using generic append and lift2 -
const append = (a = [], x) =>
[ ...a, x ]
const lift2 = f =>
(mx, my) =>
mx.chain(x => my.map(y => f(x, y)))
const column = (matrix = []) =>
matrix.reduce
( (r, x) =>
lift2(append)(r, safeHead(x))
, Just([])
)
const transpose = (matrix = []) =>
column(matrix)
.map(col =>
[ col, ...transpose(matrix.map(tail)) ]
)
.getOrElse([])
Refactor column again using generic transducer mapReduce -
const mapReduce = (map, reduce) =>
(r, x) => reduce(r, map(x))
const column = (matrix = []) =>
matrix.reduce
( mapReduce(safeHead, lift2(append))
, Just([])
)
const transpose = (matrix = []) =>
column(matrix)
.map(col =>
[ col, ...transpose(matrix.map(tail)) ]
)
.getOrElse([])
transpose stays the same in each refactoring step. It produces the following outputs -
transpose
( [ [ 1, 2, 3, 4 ]
, [ 5, 6, 7, 8 ]
, [ 9, 10, 11, 12 ]
]
)
// [ [ 1, 5, 9 ]
// , [ 2, 6, 10 ]
// , [ 3, 7, 11 ]
// , [ 4, 8, 12 ]
// ]
transpose
( [ [ 1, 2, 3, 4 ]
, [ 5 ]
, [ 9, 10, 11, 12 ]
]
)
// [ [ 1, 5, 9 ] ]

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

How can I turn an array into an object with the name being the first value in the array and the properties an array of the subarrays?

What is the best way to take a multidimensional array with an unknown list of elements and group it into an object to remove repeated values in the first element of the subarray:
For example, I'd like to turn this:
const arr = [[a, 1, 4], [b, 3, 4], [c, 1, 7], [a, 2, 5], [c, 3, 5]]
Into this:
arrResult = {a:[[1, 4],[2, 5]], b:[[3, 4]], c:[[1, 7],[3, 5]]}
I thought about sorting this and then splitting it or running some kind of reduce operation but couldn't figure out exactly how to accomplish it.
You only need to use reduce (and slice), no need for sorting or splitting
var arr = [['a', 1, 4], ['b', 3, 4], ['c', 1, 7], ['a', 2, 5], ['c', 3, 5]];
var arrResult = arr.reduce((result, item) => {
var obj = result[item[0]] = result[item[0]] || [];
obj.push(item.slice(1));
return result;
}, {});
console.log(JSON.stringify(arrResult));
You can use reduce like this:
const arr = [["a", 1, 4], ["b", 3, 4], ["c", 1, 7], ["a", 2, 5], ["c", 3, 5]];
var result = arr.reduce((obj, sub) => {
var key = sub[0]; // key is the first item of the sub-array
if(obj[key]) obj[key].push(sub.slice(1)); // if the there is already an array for that key then push this sub-array (sliced from the index 1) to it
else obj[key] = [sub.slice(1)]; // otherwise create a new array that initially contain the sliced sub-array
return obj;
}, {});
console.log(result);
you could use reduce and destructuring like this:
const arr = [['a', 1, 4],['b', 3, 4],['c', 1, 7],['a', 2, 5],['c', 3, 5]]
function sub(arr) {
return arr.reduce((obj, [key, ...value]) => {
obj[key] ? obj[key].push(value) : obj[key] = [value]
return obj
}, {})
}
console.log(sub(arr));
I like this solution better because it abstracts away the collation but allows you to control how items are collated using a higher-order function.
Notice how we don't talk about the kind or structure of data at all in the collateBy function – this keeps our function generic and allows for it to work on data of any shape.
// generic collation procedure
const collateBy = f => g => xs => {
return xs.reduce((m,x) => {
let v = f(x)
return m.set(v, g(m.get(v), x))
}, new Map())
}
// generic head/tail functions
const head = ([x,...xs]) => x
const tail = ([x,...xs]) => xs
// collate by first element in an array
const collateByFirst = collateBy (head)
// your custom function, using the collateByFirst collator
// this works much like Array.prototype.reduce
// the first argument is your accumulator, the second argument is your array value
// note the acc=[] seed value used for the initial empty collation
const foo = collateByFirst ((acc=[], xs) => [...acc, tail(xs)])
const arr = [['a', 1, 4], ['b', 3, 4], ['c', 1, 7], ['a', 2, 5], ['c', 3, 5]]
let collation = foo(arr);
console.log(collation.get('a')) // [ [1,4], [2,5] ]
console.log(collation.get('b')) // [ [3,4] ]
console.log(collation.get('c')) // [ [1,7], [3,5] ]
Of course you could write it all in one line if you didn't want to give names to the intermediate functions
let collation = collateBy (head) ((acc=[], xs) => [...acc, tail(xs)]) (arr)
console.log(collation.get('a')) // [ [1,4], [2,5] ]
Lastly, if you want the object, simply convert the Map type to an Object
let obj = Array.from(collation).reduce((acc, [k,v]) =>
Object.assign(acc, { [k]: v }), {})
console.log(obj)
// { a: [ [1,4], [2,5] ],
// b: [ [3,4] ],
// c: [ [1,7], [3,5] ] }
Higher order functions demonstrate how powerful generic procedures likes collateBy can be. Here's another example using the exact same collateBy procedure but performing a very different collation
const collateBy = f => g => xs => {
return xs.reduce((m,x) => {
let v = f(x)
return m.set(v, g(m.get(v), x))
}, new Map())
}
const collateEvenOdd = collateBy (x => x % 2 === 0 ? 'even' : 'odd')
const sumEvenOdd = collateEvenOdd ((a=0, b) => a + b)
let data = [2,3,4,5,6,7]
let collation = sumEvenOdd (data)
let even = collation.get('even')
let odd = collation.get('odd')
console.log('even sum', even) // 2 + 4 + 6 === 12
console.log('odd sum', odd) // 3 + 5 + 7 === 15

Javascript recursive array flattening

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.

Categories

Resources