How can I recursively convert a nested array to a flat array? - javascript

I'm trying to get this nested array to a flat array. While using this way to solve it seems every time I callback arrayFlattener(element) the newArr become a empty array. Can someone help me with this? Thank you.
const arrayFlattener = (arr) => {
let newArr = [];
for (let i = 0; i < arr.length; i++) {
let element = arr[i];
if (Array.isArray(element)){
newArr.push(arrayFlattener(element));
} else {
newArr.push(element);
}
}
return newArr;
}
console.log(arrayFlattener(['I', 'am', 'working', ['on', 'another', 'level']]));

flat do the job with the depth param level specifying how deep a nested array structure should be flattened.
Example
const arr = ['I', 'am', 'working', ['on', 'another', 'level'], 'now', ["now", ["hello", "you you"]]]
console.log(arr.flat(2))

Your code and theory are fine. You just chose the wrong method. Use concat instead of push (to extend the result rather than insert into it):
const arrayFlattener = (arr) => {
let newArr = [];
for (let i = 0; i < arr.length; i++) {
let element = arr[i];
if (Array.isArray(element)){
newArr = newArr.concat(arrayFlattener(element));
} else {
newArr.push(element);
}
}
return newArr;
}
console.log(arrayFlattener(['I', 'am', 'working', ['on', 'another', 'level']]));

You can use flatMap
let newArr = ['I', 'am', 'working', ['on', 'another', 'level']].flatMap(el=>el);
console.log(newArr);
or use flat
var arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]
var arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]
var arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2); // depth argument to flatten the array
// [1, 2, 3, 4, 5, 6]

Currently, your function did not flatten the array, but simply parse through every individual element of the array. It still returns the same array structure.
To flatten the array, you should pass the resulting array as well, so that the individual element can be pushed straight into the resulting array instead of making another array and push it to the resulting array (which produce the same initial array structure)
let newArr = [];
const arrayFlattener = (arr, result) => {
for (let i = 0; i < arr.length; i++) {
let element = arr[i];
if (Array.isArray(element)){
result = arrayFlattener(element, result);
} else {
result.push(element);
}
}
return result
}
console.log(arrayFlattener(['I', 'am', 'working', ['on', 'another', 'level'], 'now'], newArr));

Here's three solutions
You can use .flatMap and recursion or
const flatten = (xs) =>
Array.isArray(xs) ? xs.flatMap(flatten) : [xs]
const array = ['I', 'am', 'working', ['on', 'another', ['level']]]
console.log(flatten(array))
you can use .reduce and recursion
const flatten = (xs) =>
xs.reduce(
(y, x) => y.concat(Array.isArray(x) ? flatten(x) : [x]),
[])
const array = ['I', 'am', 'working', ['on', 'another', ['level']]]
console.log(flatten(array))
or even better just use .flat
const flatten = (xs) =>
xs.flat(Infinity)
const array = ['I', 'am', 'working', ['on', 'another', ['level']]]
console.log(flatten(array))

Related

How to get element of array with string index path

I have an array and I have a path to a specific element.
const str = "[0].subArray[2]"
const arr = [
{ subArray: [1, 2, 3, 4, 5] },
{ subArray: [32, 321, 11]}
];
Is it possible somehow to display an element using a string path?
You could take a dynamic approach for an length of the path.
function getValue(object, path) {
return path
.replace(/\[/g, '.')
.replace(/\]/g, '')
.split('.')
.filter(Boolean)
.reduce((o, k) => (o || {})[k], object);
}
console.log(getValue([{ subArray: [1, 2, 3, 4, 5] }, { subArray: [32, 321, 11] }], "[0].subArray[2]"));
You can achieve the result with match.
use regex here
/[a-zA-Z]+|\d+/g
const str = "[0].subArray[2]";
const arr = [{ subArray: [1, 2, 3, 4, 5] }, { subArray: [32, 321, 11] }];
function getValue(arr) {
const [first, prop, index] = str.match(/[a-zA-Z]+|\d+/g);
return arr[first][prop][index];
}
const result = getValue(arr);
console.log(result);
You could extract the key values out of the string using replace with a regex for numbers only, then reconstruct an array to output the sections of your sub-arrays you are targeting using a conditional to check the index to locate array and sub-array keys/values.
const arr = [
{ subArray: [1, 2, 3, 4, 5] },
{ subArray: [32, 321, 11]}
];
const str1 = "[0].subArray[2].subArray[1].subArray[4].subArray[1]"
const str2 = "[1].subArray[2].subArray[0]"
function removeNums(val){
return val.replace(/[^0-9]/g, '')
}
function getArrayValues(str){
// create an array by splitting the string at the dots
const arrs = str.split('.')
const results = []
for(let i in arrs){
// get the first index to locate the proper subarray
// remove all but numbers from the indexes
const firstIndex = removeNums(arrs[0])
// skip the first index and get remaining indexes
// to get the rest of the subarrays values
if(i > 0){
// remove all but numbers from the indexes
const index = removeNums(arrs[i])
// construct an array of the proper values
results.push(arr[firstIndex].subArray[index])
}
}
return results
}
console.log(getArrayValues(str1))
console.log(getArrayValues(str2))

