get highest element in a two dimensional array - javascript

i have this array:
const myArray = [["Cow", 3], ["Pig", 5], ["Pig", 10], ["Pig", 4], ["Chicken", 1], ["Cow", 1], ["Cow" , 12], ["Cow", 11], ["Chicken", 12]]
and want to turn into this: (only the highest of each one)
[ [ 'Pig', 10 ], [ 'Cow', 12 ], [ 'Chicken', 12 ] ]
but with my code i cant get the last one, i cant find why tho
const myArray = [["Cow", 3], ["Pig", 5], ["Pig", 10], ["Pig", 4], ["Chicken", 1], ["Cow", 1], ["Cow" , 12], ["Cow", 11], ["Chicken", 12]]
function getHighest() {
var onlyHighest = [];
myArray.sort(
function(a,b) {
if (a[0] == b[0])
return a[1] < b[1] ? -1 : 1;
return a[0] < b[0] ? 1 : -1;
}
);
myArray.forEach((a, i) => {
var i = i+1;
if (i < myArray.length) {
if (a[0] != myArray[i][0]){
onlyHighest.push([a[0], a[1]]);
}
}
});
return console.log(onlyHighest)
// [ [ 'Pig', 10 ], [ 'Cow', 12 ] ]
}

Use an object to hold the current highest value for each animal. Loop through the array, replacing the value when it's higher than the value in the object.
const myArray = [["Cow", 3], ["Pig", 5], ["Pig", 10], ["Pig", 4], ["Chicken", 1], ["Cow", 1], ["Cow" , 12], ["Cow", 11], ["Chicken", 12]]
function getHighest(array) {
let obj = {};
array.forEach(([key, value]) => {
if (key in obj) {
if (value > obj[key]) {
obj[key] = value;
}
} else {
obj[key] = value;
}
});
return Object.entries(obj);
}
console.log(getHighest(myArray));

