Flatten different properties into single array - javascript

I have a javascript object literal as follows.
data: {
prop1ByZones:[{zone: "Zone1", x: 1}, {zone: "Zone2", x: 5}],
prop2ByZones:[{zone: "Zone1", y: "1302.5"}],
prop3ByZones:[{zone: "Zone2", z: 2}]
}
the output should be like -
output: [{zone: "Zone1", x: 1, y: "1302.5", z: 0}, {zone: "Zone2", x: 5, y: 0, z: 2}]
I can do it in trivial way like first add prop1ByZones to output and then loop through prop2ByZones and prop3ByZones and check for existing zone. if the zone is there then update it else add it.
I just wanted to check if there is any elegant way of doing it. Please let me know.

One possible approach:
var data = { prop1ByZones:[{zone: "Zone1", x: 1}, {zone: "Zone2", x: 5}], prop2ByZones:[{zone: "Zone1", y: "1302.5"}], prop3ByZones: [{zone: "Zone2", z: 2}] }
var arr = [...data.prop1ByZones, ...data.prop2ByZones, ...data.prop3ByZones]
var resp = arr.reduce((acc, { zone, x, y, z }) => {
var prev = acc.find(x => zone == x.zone);
if(prev) {
prev.x = x ? x : prev.x,
prev.y = y ? y : prev.y,
prev.z = z ? z : prev.z
return acc;
}
return acc.concat({zone: zone, x: x ? x : 0, y: y ? y : 0, z: z ? z : 0});
}, []);
console.log(resp)
.as-console-wrapper { max-height: 100% !important; top: 0; }

Here's a way to do this (using lodash for iterating), you could optimize the conditionals a bit to be ternaries etc, but this would give you your output:
const data = {
prop1ByZones:[{zone: "Zone1", x: 1}, {zone: "Zone2", x: 5}],
prop2ByZones:[{zone: "Zone1", y: "1302.5"}],
prop3ByZones:[{zone: "Zone2", z: 2}]
};
let list = {};
_.each(data, (d, i) => {
_.each(d, (e) => {
const zone = e.zone;
if (!list[zone]) {
list[zone] = zone;
list[zone] = {};
}
if (!list[zone].x) {
list[zone].x = e.x || 0;
}
if (!list[zone].y) {
list[zone].y = e.y || 0;
}
if (!list[zone].z) {
list[zone].z = e.z || 0;
}
});
});
// put everything in an array
let result = [];
_.each(list, (obj, k) => {
result.push({
zone: k,
x: obj.x,
y: obj.y,
z: obj.z
});
});
console.log(result);

You can get the Object.values(), flatten to a single array by spreading into Array.concat(), and then reduce the array to a Map, and spread the Map.values() iterator back to array:
const data = {
prop1ByZones:[{zone: "Zone1", x: 1}, {zone: "Zone2", x: 5}],
prop2ByZones:[{zone: "Zone1", y: "1302.5"}],
prop3ByZones:[{zone: "Zone2", z: 2}]
}
const result = [... // spread the map values iterator (see end) back to an array
[].concat(...Object.values(data)) // get the object's values and flatten to a single array
.reduce( // reduce the array to a Map
// add the zone key to the map, and include the previous and current item
(r, o) => r.set(o.zone, { ...(r.get(o.zone) || {}), ...o }),
new Map()
).values()] // get the map values iterator
console.log(result)

Related

How can I create a dynamic array based on another array in javascript/typescript?