interleave mutliple arrays in javascript

We have an Array of arrays, which we want to interleave into a single array:
i.e:
masterArray = [[1, 2, 3], ['c', 'd', 'e']] => [1, 'c', 2, 'd', 3, 'e'],
if arrays are not of equal length, pad it to the longest innerArray's length.
i.e
[1, 2, 3], [4, 5]) => [1, 4, 2, 5, 3, null]
I've satisfied this condition with the case of 2 arrays, however if the case is more than that. I struggle to form a strategy on dealing with more than 2.
[1, 2, 3], [4, 5, 6], [7, 8, 9] => [1, 4, 7, 2, 5, 8, 3, 6, 9]
function interleave(...masterArray) {
let rtnArray = [];
let longestArrayPosition = getLongestArray(masterArray);
let longestInnerArrayLength = masterArray[longestArrayPosition].length;
padAllArraysToSameLength(masterArray, longestInnerArrayLength); //pad uneven length arrays
masterArray[0].forEach((firstArrayNum, index) => {
const secondArrayNum = masterArray[1][index];
rtnArray.push(firstArrayNum);
rtnArray.push(secondArrayNum);
});
return rtnArray;
}
function getLongestArray(masterArray) {
return masterArray
.map(a=>a.length)
.indexOf(Math.max(...masterArray.map(a=>a.length)));
}
function padAllArraysToSameLength(masterArray, maxLength) {
return masterArray.forEach(arr => {
if (arr != maxLength) {
while(arr.length != maxLength) {
arr.push(null);
}
}
})
}
Use Array.from() to transpose the array of arrays (rows => columns and vice versa), and fill in the missing places with null. Flatten the tramsposed arrays of arrays with Array.flat():
const fn = arr => Array.from({
length: Math.max(...arr.map(o => o.length)), // find the maximum length
},
(_, i) => arr.map(r => r[i] ?? null) // create a new row from all items in same column or substitute with null
).flat() // flatten the results
const arr = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
const result = fn(arr)
console.log(result)
You can do this for any number of arrays with two nested forEach statements:
let arr1 = [[1,2,3],[4,5]]
let arr2 = [[1,2,3], [4,5,6], [7,8,9]]
let arr3 = [[1,2,3,4], [4,5,6], [7,8,9], [10,11,12]]
function interLeaveArrays(mainArr){
let maxLen = Math.max(...mainArr.map(arr => arr.length))
mainArr.forEach(arr => {
let lenDiff = maxLen - arr.length
for(let i=lenDiff; i>0; i--){
arr.push(null)
}
})
let newArr = []
mainArr.forEach((arr, idx1) => {
arr.forEach((el, idx2) => {
newArr[idx2 * mainArr.length + idx1] = el
})
})
return newArr
}
console.log(interLeaveArrays(arr1))
console.log(interLeaveArrays(arr2))
console.log(interLeaveArrays(arr3))

Combine arrays of identical length into array of objects