Your
myArray.forEach((a, i) => {
var i = i+1;
if (i < myArray.length) {
is causing the problem and is pretty confusing. If the last sorted chunk of the array contains only 2 elements, that condition won't be fulfilled, so nothing from that chunk will be pushed.
A quick fix would be to unconditionally push the original element if it's the last one in the array.
myArray.forEach((a, i) => {
if (i === myArray.length - 1) {
A better refactor would be to group the input into an object that keeps the largest value for each property, no sorting involved.
const myArray = [["Cow", 3], ["Pig", 5], ["Pig", 10], ["Pig", 4], ["Chicken", 1], ["Cow", 1], ["Cow" , 12], ["Cow", 11], ["Chicken", 12]]
const grouped = {};
for (const [prop, num] of myArray) {
grouped[prop] = Math.max(num, grouped[prop] ?? -Infinity);
}
const onlyHighest = Object.entries(grouped);
console.log(onlyHighest);

How about a simple sort
function funk() {
let a = [["Cow", 3], ["Pig", 5], ["Pig", 10], ["Pig", 4], ["Chicken", 1], ["Cow", 1], ["Cow", 12], ["Cow", 11], ["Chicken", 12]];
let r = a.sort((a, b) => {
return b[1] - a[1]
})[0];
Logger.log(r);
}
Execution log
12:37:11 PM Notice Execution started
12:37:11 PM Info [Cow, 12.0]
12:37:12 PM Notice Execution completed

Related

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

Get all (number of) combinations of an array

I have been trying to accomplish this since yesterday, though no luck yet. I have found solutions where there always is a slight difference in what I want to accomplish.
I am trying to get all possible combinations, slightly like this: combination_k, but I also want the same items to pair up with itself, so given the following:
input [1, 4, 5] and 2 (number of combinations) should return:
[1, 1], [1, 4], [1, 5], [4, 4], [4, 5], [5, 5]
input [1, 4, 5] and 3 should return:
[1, 1, 1], [1, 1, 4], [1, 1, 5], [1, 4, 4], [1, 4, 5], [4, 4, 4], [4, 4, 5], [5, 5, 5], [5, 5, 4], [5, 5, 1] (The order is not important).
I have been adjusting combination_k, it got me far enough that it worked with 2 but it didn't work when I provided 3 as a parameter.
const combinations = getAllCombinations([1, 4, 5], 2);
// combinations = [1, 1], [1, 4], [1, 5], [4, 4], [4, 5], [5, 5]
Any tips are welcome!
The problem is commonly referred to as k-combinations with repetitions.
Here's a solution that relies on recursion to get the desired result:
const combinations = (array, r) => {
const result = [];
const fn = (array, selected, c, r, start, end) => {
if (c == r) {
result.push([...selected]);
return;
}
for (let i = start; i <= end; i++) {
selected[c] = array[i];
fn(array, selected, c + 1, r, i, end);
}
}
fn(array, [], 0, r, 0, array.length - 1);
return result;
}
console.log(combinations([1, 4, 5], 3));
A modified version of the code you provided:
function getAllCombinations(arr, n) {
if (n <= 0) return [];
if (n === 1) return [...arr];
return arr.reduce((acc, cur, i) => {
const head = arr.slice(i, i + 1);
const combinations = getAllCombinations(arr.slice(i), n - 1)
.map(x => head.concat(x));
return [...acc, ...combinations];
}, []);
}
console.log(getAllCombinations([1, 4, 5], 2).join('|'));
console.log(getAllCombinations([1, 4, 5], 3).join('|'));

Interval range insert into intervals without merging existing intervals

Problem description:
The idea is to insert into existing intervals new interval which doesn't merge with existing intervals but fills the missing gaps between intervals. (This is not the interval merging problem)
For example, inserting interval [0, 7] to intervals [[0, 1], [3, 5]] would result new intervals with gaps filled [[0, 1], [1, 3], [3, 5], [5, 7]].
Interval range is already sorted smallest to larger [[0, 1], [3, 5]].
My current solution is a bit "broken", I ended up using too many if checks to cover some special cases which makes everything more complex then needed. I am looking for better ways to simplify the condition part. In the bottom of the code there are test cases included, also cases where my solution fails.
The test cases where my algorithm is failing and producing wrong results:
assert.deepEqual( // Broken
insertIntervalSec([[1, 5], [7, 10]], [4, 12]),
[[1, 5], [5, 7], [7, 10], [10, 12]],
);
assert.deepEqual(insertIntervalSec([[1, 1]], [1, 3]), [[1, 3]]); // Broken
function isOverLapping(a, b) {
return Math.max(a[0], b[0]) <= Math.min(a[1], b[1]);
}
function insertIntervalSec(arr, interval) {
const result = [];
let i = 0;
const contains = (a, b) => {
return a[0] >= b[0] && a[1] <= b[1]
};
if (arr.length <= 0) {
result.push(interval);
return result;
}
if (arr.length === 1 && contains(interval, arr[0])) {
result.push(interval);
return result;
}
// Start point
if (interval[1] >= arr[0][0] && isOverLapping(interval, arr[0])) {
result.push([interval[0], arr[0][0]]);
} else if (interval[1] <= arr[0][0]) {
result.push([interval[0], Math.min(interval[1], arr[0][0])]);
}
while (i < arr.length) {
const current = arr[i];
result.push(arr[i]);
if (!contains(interval, arr[i]) && isOverLapping(arr[i], interval)) {
const next = arr[i + 1];
// Special handling for the last item
if (next !== undefined) {
if (interval[1] > current[1]) {
result.push([current[1], next[0]]);
}
} else {
if (interval[0] <= current[0] && interval[1] <= current[1]) {
// TODO: No action
} else if (interval[0] >= current[0] || interval[1] >= current[0]) {
result.push([current[1], interval[1]]);
}
}
}
i++;
}
// End point
const len = arr.length;
const last = arr[len - 1];
if (last[1] <= interval[0] && !isOverLapping(last, interval)) {
result.push(interval);
}
return result;
}
assert.deepEqual(
insertIntervalSec([[1, 5], [10, 15], [20, 25]], [12, 27]),
[[1, 5], [10, 15], [15, 20], [20, 25], [25, 27]]
);
assert.deepEqual(
insertIntervalSec([[1, 5], [10, 15], [20, 25]], [-3, 0]),
[[-3, 0], [1, 5], [10, 15], [20, 25]]
);
assert.deepEqual(
insertIntervalSec([[1, 5], [10, 15], [20, 25]], [-3, 3]),
[[-3, 1], [1, 5], [10, 15], [20, 25]]
);
assert.deepEqual(
insertIntervalSec([[0, 5], [10, 15], [20, 25]], [15, 15]),
[[0, 5], [10, 15], [20, 25]]
);
assert.deepEqual(
insertIntervalSec([[0, 5], [10, 15], [20, 25]], [20, 21]),
[[0, 5], [10, 15], [20, 25]]
);
assert.deepEqual(
insertIntervalSec([[0, 5], [10, 15], [20, 25]], [26, 27]),
[[0, 5], [10, 15], [20, 25], [26, 27]]
);
assert.deepEqual(
insertIntervalSec([[0, 5], [10, 15], [20, 25]], [25, 27]),
[[0, 5], [10, 15], [20, 25], [25, 27]]
);
assert.deepEqual(insertIntervalSec([], [25, 27]), [[25, 27]]);
assert.deepEqual(insertIntervalSec([[1, 1]], [1, 1]), [[1, 1]]);
assert.deepEqual( // Broken
insertIntervalSec([[1, 5], [7, 10]], [4, 12]),
[[1, 5], [5, 7], [7, 10], [10, 12]],
);
assert.deepEqual(insertIntervalSec([[1, 1]], [1, 3]), [[1, 3]]); // Broken
assert.deepEqual(
insertIntervalSec2([[5, 5]], [6, 6]),
[[5, 5], [6, 6]]
);
assert.deepEqual(
insertIntervalSec2([[1, 3]], [6, 6]),
[[1, 3], [6, 6]]
);
With the exception of the last test case (see comment on question), this passes all the tests. The basic idea is you just keep track of start variable that indicated where how much of the inserted range you have used. This allows you to narrow it down to three cases:
the inserted interval fits entirely before the the current item
the current item in the iteration fits completely before the inserted interval
the item in the iteration overlaps.
After iterating the items, you can check if the inserted range has anything left to insert:
function insertIntervalSec(arr, insert) {
let start = insert[0]
let res = []
for (i = 0; i < arr.length; i++) {
let a = arr[i]
// smaller item in range
if (a[0] <= start) {
res.push(a)
start = Math.max(a[1], start)
continue
}
// moved past inserted interval add rest of arr
if (start >= insert[1]) {
res.push(...arr.splice(i))
break
}
// fill in spaces
let end = Math.min(insert[1], a[0])
res.push([start, end], a)
start = a[1]
}
// clean up left over range
if (start < insert[1]) res.push([start, insert[1]])
return res
}
console.log(insertIntervalSec([ [1, 5],[10, 15],[20, 25]], [-2, 27]))

Find if two arrays are repeated in array and then select them

I have multiple arrays in a main/parent array like this:
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
here are the array's for simpler reading:
[1, 17]
[1, 17]
[1, 17]
[2, 12]
[5, 9]
[2, 12]
[6, 2]
[2, 12]
[2, 12]
I want to select the arrays that are repeated 3 or more times (> 3) and assign it to a variable. So in this example, var repeatedArrays would be [1, 17] and [2, 12].
So this should be the final result:
console.log(repeatedArrays);
>>> [[1, 17], [2, 12]]
I found something similar here but it uses underscore.js and lodash.
How could I it with javascript or even jquery (if need be)?
Try this
array.filter(( r={}, a=>!(2-(r[a]=++r[a]|0)) ))
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
var r= array.filter(( r={}, a=>!(2-(r[a]=++r[a]|0)) ))
console.log(JSON.stringify(r));
Time complexity O(n) (one array pass by filter function). Inspired by Nitish answer.
Explanation
The (r={}, a=>...) will return last expression after comma (which is a=>...) (e.g. (5,6)==6). In r={} we set once temporary object where we will store unique keys. In filter function a=>... in a we have current array element . In r[a] JS implicity cast a to string (e.g 1,17). Then in !(2-(r[a]=++r[a]|0)) we increase counter of occurrence element a and return true (as filter function value) if element a occurred 3 times. If r[a] is undefined the ++r[a] returns NaN, and further NaN|0=0 (also number|0=number). The r[a]= initialise first counter value, if we omit it the ++ will only set NaN to r[a] which is non-incrementable (so we need to put zero at init). If we remove 2- as result we get input array without duplicates - or alternatively we can also get this by a=>!(r[a]=a in r). If we change 2- to 1- we get array with duplicates only.
UPDATE
Even shorter version based on #ken comment can be written (it should always work with arrays of numbers). The original longer version of #ken code is in snippet and shows how #ken uses in clever way second argument of .filter to avoid usage global variable r.
array.filter(a=>!(2-(this[a]=++this[a]|0)))
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
var r= array.filter(a=>!(2-(this[a]=++this[a]|0)), {})
console.log(JSON.stringify(r));
You could take a Map with stringified arrays and count, then filter by count and restore the arrays.
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]],
result = Array
.from(array.reduce(
(map, array) =>
(json => map.set(json, (map.get(json) || 0) + 1))
(JSON.stringify(array)),
new Map
))
.filter(([, count]) => count > 2)
.map(([json]) => JSON.parse(json));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Filter with a map at wanted count.
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]],
result = array.filter(
(map => a =>
(json =>
(count => map.set(json, count) && !(2 - count))
(1 + map.get(json) || 1)
)
(JSON.stringify(a))
)
(new Map)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Unique!
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]],
result = array.filter(
(s => a => (j => !s.has(j) && s.add(j))(JSON.stringify(a)))
(new Set)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use Object.reduce, Object.entries for this like below
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
let res = Object.entries(
array.reduce((o, d) => {
let key = d.join('-')
o[key] = (o[key] || 0) + 1
return o
}, {}))
.flatMap(([k, v]) => v > 2 ? [k.split('-').map(Number)] : [])
console.log(res)
OR may be just with Array.filters
var array = [[1, 17], [1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
let temp = {}
let res = array.filter(d => {
let key = d.join('-')
temp[key] = (temp[key] || 0) + 1
return temp[key] == 3
})
console.log(res)
For a different take, you can first sort your list, then loop through once and pull out the elements that meet your requirement. This will probably be faster than stringifying keys from the array even with the sort:
var arr = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]]
arr.sort((a, b) => a[0] - b[0] || a[1] - b[1])
// define equal for array
const equal = (arr1, arr2) => arr1.every((n, j) => n === arr2[j])
let GROUP_SIZE = 3
first = 0, last = 1, res = []
while(last < arr.length){
if (equal(arr[first], arr[last])) last++
else {
if (last - first >= GROUP_SIZE) res.push(arr[first])
first = last
}
}
if (last - first >= GROUP_SIZE) res.push(arr[first])
console.log(res)
You could also do this with a single Array.reduce where you would only push to a result property if the length is equal to 3:
var array = [[1, 17], [1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
console.log(array.reduce((r,c) => {
let key = c.join('-')
r[key] = (r[key] || 0) + 1
r[key] == 3 ? r.result.push(c) : 0 // if we have a hit push to result
return r
}, { result: []}).result) // print the result property
ES6:
const repeatMap = {}
array.forEach(arr => {
const key = JSON.stringify(arr)
if (repeatMap[key]) {
repeatMap[key]++
} else {
repeatMap[key] = 1
}
})
const repeatedArrays = Object.keys(repeatMap)
.filter(key => repeatMap[key] >= 3)
.map(key => JSON.parse(key))

Extract child arrays from nested arrays

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

Categories

Resources