I want to create an array to loop over one of the parameters of the first array (in this example, the desired parameter is the DT) and check whether we have data for different applications on those dates. If we have it, it will put its value (in the second array) and if we don't have it, it will put 0.
What I did was also with const pluck = (arr, key) => arr.map(i => i[key]);, I obtained the desired field dates (but they had duplicate values). To remove duplicate values I used dates = [...new Set(dates)]; and finally looped over the final values and wrote a series of codes, but I didn't get what I wanted (Expected Array in below).
first_array = [
{
DT: "2022-01-01",
APP: "Application 1",
SPEED: 1547,
},
{
DT: "2022-01-01",
APP: "Application 2",
SPEED: 685,
},
{
DT: "2022-01-02",
APP: "Application 1",
SPEED: 500,
},
{
DT: "2022-01-02",
APP: "Application 2",
SPEED: 300,
},
{
DT: "2022-01-02",
APP: "Application 3",
SPEED: 600,
},
{
DT: "2022-01-03",
APP: "Application 1",
SPEED: 1000,
},
]
Expected Array:
desire_array = [
{
Name: "Application1",
Values: [1547, 500, 1000],
ValuesWithDate: [{x: '2022-01-01', y: 1547}, {x: '2022-01-02', y: 500}, {x: '2022-01-03', y: 1000}],
},
{
Name: "Application2",
Values: [685, 300, 0],
ValuesWithDate: [{x: '2022-01-01', y: 685}, {x: '2022-01-02', y: 300}, {x: '2022-01-03', y: 0}],
},
{
Name: "Application3",
Values: [0, 600, 0],
ValuesWithDate: [{x: '2022-01-01', y: 0}, {x: '2022-01-02', y: 600}, {x: '2022-01-03', y: 0}],
},
]
The reason I need to do this is to create a series that I can use to display the chart with ApexCharts.
Real data can also be displayed from this api as JSON.
You can do:
const first = [{DT: '2022-01-01',APP: 'Application 1',SPEED: 1547,},{DT: '2022-01-01',APP: 'Application 2',SPEED: 685,},{DT: '2022-01-02',APP: 'Application 1',SPEED: 500,},{DT: '2022-01-02',APP: 'Application 2',SPEED: 300,},{DT: '2022-01-02',APP: 'Application 3',SPEED: 600,},{DT: '2022-01-03',APP: 'Application 1',SPEED: 1000,}]
const dates = [...new Set(first.map(({ DT }) => DT))]
const apps = [...new Set(first.map(({ APP }) => APP))]
const result = apps.reduce((acc, app) => {
const appData = Object.assign(
{},
{
Name: app.replace(/ /, ''),
Values: [],
ValuesWithDate: [],
}
)
dates.forEach((date) => {
const data = first.find(({ DT, APP }) => DT === date && APP === app)
appData.ValuesWithDate.push({ x: date, y: data ? data.SPEED : 0 })
appData.Values.push(data ? data.SPEED : 0)
})
acc.push(appData)
return acc
}, [])
console.log(result)
You can try with something like this :
const convertArray = (arr) => arr.reduce((prev, current) => {
const existingIndex = prev.findIndex((p) => p.Name === current.APP);
if (existingIndex > -1) {
const currentApp = prev[existingIndex];
currentApp.Values.push(current.SPEED);
currentApp.ValuesWithDate.push({x: current.DT, y: current.SPEED});
prev[existingIndex] = currentApp;
} else {
prev.push({Name: current.APP, Values: [current.SPEED], ValuesWithDate:[{x: current.DT, y: current.SPEED}]})
}
return prev;
}, []);
And use it like this :
const desire_array = convertArray(first_array)
const convert = (dates, data) => {
return Object.values(data.reduce((acc, curr) => {
if (!acc[curr.APP]) {
acc[curr.APP] = {
name: curr.APP,
valuesWithDate: []
};
}
acc[curr.APP].valuesWithDate.push({
x: curr.DT,
y: curr.SPEED
});
return acc;
}, {})).map((dataWithoutGaps) => {
const valuesWithDate = [...new Set(dates)].map(date => {
const el = dataWithoutGaps.valuesWithDate.find(e => e.x === date);
return {
x: date,
y: el ? el.y : 0
};
});
return {
ValuesWithDate: valuesWithDate,
Values: valuesWithDate.map(e => e.y),
Name: dataWithoutGaps.name
}
});
};
console.log(convert(first_array.map(e => e.DT), first_array));
Expected:
[{"ValuesWithDate":[{"x":"2022-01-01","y":1547},{"x":"2022-01-02","y":500},{"x":"2022-01-03","y":1000}],"Values":[1547,500,1000],"Name":"Application 1"},{"ValuesWithDate":[{"x":"2022-01-01","y":685},{"x":"2022-01-02","y":300},{"x":"2022-01-03","y":0}],"Values":[685,300,0],"Name":"Application 2"},{"ValuesWithDate":[{"x":"2022-01-01","y":0},{"x":"2022-01-02","y":600},{"x":"2022-01-03","y":0}],"Values":[0,600,0],"Name":"Application 3"}]
Your expected result can be achieved by this code.
let filtered_app = new Set();
const obj = [];
first_array.forEach(item=>{
filtered_app.add(item.APP);
});
filtered_app.forEach(app =>{
first_array.forEach(item =>{
if(item.APP == app){
const exists = obj.findIndex(elem => elem.Name == app);
if(exists != '-1'){
obj[exists].Values.push(item.SPEED);
obj[exists].ValuesWithDate.push({x: item.DT, y: item.SPEED});
}
else{
obj.push({Name: app, Values: [item.SPEED], ValuesWithDate: [{x: item.DT, y: item.SPEED}]});
}
}
});
});
console.log(obj);
Hope it helps.

Targeting two instances of key-value pair in specific indices of nested array