I have 10 arrays of data that look like this:
var arr = [1,2,3,4,5,6,7,8,9,10]
var arr2=['hello','hello1','hello2','hello3','hello4','hello5','hello6','hello7','hello8','hello9']
...8 More Arrays
Each array will have exactly the same number of elements every time. I wanted to know the best way to generate an array of objects that look like this that combines the various arrays:
overallarray = [{
arr1 = 1,
arr2 = 'hello'
...
},
{
arr1 = 2,
arr2 = 'hello1'
...
}]
I recognize that I can use a large number of for loops but am looking for a more optimized solution that someone might have.
This is where Array.map() will be your friend. You can iterate through any of the arrays (since they have the same number of elements) and then access each element by index to get the corresponding value for each array in your dataset, like so:
var arr = [0,1,2,3,4,5,6,7,8,9]
var arr2=['hello','hello1','hello2','hello3','hello4','hello5','hello6','hello7','hello8','hello9'];
var arr3=['foo','foo1','foo2','foo3','foo4','foo5','foo6','foo7','foo8','foo9'];
let mapped = arr.map((elem, index) => {
return {
arr1: arr[index],
arr2: arr2[index],
arr3: arr3[index]
}
});
console.log(mapped);
Edit: If you wanted to access them generically, you can add all of your arrays to one dictionary and iterate over the key/value pairs, like so:
var arr = [0,1,2,3,4,5,6,7,8,9]
var arr2=['hello','hello1','hello2','hello3','hello4','hello5','hello6','hello7','hello8','hello9'];
var arr3=['foo','foo1','foo2','foo3','foo4','foo5','foo6','foo7','foo8','foo9'];
// combine all arrays into single dataset
let data = {arr, arr2, arr3};
let mapped = arr.map((elem, index) => {
// iterate over the key/value pairs of the dataset, use the key to generate the
// result object key, use the value to grab the item at the current index of the
// corresponding array
return Object.entries(data).reduce((res, [key, value]) => {
res[key] = value[index];
return res;
}, {});
});
console.log(mapped);
Assuming arr1,arr2 are not desired names of resulting object properties, if you need something
that scales nicely for arbitrary number of data arrays
assigns arbitrary key names (not necessarily corresponding to array variable names, or, worse, property name(s) that can't be valid variable name are needed)
works muuuch faster than accepted solution ;)
You may do the following:
const arr1 = [1,2,3,4,5,6,7,8,9,10],
arr2=['hello','hello1','hello2','hello3','hello4','hello5','hello6','hello7','hello8','hello9'],
keyNames = ['id', 'greeting'],
group = (...arrays) => (keys) =>
arrays.reduce((res, arr, idx) =>
(arr.forEach((e,i) => res[i][keys[idx]] = e), res),
Array.from({length:arrays[0].length}, () => ({}))
)
console.log(group(arr1,arr2)(keyNames))
.as-console-wrapper {min-height:100%;}
Just iterate all arrays with 1 loop counter:
var dataArrayOne = [1, 2, 3, 4 ];
var dataArrayTwo = ["hello", "hello1", "hello2", "hello3" ];
...
var resultArray = [];
for (var i = 0; i < 4; i++)
{
var combined = {
arr1: dataArrayOne[I],
arr2: dataArrayTwo[i]
...
};
resultArray.push(combined);
}
You can get from this:
[ [1, 2, 3]
, [4, 5, 6]
, [7, 8, 9]
]
to this:
[ [1, 4, 7]
, [2, 5, 8]
, [3, 6, 9]
]
with this function:
const combine =
(...arrs) =>
[ arrs.map(xs => xs[0])
, ... ( arrs.every(xs => xs.length === 1)
? []
: combine(...arrs.map(xs => xs.slice(1)))
)
];
combine
( [1, 2, 3]
, [4, 5, 6]
, [7, 8, 9]
);
Then from this:
[ [1, 4, 7]
, [2, 5, 8]
, [3, 6, 9]
]
to this:
[ {arr1: 1, arr2: 4, arr3: 7}
, {arr1: 2, arr2: 5, arr3: 8}
, {arr1: 3, arr2: 6, arr3: 9}
]
with this function:
const to_obj =
(...arrs) =>
arrs.map(arr =>
Object.fromEntries(
arr.map((x, i) => [`arr${i+1}`, x])));
to_obj
( [1, 4, 7]
, [2, 5, 8]
, [3, 6, 9]
)
Hopefully connecting the two functions together is straightforward.
A note about performance
With exactly 10 arrays of 10 elements each, it is unlikely that you can tell whether a particular solution performs better than another. You should probably go for the solution that feels right in terms of readability or maintenance.
By these criteria you should probably exclude mine; just wanted to share a different approach.

Is there any shorthand method to convert array of string array with header as first array to Objects of array?

