Extract child arrays from nested arrays - javascript

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

Related

An unique arrays of numbers from an Array with JavaScript

Can anyone tell me how to solv this problem please:
I tried doing this with array.map, array.filter, array.reduce but i did not got result:
Write a function putNum(arrayOfNum: number[], num: number),
which would find all possible combinations of numbers from arrayOfNum,
whose sum is equal to number. Wherein:
arrayOfNum contains only unique positive numbers (>0)
there should not be repetitions of numbers in the combination
all combinations must be unique
#param arrayOfNum: number[]
#param num: number[]
#return Array<Array<number>>
function putNum(arrayOfNum, num) {
***// write code only inside this function***
return [[1, 2], [3]];
}
// console.log(putNum([8, 2, 3, 4, 6, 7, 1], 99)); => []
// console.log(putNum([8, 2, 3, 4, 6, 7, 1], 5)); => [[2, 3], [4, 1]]
// console.log(putNum([1, 2, 3, 4, 5, 6, 7, 8], 8)); => [[1, 3, 4], [1, 2, 5], [3, 5], [2, 6], [1, 7], [8]]
let resultnum = result.filter(e => typeof e === 'number' && e > 0); // to make a new array with nums > 0
The best approach to solve this problem in optimized way is to use hash map
let twoSum = (array, sum) => {
let hashMap = {},
results = []
for (let i = 0; i < array.length; i++){
if (hashMap[array[i]]){
results.push([hashMap[array[i]], array[i]])
}else{
hashMap[sum - array[i]] = array[i];
}
}
return results;
}
console.log(twoSum([10,20,40,50,60,70,30],50));
Output:
[ [ 10, 40 ], [ 20, 30 ] ]

How to create a new 2D array out of each depths from a given nested array?

Input:
[1,2,[3,4,[5,6]]]
Output:
[[1,2],[3,4],[5,6]]
Here is the solution:
function convert(a,res=[]) {
const group = (arr) => {
res.push(arr.slice(0,2));
arr.map((v) => Array.isArray(v) && group(v));
}
group(a);
return res;
}
console.log(convert([1,2,[3,4]])); // [[1,2],[3,4]]
console.log(convert([1,2,[3,4,[5,6]]])); // [[1,2],[3,4],[5,6]]
console.log(convert([1,2,[3,4,[5,6,[7,8]]]])); // [[1,2],[3,4],[5,6],[7,8]];
Although the problem is solved, For learning purposes, I've always wonder how to solve this without the nesting function approach. I've tried to refactor the code as followed:
function convert(a,i=0,res=[]) {
return i >= a.length
? res
: convert(
a,
i+1,
Array.isArray(a[i]) ? [...res,a.slice(0,2)] : res
)
}
console.log(convert([1,2,[3,4]])); // [[1,2]]
console.log(convert([1,2,[3,4,[5,6]]])); // [[1,2]]
console.log(convert([1,2,[3,4,[5,6,[7,8]]]])); // [[1,2]]
As you can see, the result is less than ideal. I just couldn't quite wrap my head around it. Any feedback and pointer will be greatly appreciated :)
UPDATE:
Here is the solution which covers even more test cases:
function convert(a,res=[]) {
return !a.length
? res
: convert(
a.filter(Array.isArray).flat(),
[...res,a.filter((v) => !Array.isArray(v))]
);
}
console.log(convert([1,2,[3,4]])); // [[1,2],[3,4]]
console.log(convert([1,2,[3,4,[5,6]]])); // [[1,2],[3,4],[5,6]]
console.log(convert([1,2,[3,4,[5,6,[7,8]]]])); // [[1,2],[3,4],[5,6],[7,8]];
console.log(convert([1,2,[5,6,[9,10],7,8],3,4])); // [[1,2,3,4],[5,6,7,8],[9,10]]
console.log(convert([1,5,5,[5,[1,2,1,1],5,5],5,[5]])); // [[1,5,5,5],[5,5,5,5],[1,2,1,1]]
console.log(convert([1,[2],1,[[2]],1,[[[2]]],1,[[[[2]]]]])); // [[1,1,1,1],[2],[2],[2],[2]]
I'm not sure if this is what you mean, but you can use a straight recursive call, spreading each result into the previous.
const input = [1, 2, [3, 4, [5, 6]]];
function flatten(arr) {
const res = [[]];
for (const e of arr) {
Array.isArray(e) ? res.push(...flatten(e)) : res[0].push(e);
}
return res;
}
console.log(flatten(input));
This works even if the elements on any level are split by nested levels, and for variable numbers of elements on any given level.
const input = [1, 2, [4, [5, 6, [8, 9], 7]], 3];
// [[ 1, 2, 3 ], [ 4 ], [ 5, 6, 7 ], [ 8, 9 ]]
function flatten(arr) {
const res = [[]];
for (const e of arr) {
Array.isArray(e) ? res.push(...flatten(e)) : res[0].push(e);
}
return res;
}
console.log(flatten(input));
Edit
To accommodate the added conditions brought up in the comments, but maintain straight recursion without passing an accumulator to later calls, I might do something like the following.
function flatten(arr) {
const level = [], nested = [];
for (const e of arr) {
Array.isArray(e) ? nested.push(...e) : level.push(e);
}
return [level, ...(nested.length ? flatten(nested) : [])]
}
console.log(flatten([1, 2, [3, 4, [5, 6]]]));
// [[ 1, 2 ], [ 3, 4 ], [ 5, 6 ]]
console.log(flatten([1, 2, [4, [5, 6, [8, 9], 7]], 3]));
// [[ 1, 2, 3 ], [ 4 ], [ 5, 6, 7 ], [ 8, 9 ]]
console.log(flatten([1, [2], 1, [[2]], 1, [[[2]]], 1, [[[[2]]]]]));
// [[ 1, 1, 1, 1 ], [ 2 ], [ 2 ], [ 2 ], [ 2 ]]
console.log(flatten([1, 5, 5, [5, [1, 2, 1, 1], 5, 5], 5, [5]]));
// [[ 1, 5, 5, 5 ], [ 5, 5, 5, 5 ], [ 1, 2, 1, 1 ]]
I think your updated algorithm is fine.
It might be easier to express with a reusable utility function, though. I usually have a partition function lying around which accepts a predicate function and returns a function which splits an array into two sub-arrays, those for which the predicate returns true, and those for which it's false.
Using that, this becomes fairly simple:
const partition = (pred) => (xs) =>
xs .reduce (([t, f], x) => pred (x) ? [t .concat (x), f]: [t, f .concat (x)], [[], []])
const convert = (xs, [rest, first] = partition (Array .isArray) (xs)) =>
xs .length == 0 ? [] : [first, ...convert (rest)]
console.log (convert ([1, 2, [3, 4]])) //=> [[1, 2], [3, 4]]
console.log (convert ([1, 2, [3, 4, [5, 6]]])) //=> [[1, 2], [3, 4], [5, 6]]
console.log (convert ([1, 2, [3, 4, [5, 6, [7, 8]]]])) //=> [[1, 2], [3, 4], [5, 6], [7, 8]]
console.log (convert ([1, 2, [5, 6, [9, 10], 7, 8], 3, 4])) //=> [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]
console.log (convert ([1, 5, 5, [5, [1, 2, 1, 1], 5, 5], 5, [5]])) //=> [[1, 5, 5, 5], [5, 5, 5, 5], [1, 2, 1, 1]]
console.log (convert ([1, [2], 1, [[2]], 1, [[[2]]], 1, [[[[2]]]]])) //=> [[1, 1, 1, 1], [2], [2], [2], [2]]
.as-console-wrapper {max-height: 100% !important; top: 0}