Say I have a nested array something like
var allPairs = [
[
{ x: 1, y: 0 },
{ x: 1, y: 1 },
],
[
{ x: 1, y: 1 },
{ x: 1, y: 2 },
],
[
{ x: 1, y: 2 },
{ x: 2, y: 2 },
],
]
and I have an object
var currentPair = {x: 1, y: 1};
I've been struggling with trying to find a way to check if, in allPairs, currentPair equals the second index [1] of one nested array, while also equaling the first index [0] of another nested array.
I've been playing around with some:
if (arrOfPointsUsed.some(e => e[1] === firstPoint)) {
pointsFound++;
}
if (arrOfPointsUsed.some(e => e[0] === firstPoint)) {
pointsFound++;
}
if (pointsFound > 1) {
return false;
}
I've tried nested for loops also.
What is the best approach?
I think you're on the right track with some, but I think you'll need two calls to it:
const foundBoth =
// Found 0?
allPairs.some(([{x, y}]) => x === currentPair.x && y == currentPair.y)
&&
// Found 1?
allPairs.some(([, {x, y}]) => x === currentPair.x && y == currentPair.y)
;
Live Example:
var allPairs = [ [{x: 1, y: 0}, {x: 1, y: 1}], [{x: 1, y: 1}, {x: 1, y: 2}], [{x: 1, y: 2}, {x: 2, y: 2} ] ];
var currentPair = {x: 1, y: 1};
const foundBoth =
// Found 0?
allPairs.some(([{x, y}]) => x === currentPair.x && y == currentPair.y)
&&
// Found 1?
allPairs.some(([, {x, y}]) => x === currentPair.x && y == currentPair.y)
;
console.log(foundBoth);
Or to do it in one pass, you could use a loop:
let foundBoth = false;
let found0 = false;
let found1 = false;
for (const [e0, e1] of allPairs) {
found0 = found0 || e0.x === currentPair.x && e0.y === currentPair.y;
found1 = found1 || e1.x === currentPair.x && e1.y === currentPair.y;
foundBoth = found0 && found1;
if (foundBoth) {
break;
}
}
Live Example:
var allPairs = [ [{x: 1, y: 0}, {x: 1, y: 1}], [{x: 1, y: 1}, {x: 1, y: 2}], [{x: 1, y: 2}, {x: 2, y: 2} ] ];
var currentPair = {x: 1, y: 1};
let foundBoth = false;
let found0 = false;
let found1 = false;
for (const [e0, e1] of allPairs) {
found0 = found0 || e0.x === currentPair.x && e0.y === currentPair.y;
found1 = found1 || e1.x === currentPair.x && e1.y === currentPair.y;
foundBoth = found0 && found1;
if (foundBoth) {
break;
}
}
console.log(foundBoth);
...but allPairs would have to be massive for making one pass vs. two to make a difference.
Some notes on the above:
I've assumed that the currentPair object and the matching objects in allPairs are merely equivalent, not the same object. That's why I'm checking x and y, not === on the object themselves. (Because two separate objects are never === one another.)
I'm using destructuring in various places, both iterable destructuring to pick out the 0th and 1st entries from each of the subarrays and object destructuring to pick out the x and y properties of those objects. In the first example (using some), [{x, y}] in the first some call's callback function's parameter list picks the x and y properties out of the first entry in the subarray, and [,{x, y}] does the same for the second (skipping the first entry with the , at the beginning).
If it were even one tiny bit more complex to compare the objects than just the x and y checks above, I'd factor that comparison operation out into its own function and reuse it.
In a comment you've asked:
Thanks for your thorough answer. Just trying to understand your syntax in the first example: how would I, for instance, find if the first index is equivalent to currentPair in more than one array within allPairs
For that I'd probably use find or findIndex twice: The first time to find the first index, the second time to find another one that isn't the first one. For instance:
const firstIndex = allpairs.findIndex(([{x, y}]) => x === currentPair.x && y == currentPair.y);
const secondIndex = firstIndex === -1 ? -1 : allpairs.findIndex(([{x, y}], index) => index > firstIndex && x === currentPair.x && y == currentPair.y);
if (secondIndex !== -1) {
// Found in the first entry of two different subarrays
}
You need to check the values instead of the object reference.
For the check, you need the entries of the target object and iterate this as well.
const
allPairs = [[{ x: 1, y: 0 }, { x: 1, y: 1 }], [{ x: 1, y: 1 }, { x: 1, y: 2 }], [{ x: 1, y: 2 }, { x: 2, y: 2 }]],
currentPair = { x: 1, y: 1 },
entries = Object.entries(currentPair),
result = allPairs.some(array =>
array.some(object =>
entries.every(([key, value]) => object[key] === value)
)
);
console.log(result);

Group and sum of an array of objects based on number key