Is there any shorthand method to convert array of string array with header as first array (Input as shown below) to Objects of array (as expected output shown below)
Using for loop we can achieve this, I am looking for any short hand and optimized solution to do this.
Let me know if is there any easy and optimized method to implement this.
Input
[
['fromAge', 'toAge', 'gender', 'institutionalRaf'],
[0, 10, 'F', '1.5'],
[11, 20, 'F', '2.5']
]
Expected Output :
[{
fromAge : 0,
toAge: 10,
gender: "F",
institutionalRaf : "1.5"
},
{
fromAge : 11,
toAge: 20,
gender: "F",
institutionalRaf : "2.5"
}
...
]
You can use map and reudce
Take the first element as header and rest of element as values
Loop through the values array for each element build a object with key from header and value from element
let data = [["fromAge","toAge","gender","institutionalRaf"],["1",'4','m','12'],["4",'12','f','22'],["10",'20','m','109']]
let [header,...values] = data
let final = values.map(v=> {
return v.reduce((op,inp,index)=>{
op[header[index]] = inp
return op
},{})
})
console.log(final)
You could separate the keys and the values and map the value as object with the keys.
var array = [['fromAge', 'toAge', 'gender', 'institutionalRaf'], [0, 10, 'F', '1.5'], [11, 20, 'F', '2.5']],
[keys, ...values] = array,
result = values.map(a => Object.assign(...keys.map((k, i) => ({ [k]: a[i] }))));
console.log(result);
I'd shift out the first array of keys, then .map to create entries and create the objects using Object.fromEntries:
const arr = [
['a', 'b', 'c'],
[1, 2, 3],
[4, 5, 6]
];
const keys = arr.shift();
const output = arr.map(values =>
Object.fromEntries(
values.map((value, i) => [keys[i], value])
)
);
console.log(output);
Object.fromEntries is a relatively new method. On older environments, either use a polyfill, or create the object with reduce instead:
const arr = [
['a', 'b', 'c'],
[1, 2, 3],
[4, 5, 6]
];
const keys = arr.shift();
const output = arr.map(values => (
values.reduce((a, value, i) => {
a[keys[i]] = value;
return a;
}, {})
));
console.log(output);
If keys are fixed we can use the simple approach like below
let arr=[
['fromAge', 'toAge', 'gender', 'institutionalRaf'],
[0, 10, 'F', '1.5'],
[11, 20, 'F', '2.5']
];
let arr1=arr.slice();
let x=arr1.shift();
let x1=arr1.map(a=>(
{
[x[0]]:a[0],
[x[1]]:a[1],
[x[2]]:a[2],
[x[3]]:a[3],
}
)
)
console.log(x1);
Use destructuring, map and reduce
const array = [
['fromAge', 'toAge', 'gender', 'institutionalRaf'],
[0, 10, 'F', '1.5'],
[11, 20, 'F', '2.5']
]
const [keys, ...values] = array
const result = values.map((value) => value.reduce((a, b, index) => ({...a, [keys[index]]: b}), {}), [])
console.log("result",result)

Iterate an array of arrays for a match from another array

I have an array of arrays, and I would like to iterate this array with the values of another array looking for a match.
let arr1 = [[1,3,5],[2,4,7],[1,5,9]] // [false, false, true]
let arr2 = [1,2,4,5,9] // arr2 contains all values of arr1[2]. return true.
I need it to return truthy falsey if all values in an arr1[i] are present in arr2
for (let i = 0; i < arr1.length; i++) {
if (arr2.every(arr1[i])) {
return true
}
}
You can use .map() with .every()
let arr1 = [[1,3,5],[2,4,7],[1,5,9]];
let arr2 = [1,2,4,5,9];
let result = arr1.map(x => x.every(y => arr2.includes(y)));
console.log(result);
or .filter() if you just want to get matching results:
let arr1 = [[1,3,5],[2,4,7],[1,5,9]];
let arr2 = [1,2,4,5,9];
let result = arr1.filter(x => x.every(y => arr2.includes(y)));
console.log(result);
You could use Array#some for a single boolean value by using Array#every for each inner array and check array2 with Array#includes.
var array1 = [[1, 3, 5], [2, 4, 7], [1, 5, 9]],
array2 = [1, 2, 4, 5, 9],
result = array1.some(a => a.every(v => array2.includes(v)));
console.log(result);
Using a Set.
var array1 = [[1, 3, 5], [2, 4, 7], [1, 5, 9]],
array2 = [1, 2, 4, 5, 9],
result = array1.some((s => a => a.every(v => s.has(v)))(new Set(array2)));
console.log(result);

Categories

Resources