Related
How to filter data from array of array?.
pls find the explanation in below given example.
We must use startnumber and endnumber in data query.
const data = [
{
"name": "x",
"points": [
[100, 50, 1], //[number, value, bit]
[150, 51, 0],
[170, 52, 1],
[200, 53, 0]
]
},
{
"name": "y",
"points": [
[60, 50, 1],
[100, 5, 1],
[150, 6, 0],
[170, 7, 1],
[200, 53, 1]
]
},
{
"name": "z",
"points": [
[300, 50, 1],
[350, 51, 0],
[370, 52, 1],
[400, 53, 1]
]
}
]
// want to find the records with name equal to x & y and number between 100 to 170
const names = ["x", "y"];
const startnumber = 100;
const endnumber = 170;
const finalResult= [];
for(const n of names){
console.log('name', n);
console.log('startnumber', startnumber)
console.log('endnuumber', endnumber)
const result = data.find(x=>x.name === n)
// how to use startnumber and endnumber here in above line/query ? OR some other elegant solution is required
if(result){
finalResult.push('result', result);
}
}
if(finalResult.length){
console.log(finalResult);
}
Expected result should be
[
{
"name": "x",
"points": [
[100, 50, 1],
[150, 51, 0],
[170, 52, 1],
]
},
{
"name": "y",
"points": [
[100, 5, 1],
[150, 6, 0],
[170, 7, 1],
]
}
]
const results = data.filter(elem => !!names.find(name => name == elem.name))
Explanation:
data.filter applies the callback to each element of the array data
names.find applies the callback to each element of the array names in order to find a match.
!! is used because find returns an element, so !! causes a double negation that turns it into a boolean, the expected return from the find callback (its actually not strictly necessary, as find returning undefined would be coerced to false), but it clarifies the intention.
The result is only the elements of data that have names properties that match the values in names.
const data = [
{
"name": "x",
"points": [
[100, 50, 1], //[number, value, bit]
[150, 51, 0],
[170, 52, 1],
[200, 53, 0]
]
},
{
"name": "y",
"points": [
[60, 50, 1],
[100, 5, 1],
[150, 6, 0],
[170, 7, 1],
[200, 53, 1]
]
},
{
"name": "y",
"points": [
[60, 50, 1],
[200, 53, 1]
]
},
{
"name": "z",
"points": [
[300, 50, 1],
[350, 51, 0],
[370, 52, 1],
[400, 53, 1]
]
}
]
// want to find the records with name equal to x & y and number between 100 to 170
const names = ["x", "y"];
const startNumber = 100;
const endNumber = 170;
const results = data
.filter(elem => !!names.find(name => name == elem.name))
.map(elem => {
return {
name: elem.name,
points: elem.points.filter(point => point[0] >= startNumber && point[0] <= endNumber)
}
})
.filter(elem => elem.points.length > 0)
console.log(results)
The most elegant solution:
const res = data.reduce((acc, curr) => {
const { name, points } = curr
const filteredPoints = points.filter(p => p[0] >= startNumber && p[0] <= endNumber)
return names.includes(name) && filteredPoints.length > 0 ? acc.concat({ name, points: filteredPoints }) : acc
}, [])
The task and the two given data structures imply a two folded filter process for each data item.
Identifying the data item by its name (from the additionally provided names array).
Filtering each data item's points array by whether a points item's first array item (the number value) is within a certain number range (which is defined by the additionally provided startnumber and endnumber values).
Thus a viable approach was to reduce the provided data array by a reducer function which for each data item takes care of the above two tasks.
The reducer function's initial value will be kind of a config and/or collector object which features ...
a name lookup (applied as a Map instance) for the to be explicitly processed data items.
the startnumber and endnumber range values as lowerNumber respectively upperNumber.
and a result array where only those data items get pushed into which fulfill all requirements.
function collectItemsByNameWhichHavePointsInNumberRange(collector, item) {
const { nameLookup, lowerNumber, upperNumber, result } = collector;
let { name, points } = item;
// ... collect items by name ...
if (nameLookup.has(name)) {
points = points
.filter(([number]) =>
(number >= lowerNumber) && (number <= upperNumber)
);
// ... which (actually do) have points (with)in (a) number range.
// (according to the above `points` filter result)
if (points.length >= 1) {
result
.push({
...item,
points,
});
}
}
return collector;
}
const data = [{
name: "x",
points: [
//[number, value, bit]
[100, 50, 1],
[150, 51, 0],
[170, 52, 1],
[200, 53, 0],
],
}, {
name: "y",
points: [
[60, 50, 1],
[100, 5, 1],
[150, 6, 0],
[170, 7, 1],
[200, 53, 1],
],
}, {
name: "z",
points: [
[300, 50, 1],
[350, 51, 0],
[370, 52, 1],
[400, 53, 1],
],
}];
const names = ["x", "y"];
const startnumber = 100;
const endnumber = 170;
const { result } = data
.reduce(collectItemsByNameWhichHavePointsInNumberRange, {
nameLookup: new Map(names.map(value => [value, value])),
lowerNumber: startnumber,
upperNumber: endnumber,
result: [],
});
console.log({ result });
.as-console-wrapper { min-height: 100%!important; top: 0; }
I've got some data:
const data = [
[
[1609459200000, 18],
[1612137600000, 12],
[1614556800000, 12],
[1617231600000, 14]
],
[
[1609459200000, 30],
[1612137600000, 501],
[1614556800000, 81],
[1617231600000, 82],
]
]
The long numbers are timestamps and I want to return an array as below:
const result = [{
x: 1609459200000,
y: 48
}, {
x: 1612137600000,
y: 513
}, {
x: 1614556800000,
y: 93
}, {
x: 1617231600000,
y: 96
}]
The X will contain the timestamp and the Y will be the sum of the values at the index 1 of each inner array, corresponding to each timestamp.
In case one of the array's element has more values than the other, it should sum it anyway as below:
const data = [
[
[1609459200000, 18],
[1612137600000, 12],
[1614556800000, 12],
[1617231600000, 14]
],
[
[1609459200000, 30],
[1612137600000, 501],
[1614556800000, 81],
]
]
const result = [{
x: 1609459200000,
y: 48
}, {
x: 1612137600000,
y: 513
}, {
x: 1614556800000,
y: 93
}, {
x: 1617231600000,
y: 14
}]
My attempts were completely worthless and couldn't pass the part where the array is multidimensional
FAILED
const totals = data.reduce((total, curr, i) => {
total = {
x: curr[0][i],
y: curr[1][i]
}
}, {})
console.log('total', totals)
EDIT
The data array can have more than 2 sub arrays.
You can flatten the array and then perform a reduce operation over it, using an object to store the sum for each timestamp.
const data = [
[
[1609459200000, 18],
[1612137600000, 12],
[1614556800000, 12],
[1617231600000, 14]
],
[
[1609459200000, 30],
[1612137600000, 501],
[1614556800000, 81],
[1617231600000, 82],
]
];
const res = Object.values(data.flat().reduce((acc, [x,y])=>{
(acc[x] ??= {x, y: 0}).y += y;
return acc;
}, {}));
console.log(res);
const data = [
[
[1609459200000, 18],
[1612137600000, 12],
[1614556800000, 12],
[1617231600000, 14]
],
[
[1609459200000, 30],
[1612137600000, 501],
[1614556800000, 81]
]
]
const arr = [...data.flat()
.reduce((a, [x, y]) => {
a.has(x) ? (a.get(x).y += y) : a.set(x, { y })
return a
}, new Map())].map(([x, { y }]) => ({ x, y }))
console.log(
arr
)
You can achieve this in 3 steps:
First you need to flat your array (even double flat in your case),
Map elements into desired objects,
Group by timestamp (x).
Step 2 and 3 might be swapped.
Example code:
const data = [[[16094592e5,18],[16121376e5,12],[16145568e5,12],[16172316e5,14]],[[16094592e5,30],[16121376e5,501],[16145568e5,81],[16172316e5,82]]];
const res = data.flatMap(e => e.flatMap(([x, y]) => ({x, y})))
.reduce((acc, e) => {
const found = acc.find(x => x.x === e.x)
found ? found.y += e.y : acc.push(e)
return acc
}, [])
console.log(res)
.as-console-wrapper { max-height: 100% !important; top: 0; } /* ignore this */
I want to get the array of objects created from two simple arrays:
const array1 = [20, 2, 35, 86]
const array2 = [8, 86, 15, 23, 35, 44]
The expected result:
const result = [
{ id: 20, value: false },
{ id: 2, value: false },
{ id: 35, value: true },
{ id: 86, value: true },
];
The array1 length is the one that matters. So I need to find matched values in both arrays as showed in the expected result.
Thank you very much for your help.
You can combine map with includes:
array1.map(i => ({id: i, value: array2.includes(i)}))
Should be simple. Loop through the first array using Array.map & return an object.
const array1 = [20, 2, 35, 86]
const array2 = [8, 86, 15, 23, 35, 44]
const result = array1.map(i => ({ id: i, value: array2.includes(i) }))
console.log(result)
Create a set from the second array:
const a2set = new Set(array2);
then map your first array:
array1.map(v1 => ({id:v1, value: a2set.has(v1)}))
Start a loop against first array and check if that element exists in second array or not.
If element exists push it to array containing objects with flag true or else as false.
const array1 = [20, 2, 35, 86]
const array2 = [8, 86, 15, 23, 35, 44]
var objArray = []
array1.forEach(function(elem){
objArray.push({
id : elem,
value : array2.indexOf(elem) != -1 ? true : false
});
});
console.log(objArray);
You can use array indexOf to find if the item is inside the second array.
const array1 = [20, 2, 35, 86];
const array2 = [8, 86, 15, 23, 35, 44];
let output = [];
array1.forEach((number) => {
output.push({
id: number,
value: array2.indexOf(number) !== -1
});
});
console.log(output);
Try a simple for loop:
const array1 = [20, 2, 35, 86];
const array2 = [8, 86, 15, 23, 35, 44];
var res = [];
for (var i = 0; i < array1.length; i++) {
if (array2.includes(array1[i])) {
res.push({ id: array1[i], value: true });
} else {
res.push({ id: array1[i], value: false });
}
}
console.log(res);
Try the following. If performance is important, or if the arrays might include a large amount of elements, I'd consider using sets for better lookup performance.
const array1 = [20, 2, 35, 86]
const array2 = [8, 86, 15, 23, 35, 44]
const result = array1.map(element => {
return {
id: element,
value: array2.includes(element)
};
})
How can I sum vertically all data from an array of arrays?
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
var result = arrayOfArrays.reduce(function(array1, array2) {
return array1.data.map(function(value, index) {
return value + array2.data[index];
}, 0);
});
console.log(result)
The output should be the vertical sum of arrays.
[3,6,9,12,15,18,21,24]
The problem is that array1 return always as undefined.
You code is almost correct but with 1 issues.
You are looping on accumulator. This will be an array of number in second iteration. Instead loop over array2 or current item.
Idea of .reduce is to have same signature for all iteration. If you do not pass default value for accumulator, first iteration will be of type Array<{ label: string, data: Array<number>}> and second iteration will be just Array<number>. So you can skip behavior for first iteration by passing default value as []. Now the calculation will break as array[n] will be undefined. For this, you can use a default value of 0.
So your calculation will look like:
value + (array1[index] || 0)
Following is a sample:
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
var result = arrayOfArrays.reduce(function(array1, array2) {
return array2.data.map(function(value, index) {
return value + (array1[index] || 0);
}, 0);
}, []);
console.log(result)
Use the index/key of map and add to the previous value.
const arrayOfArrays = [{label:'First Value', data:[1,2,3,4,5,6,7,8]},{label:'Second Value', data:[1,2,3,4,5,6,7,8]},{label:'Third Value', data:[1,2,3,4,5,6,7,8]}];
const res = arrayOfArrays.reduce((acc, cur) => (cur.data.map((i, k) => {acc[k] = acc[k] ? acc[k] += i : i}), acc), [])
console.log(res)
you're using reduce in a wrong way, but heres a for loop that does the same job:
arrayOfArrays = [{
label:'First Value', data:[1,2,3,4,5,6,7,8]},{
label:'Second Value', data:[1,2,3,4,5,6,7,8]},{
label:'Third Value', data:[1,2,3,4,5,6,7,8]
}];
const newArr = [];
for(let x = 0; x < arrayOfArrays[0].length; x++){
newArr.push(arrayOfArrays[0].data[x]+arrayOfArrays[1].data[x]+arrayOfArrays[2].data[x])
}
console.log(newArr); // new array
You can flatten the array by looping the array of objects and pushing the data property to a new array, then use reduce/map on the flattened data:
arrayOfArrays = [
{label:'First Value', data:[1,2,3,4,5,6,7,8]},
{label:'Second Value', data:[1,2,3,4,5,6,7,8]},
{label:'Third Value', data:[1,2,3,4,5,6,7,8]}
];
var data = [];
arrayOfArrays.forEach((element)=> {
data.push(element.data)
})
var sum = (r, a) => r.map((b, i) => a[i] + b);
var result = data.reduce(sum);
console.log(result);
Which outputs:
[3, 6, 9, 12, 15, 18, 21, 24]
Working fiddle
If you know that the length of each array is same. you can do as follows
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
let out = arrayOfArrays.reduce((acc, {data}) => acc.map((e, i) => e+data[i]), new Array(8).fill(0));
console.log(out)
You are passing the wrong accumulator which should be an array also in wrong place, it must be with reduce not with map
var result = arrayOfArrays.reduce(function (array1, array2) {
return array1.map(function (value, index) {
return value + array2.data[index];
});
}, Array(8).fill(0));
I would do it like this:
Introduce a helper transport function:
const transport = (arr) => arr[0].map((col, i) => arr.map(row => row[i]));
Get a proper matrix:
const matrix = arrayOfArrays.map(el => el.data)
Then the task becomes trivial:
const res = transport(matrix).map(arr => arr.reduce((x, y) => x + y))
// > (8) [3, 6, 9, 12, 15, 18, 21, 24]
You could take advantage of function generators in case you need to later transform or alterate the values, or just iterate them without needing the entire result set.
In this solution, a function generator is used and the logic applied is:
Get the array with the longest length (assuming length might change)
Get all the elements at index i from 0 to longest length and yield their sum.
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
/**
Sums elements of arrays inside the array vertically.
*/
function* sumVertically(arr) {
// Get the longest array.
const longestArrayLength = arr.sort(({length: l1}, {length: l2}) => l1 - l2)[0].length;
// Acquire all elements at index [i] of each array and sum them. Yield the sum.
for (let i = 0; i < longestArrayLength; i++) yield arr.map(e => e[i]).reduce((a,b) => a + b, 0);
}
const result = [...sumVertically(arrayOfArrays.map(i => i.data))];
console.log(result);
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))