I am trying to create a statistical pie chart. As a http response i am getting a list from server using which i need to draw a pie chart.
For example: Data received:
[{1: 9, 2: 7}, {3:8, 2: 1}, {1:8, 5:9}, {2:3, 3:1}]
This is the desired output:
[{x: 1, y: 17}, {x: 2, y:10}, {x: 3, y: 9}, {x: 5, y: 9}]
Please note: x is the key and y is sum of similar key values
I have tried data.forEach((item, index) => {}). After writing this, I am actually getting no lead about how I can combine Object.keys(item), Object.values(item) and Object.values(item).reduce((a,b)=> return a+b;)
This may sound silly question, but any help would be appreciated. :)
You could reduce the array. Create an accumulator object with each number as key and and object with x and y keys as it's value. Loop through each object and update the y value based on the number. Then use Object.values() on the object returned to get the values of the accumulator as an array
const input = [{1: 9, 2: 7}, {3:8, 2: 1}, {1:8, 5:9}, {2:3, 3:1}]
const grouped = input.reduce((acc, obj) => {
for (const x in obj) {
acc[x] = acc[x] || { x , y: 0 }
acc[x].y += obj[x]
}
return acc;
}, {})
console.log(Object.values(grouped))
You could look for same key and update or insert a new object.
This approach does not change the order of the keys.
var data = [{ 1: 9, 2: 7 }, { 3: 8, 2: 1 }, { 1: 8, 5: 9 }, { 2: 3, 3: 1 }] ,
result = data.reduce((r, o) => {
Object.entries(o).forEach(([x, y]) => {
var temp = r.find(o => o.x === x);
if (temp) temp.y += y;
else r.push({ x, y });
});
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Return a random Object containing a specific parameter from an object array

I'm currently storing data as objects inside a array in the following way:
let data = [];
module.exports.init = function() {
database.pool.query("SELECT * FROM data", (error, rows) => {
if (error) {
logUtil.log.error(`Loading failed: ${ error.message }`);
}
else {
rows.forEach((row) => data.push({dimension: row.dimension, x: row.x, y: row.y, z: row.z}));
logUtil.log.info(data);
}
});
};
data will hold the following now: [{ dimension: 2, x: -973.097, y: -133.411, z: 38.2531 }, { dimension: 3, x: -116.746, y: -48.414, z: 17.226 }, { dimension: 2, x: -946.746, y: -128.411, z: 37.786 }, { dimension: 2, x: -814.093, y: -106.724, z: 37.589 }]
Now I'm trying to receive a random object from this array storing a specific dimension parameter.
For example I want to return a random object storing the dimension: 2
I've tried to filter the array using something like:
let result = jsObjects.filter(data => {
return data.dimension === 2
})
then return a random object from the result.
Question: How could I receive this random object in the best way?
You can do it in two steps.
Get all record which satisfy criteria like dimension === 2
let resultArr = jsObjects.filter(data => {
return data.dimension === 2
})
Get random object from result.
var randomElement = resultArr[Math.floor(Math.random() * resultArr.length)];
var arr = [{ dimension: 2, x: -973.097, y: -133.411, z: 38.2531 }, { dimension: 3, x: -116.746, y: -48.414, z: 17.226 }, { dimension: 2, x: -946.746, y: -128.411, z: 37.786 }, { dimension: 2, x: -814.093, y: -106.724, z: 37.589 }]
//Filter out with specific criteria
let resultArr = arr.filter(data => {
return data.dimension === 2
})
//Get random element
var randomElement = resultArr[Math.floor(Math.random() * resultArr.length)];
console.log(randomElement)
You could use Math.random() and in the range of 0 to length of array.
let result = jsObjects.filter(data => {
return data.dimension === 2
})
let randomObj = result[Math.floor(Math.random() * result.length)]

Object rest/spread only excising properties

I was wondering if it with rest/spread is possible to only override the existing properties on an object:
let xy = {
x: 1,
y: 2,
}
let xyz = {
x: 41,
y: 23,
z: 1
}
Now i have two objects and i wish to override the existing properties on xy without getting the z property from xyz as well, so my output is the following:
xy = {
x: 41,
y: 23,
}
Is this possible?
Thanks in advance!
Rest/spread will always stuff into a destination what it finds in a source object, if you don't want to use a loop / want a functional approach, go for reduce, e.g.
const xyNew = Object.keys(xyz).reduce((res, key) =>
// if the key is contained in the accumulator, rewrite it with xyz value, else just return the accumulator (res ~ "result")
res[key] ? { ...res, [key]: xyz[key] } : res
, xy);
Here is a simple implementation :-)
let xy = {
x: 1,
y: 2,
}
let xyz = {
x: 41,
y: 23,
z: 1
}
function compute(){
let key = Object.keys(xy);
for(let i=0;i<key.length;i++){
if(key[i] in xyz) xy[key[i]] = xyz[key[i]];
}
}
compute();
console.log(xy);

Categories