Sum values in 2D array based on lookup value - Javascript

I have two arrays:
const array = [
[1, 7, 'AAA'],
[2, 5, 'BBB'],
[3, 2, 'CCC'],
[4, 4, 'DDD'],
[4, 9, 'EEE'],
[4, 2, 'FFF'],
[5, 8, 'GGG'],
[6, 2, 'HHH']];
const names = [
[1, 'Joe'],
[2, 'Dave'],
[3, 'Mike'],
[4, 'Sandra'],
[5, 'Sue'],
[6, 'Mary']];
Based on the value in the first column, I want to sum the values in the array[1] and list the three-character letters. The result I'm trying to get is:
const names = [
[1, 'Joe',7,'AAA'],
[2, 'Dave',5,'BBB'],
[3, 'Mike',2,'CCC'],
[4, 'Sandra',15,'DDD, EEE, FFF'],
[5, 'Sue',8,'GGG'],
[6, 'Mary',2,'HHH']]
I'm not sure of the best approach, I'm fairly new to Javascript. What I've managed to do is get the right result when a value in array[0] isn't repeated, but I can't get a sum or list to work.
const counter = (array,value) => array.filter((v) => (v === value)).length;
const arrayCol = (array,value) => array.map(v => v[value]);
const sum = (prevVal, curVal) => prevVal + curVal;
names.forEach ((p,e) => {
array.forEach ((v,x) => (counter(arrayCol(array,0),v[0])===1) ?
(v[0]===p[0]) && names[e].push(v[1],v[2]) :
(v[0]===p[0]) && names[e].push(array.reduce(sum,0)) );
});
console.log(names);
I'm sure the answer has to do with map or filter but not sure how... any pointers appreciated. Thank you
EDIT: All three answers below (from Michael Haddad, Nina Scholz, and testing_22) work and are interesting.
You can use a combination of map and reduce, as in:
const array = [[1, 7, 'AAA'], [2, 5, 'BBB'], [3, 2, 'CCC'],[4, 4, 'DDD'], [4, 9, 'EEE'], [4, 2, 'FFF'], [5, 8, 'GGG'], [6, 2, 'HHH']];
const names = [[1, 'Joe'],[2, 'Dave'],[3, 'Mike'],[4, 'Sandra'],[5, 'Sue'],[6, 'Mary']];
const result = names.map(([id, name]) => {
let vals = [];
let sum = array.reduce((acc, [idx, number, XXX]) =>
(idx === id ? (vals.push(XXX), number) : 0) + acc, 0);
return [
id,
name,
sum,
vals.join(", ")
]
})
console.log(result)
You could collect all data for each group and then map the result in order of the names array.
const
array = [[1, 7, 'AAA'], [2, 5, 'BBB'], [3, 2, 'CCC'], [4, 4, 'DDD'], [4, 9, 'EEE'], [4, 2, 'FFF'], [5, 8, 'GGG'], [6, 2, 'HHH']],
names = [[1, 'Joe'], [2, 'Dave'], [3, 'Mike'], [4, 'Sandra'], [5, 'Sue'], [6, 'Mary']],
groups = array.reduce((r, [id, value, code]) => {
r[id] ??= [0, ''];
r[id][0] += value;
r[id][1] += (r[id][1] && ', ') + code;
return r;
}, {}),
result = names.map(a => [...a, ...groups[a[0]]]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A basic approach could be:
const array = [[1, 7, 'AAA'], [2, 5, 'BBB'], [3, 2, 'CCC'], [4, 4, 'DDD'], [4, 9, 'EEE'], [4, 2, 'FFF'], [5, 8, 'GGG'], [6, 2, 'HHH']];
const names = [[1, 'Joe'], [2, 'Dave'], [3, 'Mike'], [4, 'Sandra'], [5, 'Sue'], [6, 'Mary']];
let result = [];
for (let name of names) {
let newValue = [...name, 0];
let matchingItems = array.filter(i => i[0] === name[0]);
let strings = []; // for lack of a better name...
for (let item of matchingItems) {
newValue[2] += item[1];
strings.push(item[2]);
}
newValue.push(strings.join(", "));
result.push(newValue);
}
console.log(result);
You could also implement the joining logic yourself (I actually prefer this version for readability reasons):
const array = [[1, 7, 'AAA'], [2, 5, 'BBB'], [3, 2, 'CCC'], [4, 4, 'DDD'], [4, 9, 'EEE'], [4, 2, 'FFF'], [5, 8, 'GGG'], [6, 2, 'HHH']];
const names = [[1, 'Joe'], [2, 'Dave'], [3, 'Mike'], [4, 'Sandra'], [5, 'Sue'], [6, 'Mary']];
let result = [];
for (let name of names) {
let newValue = [...name, 0, ""];
let matchingItems = array.filter(i => i[0] === name[0]);
for (let item of matchingItems) {
newValue[2] += item[1];
newValue[3] += newValue[3] === "" ? item[2] : `, ${item[2]}`;
}
result.push(newValue);
}
console.log(result);

How to .filter an array against an array with .includes?

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

Performant way to convert adjacency list to links for undirected graph

I have an adjacency list like below:
const list = [
[1, 6, 8],
[0, 4, 6, 9],
[4, 6],
[4, 5, 8],
// ...
];
I need to create a set of links for an undirected graph without duplicates (example bellow).
Such links as [0,1] and [1,0] are considered duplicates.
const links = [
[ 0, 1 ], // duplicates
[ 0, 6 ],
[ 0, 8 ],
[ 1, 0 ], // duplicates
[ 1, 4 ],
// ...
]
Right now I do it this way:
const links = new Set;
const skip = [];
list.forEach( (v, i) => {
v.forEach( j => {
if (skip.indexOf(j) === -1) {
links.add([i, j]);
}
})
skip.push(i);
})
I am wondering if there is a better pattern to solve this kind of task on massive arrays.
You could sort your link tuple values, skip the check skip.indexOf(j) and let Set take care of the duplicates.
You could take a stringed array as value for the for the set, because an array with only sorted value is checking with strict mode in the set.
A primitive data type, like string works best.
var list = [[1, 6, 8], [0, 4, 6, 9], [4, 6], [4, 5, 8]],
links = new Set;
list.forEach((v, i) => v.forEach(j => links.add([Math.min(i, j), Math.max(i, j)].join())));
console.log([...links]);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use one object to store value: index that has already been used and then check that object before adding to array.
const list = [[1, 6, 8],[0, 4, 6, 9],[4, 6],[4, 5, 8],];
var o = {},r = []
list.forEach(function(e, i) {
e.forEach(function(a) {
if (o[i] != a) {
r.push([i, a])
o[a] = i
}
})
})
console.log(JSON.stringify(r))
With ES6 arrow functions you can write the same like this.
const list = [[1, 6, 8], [0, 4, 6, 9], [4, 6], [4, 5, 8],];
var o = {}, r = []
list.forEach((e, i) => e.forEach(a => o[i] != a ? (r.push([i, a]), o[a] = i) : null))
console.log(JSON.stringify(r))

Categories

Resources