I'd like to get output from input.
It should be pushed element like chasing tails and it couldn't be duplicated.
The rule is very simple.
Firstly, if there is the same element with input[0][1] in input[i][0],
you can put the first value of the element in a result.
The element is [1,5] so result becomes [[0,1]].
Now you can just repeat. The first element of [5,29] is the same element with [1,5][1] and result becomes [[0,1,5]]
I've been really frustrating for few weeks to solve this issue. Please help. Any comments would be appreciated.
input =
[
[ 0, 1 ], [ 0, 2 ], [ 0, 3 ],
[ 0, 4 ], [ 1, 5 ], [ 2, 6 ],
[ 3, 7 ], [ 4, 10 ], [ 4, 11 ],
[ 4, 12 ], [ 4, 13 ], [ 5, 29 ],
[ 6, 29 ], [ 7, 8 ], [ 8, 29 ],
[ 9, 29 ], [ 12, 18 ], [ 13, 19 ],
[ 17, 29 ], [ 18, 29 ], [ 19, 29 ],
[ 21, 29 ], [ 24, 29 ], [ 26, 29 ],
[ 28, 29 ]
]
output = [
[0,1,5,29],[0,2,6,29],[0,3,7,8,29],[0,4,10],[0,4,11],
[0,4,12,18,29],[0,4,13,19,29]
]
You can write solver as a generator with an input of t and a starting query of q. Other answers suggest reshaping your input or other functional techniques that iterate over the input multiple times. This simple imperative-style technique uses only one pass (per recursive call). The use of a generator allows you to find all solutions, but can be paused/stopped at any time, for any other reason -
function* solver (t, q) {
let atLeastOnce = false
for (const [parent, child] of t) {
if (parent == q) {
atLeastOnce = true
for (const sln of solver(t, child))
yield [parent, ...sln]
}
}
if (!atLeastOnce) {
yield [q]
}
}
const input =
[[0,1],[0,2],[0,3],[0,4],[1,5],[2,6],[3,7],[4,10],[4,11],[4,12],[4,13],[5,29],[6,29],[7,8],[8,29],[9,29],[12,18],[13,19],[17,29],[18,29],[19,29],[21,29],[24,29],[26,29],[28,29]]
for (const sln of solver(input, 0))
console.log(JSON.stringify(sln))
[0,1,5,29]
[0,2,6,29]
[0,3,7,8,29]
[0,4,10]
[0,4,11]
[0,4,12,18,29]
[0,4,13,19,29]
Generators are iterable and so you can collect all results in an array using Array.from -
const all = Array.from(solver(input, 0))
console.log(all)
[
[0,1,5,29],
[0,2,6,29],
[0,3,7,8,29],
[0,4,10],
[0,4,11],
[0,4,12,18,29],
[0,4,13,19,29],
]
Combine generators with an optimized input type for even better results.
Based on how I understood your output, what you want is kind of like a domino-chain. You seed the output with arrays start contain 0 in the first element, e.g. [0,1] and [0,2]..., and then you simply find the next array to join to the chain based on the last item in the array vs the first item in the next entry.
This process is recursive in nature, since you start with the seeds and have no idea how far/deep you will need to join. To break this down, we can do this:
Separate your input into seeds and non-seeds (nodes).
Start with seeds
Iterate through the seeds and invoke a function, that will recursively add nodes to your seed. The trick is that you want to duplicate the seed array, when multiple paths can be found
The function should invoke itself recursively, and here's a quick logic:
/**
* #method
* #params arr The seed array (which will grow in length)
* #params nodes The collection of non-seeds
*/
function chain(arr, nodes) {
// We first find whatever nodes left that we can domino-chain to the current seed
const nextNodes = nodes.filter(node => node[0] === arr[arr.length - 1]);
// If nothing is to be found, return the array
if (!nextNodes.length) return [arr];
// Otherwise we go through all the next nodes and create a copy of the current seed
// And then append the next node to it
return nextNodes.map(nextNode => {
return chain([...arr, nextNode[1]], nodes);
}).flat();
}
See proof-of-concept below:
const input = [
[0, 1],
[0, 2],
[0, 3],
[0, 4],
[1, 5],
[2, 6],
[3, 7],
[4, 10],
[4, 11],
[4, 12],
[4, 13],
[5, 29],
[6, 29],
[7, 8],
[8, 29],
[9, 29],
[12, 18],
[13, 19],
[17, 29],
[18, 29],
[19, 29],
[21, 29],
[24, 29],
[26, 29],
[28, 29]
];
const seeds = input.filter(entry => entry[0] === 0);
const nodes = input.filter(entry => entry[0] !== 0);
function chain(arr, nodes) {
const nextNodes = nodes.filter(node => node[0] === arr[arr.length - 1]);
if (!nextNodes.length) return [arr];
return nextNodes.map(nextNode => {
return chain([...arr, nextNode[1]], nodes);
}).flat();
}
const output = seeds.map(seed => {
return chain(seed, nodes);
}).flat();
console.log(output);
I would split the problem in 2:
Create a tree-like structure from your list. Each node in your tree contains a reference to all possible next nodes.
Write a recursive function that walks the tree to find all possible paths
Here's an implementation of that approach:
const input =
[
[ 0, 1 ], [ 0, 2 ], [ 0, 3 ],
[ 0, 4 ], [ 1, 5 ], [ 2, 6 ],
[ 3, 7 ], [ 4, 10 ], [ 4, 11 ],
[ 4, 12 ], [ 4, 13 ], [ 5, 29 ],
[ 6, 29 ], [ 7, 8 ], [ 8, 29 ],
[ 9, 29 ], [ 12, 18 ], [ 13, 19 ],
[ 17, 29 ], [ 18, 29 ], [ 19, 29 ],
[ 21, 29 ], [ 24, 29 ], [ 26, 29 ],
[ 28, 29 ]
];
const nodes = {};
// Store all paths between nodes
for (const [ start, end ] of input) {
nodes[end] = nodes[end] || { id: end, children: [] };
nodes[start] = nodes[start] || { id: start, children: [] };
nodes[start].children.push(nodes[end]);
}
// Find all paths from 0
const getPaths = ({ children, id }) => children.length === 0
? [[ id ]]
: children.flatMap(
n => getPaths(n).map(p => [ id, ...p ])
)
console.log(getPaths(nodes[0]));
You can do that with a single loop too, and follow your rules:
if edge starts (start) with 0, just push it into the result
otherwise try finding an already existing path in result which contains start
if the existing path ends with the start, just append edge's ending location
if the existing path contains the start somewhere inside, put a copy of path up to that position into result, and append edge's ending location.
let input = [[0, 1], [0, 2], [0, 3], [0, 4], [1, 5], [2, 6], [3, 7], [4, 10], [4, 11],
[4, 12], [4, 13], [5, 29], [6, 29], [7, 8], [8, 29], [9, 29], [12, 18],
[13, 19], [17, 29], [18, 29], [19, 29], [21, 29], [24, 29], [26, 29],
[28, 29]];
let result = [];
for (let edge of input) {
let start = edge[0];
if (start === 0) { // <--- 1. (0 => push for sure)
result.push(edge);
} else {
for (let path of result) {
let position = path.indexOf(start);
if (position > 0) { // <--- 2. (can't be 0, but >= would work too)
if (position !== path.length - 1) { // <--- 4. (need a copy)
path = path.slice(0, position + 1);
result.push(path);
}
path.push(edge[1]); // <--- 3. 4. (whatever path is)
break;
}
}
}
}
console.log(JSON.stringify(result));
(JSON.stringify() is just used for formatting)
Related
I have an array as below
var a = [ [ 5, 5, 1, -4 ], [ 3, 7, 3, -1 ], [ 7, 3, 4, 1 ], [ 5, 5, 5, 0 ] ]
Every nested array index:2 element indicates its distance from 0(Zero) and every index:3 element indicate how near it is to its next poll point.
I am trying to sort this array so I can get nested array which is near to index:0 with reference to index:2 element and its next poll point is very near.
For example, my answer here is [ 3, 7, 3, -1 ] because
though [ 5, 5, 1, -4 ] , index:2 is very near to 0 its next point is located at after/before 4 positions. But for [ 3, 7, 3, -1 ] next poll point is at one position.
I tried with sorting like below
js
inDiff = inDiff.sort( function(a,b ){
if ( a[2] < b[2]) {
if ( Math.abs(a[3]) < Math.abs(b(3)) ){
return Math.abs(b[3]) - Math.abs(a[3]);
}
}
});
Update 1
As asked in the comment I am adding explanation to each element of the nested array. For example in nested array [ 5, 5, 1, -4 ]
Index:0: Value 5 Represents 1st Number that I am looking for
Index:1 Value 5 Represents 2nd Number ( next poll point number)
By Adding these two numbers I will achieve my requirement of finding two numbers which can sum up for 10.
Index 2 : Value 1 : Indicates index of 1st number nothing but 5 in the source array
Index 3 : Value -4 : Indicates difference between indexes of Index:0 and Index:1 number of nested array from source array.
But nothing happens with my array.
Any help, much appreciated.
Assuming the input always follows requirements this demo uses .reduce()
const AA = [
[5, 5, 1, -4],
[3, 7, 3, -1],
[7, 3, 4, 1],
[5, 5, 5, 0]
];
let result = AA.reduce((min, now, idx, AoA) => {
let distTo0 = array => Math.abs(Math.floor(array[2]) + Math.floor(array[3]));
min = distTo0(now) < distTo0(min) ? now : min;
return min;
});
console.log(result);
The following demo includes all of the rules as I understood them:
const crazyLogic = arrayOfArrays => {
let AAClone = JSON.parse(JSON.stringify(arrayOfArrays));
const shared = AAClone[0][0] + AAClone[0][1];
const rules = [`Must be an array of number arrays`, `The sum of index 0 and 1 of each sub-array must be identical`, `Mismatched sub-array lengths`];
let message = !AAClone.every(sub => Array.isArray(sub)) ? rules[0] : !AAClone.every(sub => sub.every(num => num + 0 === num)) ? rules[0] : !AAClone.every(sub => sub[0] + sub[1] === shared) ? rules[1] : !AAClone.every(sub => sub.length === 4) ? rules[2] : null;
if (message !== null) {
return message;
}
return AAClone.reduce((min, now, idx, AoA) => {
let distTo0 = array => Math.abs(Math.floor(array[2]) + Math.floor(array[3]));
min = distTo0(now) < distTo0(min) ? now : min;
return min;
});
};
/* Input rules:
1. Must be an array of arrays (only numbers)
2. Each sum of subArray[0] + subArray[1] must be identical
3. Each subArray.length = 4
*/
// AAa returns [ 3, 7, 3, -1 ]
const AAa = [
[5, 5, 1, -4],
[3, 7, 3, -1],
[7, 3, 4, 1],
[5, 5, 5, 0]
];
// AA1 breaks rule 1
const AA1 = [
[5, 5, 1, -4],
[3, 7, 3, -1],
[7, 3, ' X', 1],
[5, 5, 5, 0]
];
// AAO breaks rule 1
const AAO = [
[5, 5, 1, -4],
[3, 7, 3, -1],
[7, 3, 4, 1],
[5, 5, 5, 0], {}
];
// AA2 breaks rule 2
const AA2 = [
[5, 5, 1, -4],
[3, 17, 3, -1],
[7, 3, 4, 1],
[5, 5, 5, 0]
];
// AA3 breaks rule 3
const AA3 = [
[5, 5, 1, -4],
[3, 7, 3, -1],
[7, 3, 4, 1],
[5, 5, 5]
];
console.log(crazyLogic(AAa));
console.log(crazyLogic(AA1));
console.log(crazyLogic(AAO));
console.log(crazyLogic(AA2));
console.log(crazyLogic(AA3));
We have an array of arrays like this:
const arrays = [
[0, 1, 2, 3, 4, 4, 4, 4],
[5, 6, 7, 8, 9, 10, 11, 11],
[2, 7, 10, 12],
[0, 7, 10, 14]
];
There may be duplicate elements in each array and that's fine.
But I'm after a proper solution to remove duplicate elements in each set comparing to lower sets!
So as we have a 0 in the first array and the last array, we should consider the 0 in last one a duplication and remove it...
the desired result would be:
[0, 1, 2, 3, 4, 4, 4, 4],
[5, 6, 7, 8, 9, 10, 11, 11],
[12],
[14]
It's a confusing issue for me please help...
You could collect the values in an object with index as value, and filter for values who are at the same index.
const
arrays = [[0, 1, 2, 3, 4, 4, 4, 4], [5, 6, 7, 8, 9, 10, 11, 11], [2, 7, 10, 12], [0, 7, 10, 14]],
seen = {},
result = arrays.map((array, i) => array.filter(v => (seen[v] ??= i) === i));
result.forEach(a => console.log(...a));
const arrays = [
[0, 1, 2, 3, 4, 4, 4, 4],
[4, 4, 5, 6, 7, 8, 9, 10, 11, 11],
[2, 7, 10, 12],
[0, 7, 10, 14]
]
let filtered = arrays.map((row, i) => {
// concat all previous arrays
let prev = [].concat(...arrays.slice(0, i))
// filter out duplicates from prev arrays
return row.filter(r => !prev.includes(r))
})
console.log(filtered)
We can do this using Array#reduce and maintain a seen Set, which will have all visited numbers from each array.
Once you iterate over an array you push all visited elements in the seen Set, then push a new array filtered by the elements not in the seen Set:
const arrays = [
[0, 1, 2, 3, 4, 4, 4, 4],
[5, 6, 7, 8, 9, 10, 11, 11],
[2, 7, 10, 12],
[0, 7, 10, 14]
];
const removeDupsInSibling = (arr) => {
let seen = new Set();
return arr.reduce((acc, a)=> {
const f = a.filter(v => !seen.has(v));
seen = new Set([...seen, ...a]);
acc.push(f);
return acc;
}, []);
}
console.log(removeDupsInSibling(arrays));
There are plenty of inefficient ways to do this, but if you want to do this in O(n), then we can make the observation that what we want to know is "which array a number is in". If we know that, we can run our algorithm in O(n):
for every element e in array at index i:
if index(e) == i:
this is fine
if index(e) < i:
remove this e
So let's just do literally that: we allocate an object to act as our lookup, and then we run through all elements:
const lookup = {};
const arrays = [
[0, 1, 2, 3, 4, 4, 4, 4],
[5, 6, 7, 8, 9, 10, 11, 11],
[2, 7, 10, 12],
[0, 7, 10, 14]
];
const reduced = arrays.map((array, index) => {
// run through the elements in reverse, so that we can
// safely remove bad elements without affecting the loop:
for(let i=array.length-1; i>=0; i--) {
let value = array[i];
let knownIndex = (lookup[value] ??= index);
if (knownIndex < index) {
// removing from "somewhere" in the array
// uses the splice function:
array.splice(i,1);
}
}
return array;
});
console.log(reduced);
For an alternative, where the loop+splice is taken care of using filter, see Nina's answer.
Simple, clean and high performance solution:
const arrays = [
[0, 1, 2, 3, 4, 4, 4, 4],
[5, 6, 7, 8, 9, 10, 11, 11],
[2, 7, 10, 12],
[0, 7, 10, 14]
];
const duplicates = {};
const answer = arrays.map( (array, level) => {
return array.filter( el => {
if ( duplicates[el] < level ) {
// return nothing; fine
} else {
duplicates[el] = level;
return el
}
})
});
console.log(JSON.stringify(answer))
here is on-liner and less-readable form:
const d = {}, arrays = [ [0, 1, 2, 3, 4, 4, 4, 4], [5, 6, 7, 8, 9, 10, 11, 11], [2, 7, 10, 12], [0, 7, 10, 14]];
const answer = arrays.map((a,l)=> a.filter(el=> d[el]<l ? 0 : (d[el]=l,el)));
console.log(JSON.stringify(answer))
const arrays = [
[0, 1, 2, 3, 4, 4, 4, 4],
[5, 6, 7, 8, 9, 10, 11, 11],
[2, 7, 10, 12],
[0, 7, 10, 14],
];
const output = arrays.reduce(
({ output, set }, current, i) => {
output[i] = current.filter((num) => !set.has(num));
[...new Set(output[i])].forEach((num) => set.add(num));
return { output, set };
},
{ output: [], set: new Set() }
).output;
console.log(output);
Gets the exact output you want:
[
[
0, 1, 2, 3,
4, 4, 4, 4
],
[
5, 6, 7, 8,
9, 10, 11, 11
],
[ 12 ],
[ 14 ]
]
I've looked at a number of other posts and can't resolve my case.
I am working on a music/chord playing program and I want to filter out all chords (aka objects) that include one or more notes (numbers in my program) that are out of key.
I have an array named chordLibrary filled with objects (e.g., {notesInChord: [5, 8], chordName: 'Major'}). I have another array with numbers (outOfKeyNotes = [11, 12];). I want to .filter and return only the objects in chordLibrary that do not include the numbers in outOfKeyNotes in the element notesInChord.
For example:
// THIS is the original array:
const chordLibrary = [
{ notesInChord: [5, 8], chordName: 'Major' },
{ notesInChord: [5, 8, 11], chordName: 'Dominant 7th' },
{ notesInChord: [4, 8, 12], chordName: 'Minor Major 7th' }
];
// THIS is what I hope to end up with after filtering for the array [11,12]:
let chordsInKey = [
{ notesInChord: [5, 8], chordName: 'Major' },
];
Here is my program which is currently not working. it simply returns the whole original array.
const chordLibrary = [
{ notesInChord: [5, 8], chordName: 'Major' },
{ notesInChord: [5, 8, 11], chordName: 'Dominant 7th' },
{ notesInChord: [4, 8, 12], chordName: 'Minor Major 7th' }
];
let outOfKeyNotes = [11, 12];
console.log(chordLibrary.length);
let chordsInKey = chordLibrary.filter(function(item) {
return !outOfKeyNotes.includes(item.notesInChord);
});
console.log(chordsInKey);
console.log(chordsInKey.length);
If I change the program so that chordLibrary is simply an array of number values instead of an array of objects, it works fine. It just doesn't suit my needs. Here is a working example of that:
let chordLibrary = [1,2,3,11,12];
let outOfKeyNotes = [11, 12];
console.log(chordLibrary.length);
let chordsInKey = chordLibrary.filter(function(item) {
return !outOfKeyNotes.includes(item);
});
console.log(chordsInKey);
console.log(chordsInKey.length);
What am I missing? Thanks
You're trying to use includes to see if an array of values includes another array of values.
Instead, you can use Array.every in your filtering mechanism, which makes sure every element in an array passes a specific test.
I would change
return !outOfKeyNotes.includes(item.notesInChord);
to
return item.notesInChord.every(note => !outOfKeyNotes.includes(note));
You can use Array#some() to check if any of the notesInChord values exist in outOfKeyNotes
const chordLibrary = [
{ notesInChord: [5, 8], chordName: 'Major' },
{ notesInChord: [5, 8, 11], chordName: 'Dominant 7th' },
{ notesInChord: [4, 8, 12], chordName: 'Minor Major 7th' }
];
let outOfKeyNotes = [11, 12];
let chordsInKey = chordLibrary.filter(function(item) {
return !item.notesInChord.some(function(v){
return outOfKeyNotes.includes(v);
});
});
console.log(chordsInKey);
I am trying to use a priority Queue for my own data type which is a 3x3 array. The actual code and data type is more complex so I boiled it down to the essentials. Note that the priority queue works well for an integer data type (see output at the end). Everything is self explanatory. Assume the metric function returns a positive integer for a given 3X3 array. I am confused why the dequeueing from the heap does not return the min valued object (or max valued in case I got the comparator backwards but I got the middle value for both). For the data type of integer the priority Queue seems to work correctly as the output shows.
var r = require('js-priority-queue');
metric = function (A) {
N = A.length;
if (A[0][1] == 0) return 123;
if (A[0][1] == 5) return 124;
if (A[0][1] == 1) return 122;
if (A[0][1] == 6) return 122;
return 0;
}
mComparator = function (m1, m2) {
ret = metric(m2) - metric(m1);
return ret;
}
mHeap = new r(mComparator);
nHeap = new r(function (a,b) {
return b - a;
})
A = [[5, 0, 1], [7, 6, 3], [2, 4, 8]];
B = [[5, 6, 1], [7, 0, 3], [2, 4, 8]];
C = [[5, 1, 0], [7, 6, 3], [2, 4, 8]];
D = [[0, 5, 1], [7, 6, 3], [2, 4, 8]];
console.log("metric(A) -> %d", metric(A));
console.log("metric(B) -> %d", metric(B));
console.log("metric(C) -> %d", metric(C));
console.log("metric(D) -> %d", metric(D));
mHeap.queue(A);
mHeap.queue(B);
mHeap.queue(C);
mHeap.queue(D);
X = mHeap.dequeue();
console.log(X);
X = mHeap.dequeue();
console.log(X);
X = mHeap.dequeue();
console.log(X);
X = mHeap.dequeue();
console.log(X);
nHeap.queue(123);
nHeap.queue(124);
nHeap.queue(122);
nHeap.queue(122);
y = nHeap.dequeue();
console.log(y);
#Output
metric(A) -> 123
metric(B) -> 122
metric(C) -> 122
metric(D) -> 124
[ [ 5, 0, 1 ], [ 7, 6, 3 ], [ 2, 4, 8 ] ]
[ [ 0, 5, 1 ], [ 7, 6, 3 ], [ 2, 4, 8 ] ]
[ [ 5, 1, 0 ], [ 7, 6, 3 ], [ 2, 4, 8 ] ]
[ [ 5, 6, 1 ], [ 7, 0, 3 ], [ 2, 4, 8 ] ]
122
If you are using the adamhooper priority queue, then your problem is you are not supplying the comparator correctly. Change your constructor invocations to:
mHeap = new r({"comparator": mComparator});
nHeap = new r({"comparator": function (a,b) {
return b - a;
}})
And you should start getting expected results. Note that this gives you max-heaps. Since you wanted min-heaps, you should also reverse your comparison order:
mComparator = function (m1, m2) {
ret = metric(m1) - metric(m2);
return ret;
}
mHeap = new r({"comparator": mComparator});
nHeap = new r({"comparator": function (a,b) {
return a - b;
}})
Example code for correctly supplying a comparator is given on the github project's front page, but I see how it could be easy to miss.
I have nested array data and I would like to extract all nested arrays to be siblings of their parent. I am pretty close, but I am getting an extra empty array in the results and I cannot figure out where it is coming from or how to get rid of it.
Note: I would really like to understand why this is happening and how to get rid of it in my function, and not just a .filter(arr => arr.length) on my results list.
This is my attempt so far:
var arrs = [
[1, 2, [3, 4], 5],
[6, [7, 8, 9, [10, 11]]],
[12, 13],
[[14, 15], [16, 17]],
[[1], 4, [1, 1], 4]
];
// Desired Output
// [
// [1, 2, 5],
// [3, 4],
// [6],
// [7, 8, 9],
// [10, 11],
// [12, 13],
// [14, 15],
// [16, 17],
// [4, 4]
// [1]
// [1, 1]
// ]
function extractArrays (arr) {
return arr.reduce((res, curr) => {
if (Array.isArray(curr)) {
res = res.concat(extractArrays(curr));
}
else {
res[0].push(curr);
}
return res;
}, [[]]);
}
console.log(extractArrays(arrs));
// Results:
// [
// [], <-- Where is this coming from?
// [ 1, 2, 5 ],
// [ 3, 4 ],
// [ 6 ],
// [ 7, 8, 9 ],
// [ 10, 11 ],
// [ 12, 13 ],
// [], <-- Also here
// [ 14, 15 ],
// [ 16, 17 ],
// [ 4, 4 ],
// [ 1 ],
// [ 1, 1 ]
// ]
.as-console-wrapper {
max-height: 100% !important;
}
Element like [[14, 15], [16, 17]] will introduce a [] after recursion. This should be handled by checking length.
var arrs = [
[1, 2, [3, 4], 5],
[6, [7, 8, 9, [10, 11]]],
[12, 13],
[[14, 15], [16, 17]],
[[1], 4, [1, 1], 4]
];
function extractArrays (arr, acc=[]) {
if (arr.length == 0 ) return acc;
let pure = arr.filter(elm => !Array.isArray(elm));
if (pure.length > 0) {
acc.push(pure);
}
acc.concat(arr.filter(elm => Array.isArray(elm)).map(elm => extractArrays(elm, acc)));
return acc;
}
console.log(extractArrays(arrs));
You can try the following code
var arrs = [
[1, 2, [3, 4], 5],
[6, [7, 8, 9, [10, 11]]],
[12, 13],
[
[14, 15],
[16, 17]
], // <-- added additional test case
[
[1], 4, [1, 1], 4
]
];
function extractArrays(arr) {
return arr.reduce((res, curr, i) => {
if (Array.isArray(curr)) {
res = res.concat(extractArrays(curr));
} else {
let index = 0;
for (let j = 0; j <= i; j++) {
if (!Array.isArray(arr[j])) {
res[index] ? res[index].push(curr) : res.push([curr]);
break;
} else {
index++;
}
}
}
return res;
}, []); // <-- no initial empty array inside here
}
console.log(extractArrays(arrs));
I just wanted to share my approach to this problem, I enjoyed trying to solve it, in my case I also passed an array to the extractArrays method, in order to make easier to capture and filter every array inside the arrs param.
let result = [];
extractArrays(arrs, result);
console.log(result);
function extractArrays(arr, result) {
let newResult = arr.reduce((acc, curr) => {
if (Array.isArray(curr)) {
extractArrays(curr, result);
} else {
acc.push(curr);
}
return acc;
}, []);
newResult.length && result.push(newResult);
}
You can check it when you return from function. stackblitz
function extractArray(arr) {
const res = arr.reduce((res, curr) => {
if(!Array.isArray(curr)){
return [[...res[0], curr], ...res.slice(1)]
}
return [...res, ...extractArray(curr)]
}, [[]]);
return res[0].length ? res : res.slice(1);
}
EDIT: More performant function (check stackblitz link)
function extractFaster(arr) {
let res = [0];
function recExtract(arr) {
let hasNonArrayElm = false;
let index = res.length -1;
arr.forEach(curr => {
if (!Array.isArray(curr)) {
hasNonArrayElm ? res[index].push(curr) : res.splice(index, 0, [curr]);
hasNonArrayElm = true;
return;
}
recExtract(curr);
});
}
recExtract(arr);
res.splice(-1, 1)
return res;
}
EDIT: The answer below the line is a great way to flatten arrays, but I suggested it because I misunderstood this question. I will leave it in case it benefits someone to know, but in order to keep an accurate record, I'll also update my answer to address the problem posed in the question.
The accepted answer seems sufficient enough, but I'll try my hand at it. I would use Array.reduce to cover everything with one swoop, and inside use Array.filter to separate the normal items from the array items, then use the spread ... operator on the nested arrays so everything gets pushed to the same level, after recursively calling the same extract function on all nested arrays. Honestly, the explanation might be harder to understand than the code, have a look:
const data = [
[1, 2, [3, 4], 5],
[6, [7, 8, 9, [10, 11]]],
[12, 13],
[[14, 15], [16, 17]],
[[1], 4, [1, 1], 4]
]
const extractChildArrays = arrs => arrs.reduce((acc, cur) => {
const nestedArrs = cur.filter(a => Array.isArray(a))
const normalItems = cur.filter(a => !Array.isArray(a))
acc.push(normalItems, ...extractChildArrays(nestedArrs))
return acc
}, [])
console.log(extractChildArrays(data))
UPDATE: Array.flat() is now accepted as part of the spec and it's supported by all modern browsers except Edge.
In ES6 there is actually an experimental array method called flat(). As of the writing of this answer, it's only compatible with Chrome, but a polyfill might be worth looking into, because it's SO EASY!
The first parameter of flat() is depth, so with the help of another answer, you can easily figure that out dynamically.
const data = arrs = [
[1, 2, [3, 4], 5],
[6, [7, 8, 9, [10, 11]]],
[12, 13],
[[14, 15], [16, 17]],
[[1], 4, [1, 1], 4]
]
const flattenArray = arr => {
const getDepth = a => Array.isArray(a) ?
1 + Math.max(...a.map(getDepth)) : 0
return arr.flat(getDepth(arr))
}
console.log(flattenArray(data))