Related
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.
I have an array with some streaming channel data, according to the shift (Morning and Afternoon).
I spent the night trying some functions like reduce, but I couldn't understand how it works and stagnated.
var array = [{"label":"manha","data":[{"Amazon Prime":"0.00"},{"AppleTV":"0.00"},{"HBO Max":"25.00"},{"Nenhuma":"0.00"},{"Netflix":"55.00"},{"Outro":"20.00"}]},{"label":"manha","data":[{"Amazon Prime":"50.00"},{"AppleTV":"0.00"},{"HBO Max":"25.00"},{"Nenhuma":"0.00"},{"Netflix":"25.00"},{"Outro":"0.00"}]},{"label":"manha","data":[{"Amazon Prime":"0.00"},{"AppleTV":"0.00"},{"HBO Max":"0.00"},{"Nenhuma":"50.00"},{"Netflix":"50.00"},{"Outro":"0.00"}]},{"label":"tarde","data":[{"Amazon Prime":"10.00"},{"AppleTV":"11.00"},{"HBO Max":"0.00"},{"Nenhuma":"50.00"},{"Netflix":"9.00"},{"Outro":"20.00"}]},{"label":"tarde","data":[{"Amazon Prime":"0.00"},{"AppleTV":"0.00"},{"HBO Max":"5.00"},{"Nenhuma":"25.00"},{"Netflix":"20.00"},{"Outro":"50.00"}]}]
I expect to have this output:
The sum total of all streaming channels, divided by the amount of objects value per shift (morning and afternoon)
[{"label":"manha","data":[{"Amazon Prime":"16.66"},{"AppleTV":"0.00"},{"HBO Max":"16.66"},{"Nenhuma":"16.66"},{"Netflix":"43.33"},{"Outro":"6.66"}]},{"label":"tarde","data":[{"Amazon Prime":"5.00"},{"AppleTV":"5.50"},{"HBO Max":"2.50"},{"Nenhuma":"36.00"},{"Netflix":"14.50"},{"Outro":"35.00"}]}]
I would be very grateful if you could help me, and if possible, explain.
Let's solve this problem in steps.
The first attempt is using reduce to iterate through the result and build something.
For simplicity let's get it to build an object where the keys represents the labels.
The reason for this is we can use this key to merge similar keys together.
On the TODO we observe that:
The numerical values are strings not number and do not lend itself for arithmetic operations
The subarray is difficult to merge and could do with a conversion to object as well
let array = [{"label":"manha","data":[{"Amazon Prime":"0.00"},{"AppleTV":"0.00"},{"HBO Max":"25.00"},{"Nenhuma":"0.00"},{"Netflix":"55.00"},{"Outro":"20.00"}]},{"label":"manha","data":[{"Amazon Prime":"50.00"},{"AppleTV":"0.00"},{"HBO Max":"25.00"},{"Nenhuma":"0.00"},{"Netflix":"25.00"},{"Outro":"0.00"}]},{"label":"manha","data":[{"Amazon Prime":"0.00"},{"AppleTV":"0.00"},{"HBO Max":"0.00"},{"Nenhuma":"50.00"},{"Netflix":"50.00"},{"Outro":"0.00"}]},{"label":"tarde","data":[{"Amazon Prime":"10.00"},{"AppleTV":"11.00"},{"HBO Max":"0.00"},{"Nenhuma":"50.00"},{"Netflix":"9.00"},{"Outro":"20.00"}]},{"label":"tarde","data":[{"Amazon Prime":"0.00"},{"AppleTV":"0.00"},{"HBO Max":"5.00"},{"Nenhuma":"25.00"},{"Netflix":"20.00"},{"Outro":"50.00"}]}];
let result = array.reduce( function (p, c, i, a) {
let label = c.label;
if (!(label in p)) {
p[label] = c.data;
}
return p;
}, { } );
console.log(JSON.stringify(result, undefined, 2));
// Output:
// {
// "manha": [ { "Amazon Prime": "0.00" },
// { "AppleTV": "0.00" },
// { "HBO Max": "25.00" },
// { "Nenhuma": "0.00" },
// { "Netflix": "55.00" },
// { "Outro": "20.00" }
// ],
// "tarde": [ { "Amazon Prime": "10.00" },
// { "AppleTV": "11.00" },
// { "HBO Max": "0.00" },
// { "Nenhuma": "50.00" },
// { "Netflix": "9.00" },
// { "Outro": "20.00" }
// ]
// }
For the second attempt, we will:
convert more arrays to objects
as an object we can make use of object methods such as in to determine whether a key exists or not
we can use parseFloat to convert strings to numbers
we can start collating the information together
This produces a nice set of raw data.
let array = [{"label":"manha","data":[{"Amazon Prime":"0.00"},{"AppleTV":"0.00"},{"HBO Max":"25.00"},{"Nenhuma":"0.00"},{"Netflix":"55.00"},{"Outro":"20.00"}]},{"label":"manha","data":[{"Amazon Prime":"50.00"},{"AppleTV":"0.00"},{"HBO Max":"25.00"},{"Nenhuma":"0.00"},{"Netflix":"25.00"},{"Outro":"0.00"}]},{"label":"manha","data":[{"Amazon Prime":"0.00"},{"AppleTV":"0.00"},{"HBO Max":"0.00"},{"Nenhuma":"50.00"},{"Netflix":"50.00"},{"Outro":"0.00"}]},{"label":"tarde","data":[{"Amazon Prime":"10.00"},{"AppleTV":"11.00"},{"HBO Max":"0.00"},{"Nenhuma":"50.00"},{"Netflix":"9.00"},{"Outro":"20.00"}]},{"label":"tarde","data":[{"Amazon Prime":"0.00"},{"AppleTV":"0.00"},{"HBO Max":"5.00"},{"Nenhuma":"25.00"},{"Netflix":"20.00"},{"Outro":"50.00"}]}];
let result = array.reduce( function (p, c, i, a) {
let label = c.label;
if (!(label in p)) {
p[label] = { };
}
for (let i = 0; i < c.data.length; i++) {
let e = Object.entries(c.data[i]);
let k = e[0][0];
let v = parseFloat(e[0][1]);
if (!(k in p[label])) {
p[label][k] = [ ];
}
p[label][k].push(v);
}
return p;
}, { } );
console.log(JSON.stringify(result, undefined, 2));
// Output:
// {
// "manha": {
// "Amazon Prime": [ 0, 50, 0 ],
// "AppleTV": [ 0, 0, 0 ],
// "HBO Max": [ 25, 25, 0 ],
// "Nenhuma": [ 0, 0, 50 ],
// "Netflix": [ 55, 25, 50 ],
// "Outro": [ 20, 0, 0 ]
// },
// "tarde": {
// "Amazon Prime": [ 10, 0 ],
// "AppleTV": [ 11, 0 ],
// "HBO Max": [ 0, 5 ],
// "Nenhuma": [ 50, 25 ],
// "Netflix": [ 9, 20 ],
// "Outro": [ 20, 50 ]
// }
// }
For the final step, we just average out the remaining array and convert the numerical result back to a string.
let array = [{"label":"manha","data":[{"Amazon Prime":"0.00"},{"AppleTV":"0.00"},{"HBO Max":"25.00"},{"Nenhuma":"0.00"},{"Netflix":"55.00"},{"Outro":"20.00"}]},{"label":"manha","data":[{"Amazon Prime":"50.00"},{"AppleTV":"0.00"},{"HBO Max":"25.00"},{"Nenhuma":"0.00"},{"Netflix":"25.00"},{"Outro":"0.00"}]},{"label":"manha","data":[{"Amazon Prime":"0.00"},{"AppleTV":"0.00"},{"HBO Max":"0.00"},{"Nenhuma":"50.00"},{"Netflix":"50.00"},{"Outro":"0.00"}]},{"label":"tarde","data":[{"Amazon Prime":"10.00"},{"AppleTV":"11.00"},{"HBO Max":"0.00"},{"Nenhuma":"50.00"},{"Netflix":"9.00"},{"Outro":"20.00"}]},{"label":"tarde","data":[{"Amazon Prime":"0.00"},{"AppleTV":"0.00"},{"HBO Max":"5.00"},{"Nenhuma":"25.00"},{"Netflix":"20.00"},{"Outro":"50.00"}]}];
let tmp = array.reduce( function (p, c, i, a) {
let label = c.label;
if (!(label in p)) {
p[label] = { };
}
for (let i = 0; i < c.data.length; i++) {
let e = Object.entries(c.data[i]);
let k = e[0][0];
let v = parseFloat(e[0][1]);
if (!(k in p[label])) {
p[label][k] = [ ];
}
p[label][k].push(v);
}
return p;
}, { } );
let result = Object.entries(tmp).map( ( [label, _data] ) => (
{ label : label,
data: Object.entries(_data).map( function ( [k, v] ) {
let obj = { };
obj[k] = ( v.reduce( (p,c) => p + c ) / v.length ).toFixed(2);
return obj;
} )
}
) );
console.log(JSON.stringify(result, undefined, 2));
// Output:
// [
// {
// "label": "manha",
// "data": [
// { "Amazon Prime": "16.67" },
// { "AppleTV": "0.00" },
// { "HBO Max": "16.67" },
// { "Nenhuma": "16.67" },
// { "Netflix": "43.33" },
// { "Outro": "6.67" }
// ]
// },
// {
// "label": "tarde",
// "data": [
// { "Amazon Prime": "5.00" },
// { "AppleTV": "5.50" },
// { "HBO Max": "2.50" },
// { "Nenhuma": "37.50" },
// { "Netflix": "14.50" },
// { "Outro": "35.00" }
// ]
// }
// ]
Some final remarks on this answer compared to the question supplied.
I used toFixed(2) which implements a round up
Answers show "16.67" instead of "16.66" for the "manha" averages
For the "tarde" I got "37.50" ( i.e. (50 + 25) / 2 === 37.50 ) instead of "36.00"
All other values shown in the output matches to what is being requested in the question
This is probably a bit longer then what you have hoped for but it should work.
let newAr = []
for(el of array) {
const currentLabel = el.label
let indexOfLabel = -1
for(let i = 0; i < newAr.length; ++i) {
if(newAr[i].label === currentLabel) indexOfLabel = i
}
if(indexOfLabel >= 0) {
for(let i = 0; i < el.data.length; ++i) {
const key = Object.keys(el.data[i])
newAr[indexOfLabel].data[i][key] = parseInt(newAr[indexOfLabel].data[i][key])
newAr[indexOfLabel].data[i][key] += parseInt(el.data[i][key])
}
} else {
newAr.push(el)
}
}
const occurrences = array.reduce(function (acc, curr) {
return acc[curr.label] ? ++acc[curr.label] : acc[curr.label] = 1, acc
}, {});
for(el of newAr) {
for(data of el.data) {
data[Object.keys(data)] = (data[Object.keys(data)] / occurrences[el.label]).toFixed(2)
}
}
Another approach. I simply used hash grouping twice to create an intermediate object and create a final object at the end
const data = [{"label":"manha","data":[{"Amazon Prime":"0.00"},{"AppleTV":"0.00"},{"HBO Max":"25.00"},{"Nenhuma":"0.00"},{"Netflix":"55.00"},{"Outro":"20.00"}]},{"label":"manha","data":[{"Amazon Prime":"50.00"},{"AppleTV":"0.00"},{"HBO Max":"25.00"},{"Nenhuma":"0.00"},{"Netflix":"25.00"},{"Outro":"0.00"}]},{"label":"manha","data":[{"Amazon Prime":"0.00"},{"AppleTV":"0.00"},{"HBO Max":"0.00"},{"Nenhuma":"50.00"},{"Netflix":"50.00"},{"Outro":"0.00"}]},{"label":"tarde","data":[{"Amazon Prime":"10.00"},{"AppleTV":"11.00"},{"HBO Max":"0.00"},{"Nenhuma":"50.00"},{"Netflix":"9.00"},{"Outro":"20.00"}]},{"label":"tarde","data":[{"Amazon Prime":"0.00"},{"AppleTV":"0.00"},{"HBO Max":"5.00"},{"Nenhuma":"25.00"},{"Netflix":"20.00"},{"Outro":"50.00"}]}]
const groups = data.reduce((acc, { label, data }) => {
acc[label] ??= {};
data.forEach((item) => {
const [[channel, value]] = Object.entries(item);
acc[label][channel] ??= [];
acc[label][channel].push(Number(value));
});
return acc;
}, {});
// groups
//
// {"manha": {
// "Amazon Prime": [0, 50, 0"],
// "AppleTV": [0, 0, 0],
// "HBO Max": [25, 25, 0],
// "Nenhuma": [0, 0, 50],
// "Netflix": [55, 25,50],
// "Outro": [20, 0, 0]},
// ...}
const getFixedAvg = (data) => {
const average = data.reduce((avg, e) => avg + e) / data.length;
return (Math.floor(average * 100) / 100) // fix 16.67 to 16.66
.toFixed(2);
};
const makeData = (dataObj) => Object.entries(dataObj)
.map(([channel, dataValues]) => ({ [channel]: getFixedAvg(dataValues) }));
const result = Object.entries(groups)
.map(([label, dataObj]) => ({ label, data: makeData(dataObj) }));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0 }
I have an issue looping through an array using a bayesian classifier function.
Here is my array:
var data = ['good', {
dry: 1,
wet: 0,
moist:0
}, 'bad', {
dry: 0,
wet: 1,
moist: 1
}, 'neutral', {
dry: 1,
wet: 1,
moist:1
}, 'good', {
dry: 1,
wet: 0,
moist: 1
}];
Here's my classifier function:
class Bayes{
constructor(...categories) {
this.categories = {};
this.categoryCounts = {};
categories.forEach(category => {
this.categories[category] = {};
this.categoryCounts[category] = 0;
});
}
train(category, dataset) {
this.categoryCounts[category]++;
Object.keys(dataset).forEach(key => {
this.categories[category][key] = (this.categories[category][key] || '') + dataset[key];
});
};
classify(dataset) {
let scores = {};
let trainingCount = Object.values(this.categoryCounts).reduce((a, b) => a + b );
Object.keys(this.categories).forEach(category => {
scores[category] = 0;
let categoryWords = this.categories[category];
let total = Object.values(categoryWords).reduce((a, b) => a + b );
Object.keys(dataset).forEach(function (key) {
let value = dataset[key];
let s = categoryWords[key] || 0.1;
let i = 0;
while(i<value){
scores[category] += Math.log(s / parseFloat(total));
i++;
}
});
let s = this.categoryCounts[category] || 0.1;
scores[category] = (s / trainingCount);
});
return scores;
};
};
Normally, to classify the data; I'll do:
var b = new Bayes('good', 'bad', 'neutral');
b.train('good', { dry: 1, wet: 0, moist:0});
b.train('bad', {dry: 0,wet: 1,moist: 1});
b.train('neutral', {dry: 1,wet: 1,moist:1});
b.train('good', {dry: 1,wet: 0,moist: 1});
console.log(b.classify({ dry: 0, wet: 1, moist: 1}));
// good: 0.5, bad: 0.25, neutral: 0.25
But when I can't figure out how to train the data by iterating through data.
I need help to feed the array dynamically as a javascript object.
if you can guarantee the data structure consistency, such as
let data = [key, value, key, value, key, value......]
const data = ['good', { dry: 1, wet: 0, moist:0}, 'neutral', {dry: 1,wet: 1,moist:1}, 'good', {dry: 1,wet: 0,moist: 1}];
// 1: chunk it
const size = 2;
const chunks = [];
while (data.length) {
chunks.push(data.splice(0, size));
}
console.log(chunks);
// 2: loop through your train
let keys = chunks.map(val=>val[0])
let deDupeKeys = [...new Set(keys)]
console.log(keys)
console.log(deDupeKeys)
// var b = new Bayes(deDupeKeys)
chunks.forEach(chunk => {
console.log(chunk[0])
console.log(chunk[1])
// b.train(chunk[0],chunk[1]);
})
Assuming the data array will have the format: data = [category, dataset, category, dataset...], a simple solution would be to loop the data array as follows and train the classifier.
for (let i = 0; i < data.length; i = i + 2) {
console.log("category : ", data[i], "dataset : ", data[i + 1]);
b.train(data[i], data[i + 1]);
}
I am trying to group data by multiple properties and sum their values.
Here is what I tried as per this question
I had a follow up to this question:
const arr = [{"shape":"square","color":"red","used":1,"instances":1},{"shape":"square","color":"red","used":2,"instances":1},{"shape":"circle","color":"blue","used":0,"instances":0},{"shape":"square","color":"blue","used":4,"instances":4},{"shape":"circle","color":"red","used":1,"instances":1},{"shape":"circle","color":"red","used":1,"instances":0},{"shape":"square","color":"blue","used":4,"instances":5},{"shape":"square","color":"red","used":2,"instances":1}];
const result = [...arr.reduce((r, o) => {
const key = o.shape + '-' + o.color;
const item = r.get(key) || Object.assign({}, o, {
used: 0,
instances: 0
});
item.used += o.used;
item.instances += o.instances;
return r.set(key, item);
}, new Map).values()];
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
I wanted to make this more reusable with the numerical values. In this for example, I want the
const item = r.get(key) || Object.assign({}, o, {
used: 0,
instances: 0
});
item.used += o.used;
item.instances += o.instances;
part especially to be reusable.
I got the numerical value keys in an array: let gee = ['used', 'instances'];
I am not sure how to use it with Object.assign. I tried to do this:
const result = [...arr.reduce((r, o) => {
const key = o.shape + '-' + o.color;
// console.log(o);
const item = gee.forEach(v => o[v] += o[v]);
// const item = r.get(key) || Object.assign({}, o, {
// used: 0,
// instances: 0
// });
// item.used += o.used;
// item.instances += o.instances;
return r.set(key, item);
}, new Map).values()];
But this is not working. How can I use an array for this bit of code:
const item = r.get(key) || Object.assign({}, o, {
used: 0,
instances: 0
});
item.used += o.used;
item.instances += o.instances;
If the Map object has the key, loop through the totalKeys and increment the object in the accumulator with current object's data. If it is new key, add a copy of the object to the Map
if (r.has(key)) {
const item = r.get(key)
totalKeys.forEach(k => item[k] += o[k])
} else {
r.set(key, { ...o })
}
Here's a snippet:
const arr = [{"shape":"square","color":"red","used":1,"instances":1},{"shape":"square","color":"red","used":2,"instances":1},{"shape":"circle","color":"blue","used":0,"instances":0},{"shape":"square","color":"blue","used":4,"instances":4},{"shape":"circle","color":"red","used":1,"instances":1},{"shape":"circle","color":"red","used":1,"instances":0},{"shape":"square","color":"blue","used":4,"instances":5},{"shape":"square","color":"red","used":2,"instances":1}];
function groupSum(array, totalKeys) {
const group = arr.reduce((r, o) => {
const key = o.shape + '-' + o.color;
if (r.has(key)) {
const item = r.get(key)
totalKeys.forEach(k => item[k] += o[k])
} else {
r.set(key, { ...o })
}
return r;
}, new Map);
return Array.from(group.values())
}
console.log(
groupSum(arr, ['used', 'instances'])
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
You can make it even more dynamic by providing an array of keys to group by. Create the key using the values of the object separated by a |
const key = groupKeys.map(k => o[k]).join("|");
if (r.has(key)) {
const item = r.get(key)
totalKeys.forEach(k => item[k] += o[k])
} else {
r.set(key, { ...o })
}
Here's a snippet:
const arr = [{"shape":"square","color":"red","used":1,"instances":1},{"shape":"square","color":"red","used":2,"instances":1},{"shape":"circle","color":"blue","used":0,"instances":0},{"shape":"square","color":"blue","used":4,"instances":4},{"shape":"circle","color":"red","used":1,"instances":1},{"shape":"circle","color":"red","used":1,"instances":0},{"shape":"square","color":"blue","used":4,"instances":5},{"shape":"square","color":"red","used":2,"instances":1}];
function groupSum(array, groupKeys, totalKeys) {
const group = arr.reduce((r, o) => {
const key = groupKeys.map(k => o[k]).join("|");
if (r.has(key)) {
const item = r.get(key)
totalKeys.forEach(k => item[k] += o[k])
} else {
r.set(key, { ...o })
}
return r;
}, new Map);
return Array.from(group.values())
}
console.log(
groupSum(arr, ['shape', 'color'], ['used', 'instances'])
)
You could vastly simplify the dataset too by not using the combination of array.reduce() with a map()... and instead just build your new array by looping through all elements of the original array with array.forEach().
I added your use of the gee array as being a list of numeric fields you want to have added... to include making sure they exist on every object of the result array...whether or not they existed on each of the previous objects in arr.
const arr = [{
"shape": "square",
"color": "red",
"used": 1,
"instances": 1
}, {
"shape": "square",
"color": "red",
"used": 2,
"instances": 1
}, {
"shape": "circle",
"color": "blue",
"used": 0,
"instances": 0
}, {
"shape": "square",
"color": "blue",
"used": 4,
"instances": 4
}, {
"shape": "circle",
"color": "red",
"used": 1,
"instances": 1
}, {
"shape": "circle",
"color": "red",
"used": 1,
"instances": 0,
"testProp": 1
}, {
"shape": "square",
"color": "blue",
"used": 4,
"instances": 5
}, {
"shape": "square",
"color": "red",
"used": 2,
"instances": 1
}];
let gee = ['used', 'instances', 'testProp'];
let result = [];
arr.forEach((o) => {
// Setup TempSource since not all o may have all elements in gee
let tempSource = {};
gee.forEach((key) => {
if (o.hasOwnProperty(key)) {
tempSource[key] = o[key];
} else {
tempSource[key] = 0;
}
});
// Look to see if the result array already has an object with same shape/color
const matchingObject = result.find(element => {
let returnValue = true;
returnValue &= (element.shape == o.shape);
returnValue &= (element.color == o.color);
return returnValue;
});
if (matchingObject) {
// Matching Object already exists... so increment values
gee.forEach((key) => {
matchingObject[key] += tempSource[key];
});
} else {
// Matching Object missing, so merge newObject and insert
let newObj = {};
Object.assign(newObj, o, tempSource);
result.push(newObj);
}
});
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Maybe this would be a way to go:
const arr = [{"shape":"square","color":"red","used":1,"instances":1},{"shape":"square","color":"red","used":2,"instances":1},{"shape":"circle","color":"blue","used":0,"instances":0},{"shape":"square","color":"blue","used":4,"instances":4},{"shape":"circle","color":"red","used":1,"instances":1},{"shape":"circle","color":"red","used":1,"instances":0},{"shape":"square","color":"blue","used":4,"instances":5},{"shape":"square","color":"red","used":2,"instances":1}],
nums=["used","instances"]
function summationOn(ar,cnts){ // cnts: add up counts on these properties
const grp=Object.keys(ar[0]).filter(k=>cnts.indexOf(k)<0) // grp: group over these
return Object.values(ar.reduce((a,c,t)=>{
const k=grp.map(g=>c[g]).join("|");
if (a[k]) cnts.forEach(p=>a[k][p]+=c[p])
else a[k]={...c};
return a
},{}))
}
const res=summationOn(arr,nums);
console.log(res);
re-write
Similar to #adiga I now expect the "countable" properties to be given in the array cnts. With this array I collect all other properties of the first object of input array ar into array grp. These are the properties I will group over.
I need to transmit some data, that has too many key-value pairs.
As the keys are similar, I dont want to transmit them with each object.
Consider I have the following data:
[
{
x:11,
y:12
},{
x:21,
y:22
},{
x:31,
y:32
},{
x:41,
y:42
}
];
And I need the final output as
[ [x,y],[[11,12],[21,22],[31,32],[41,42]] ] OR
[ [x,y],[11,12],[21,22],[31,32],[41,42] ]
On the other end, I should be able to convert back to its original form.
It would be great if it can handle an additional key in some of the objects
I think I have seen lodash or underscore function for something close/similar to this, but I'm not able to find it right now.
NOTE: I don't know what the keys will be
Lodash v4.17.1
modify original
var modifiedOriginal = _.chain(original)
.map(_.keys)
.flatten()
.uniq()
.thru(function(header){
return _.concat(
[header],
_.map(original, function(item) {
return _.chain(item)
.defaults(_.zipObject(
header,
_.times(_.size(header), _.constant(undefined))
))
.pick(header)
.values()
.value()
})
);
})
.value();
modified back to original (keys order is not
guarantee)
var backToOriginal = _.map(_.tail(modified), function(item) {
return _.chain(_.head(modified))
.zipObject(item)
.transform(function(result, val, key) {
if (!_.isUndefined(val)) {
result[key] = val;
}
})
.value();
});
JSFiddle code https://jsfiddle.net/wa8kaL5g/1/
Using Array#reduce
var arr = [{
x: 11,
y: 12
}, {
x: 21,
y: 22
}, {
x: 31,
y: 32
}, {
x: 41,
y: 42
}];
var keys = Object.keys(arr[0]);
var op = arr.reduce(function(a, b) {
var arr = keys.reduce(function(x, y) {
return x.concat([b[y]]);
}, [])
return a.concat([arr]);
}, [keys]); //If all the objects are having identical keys!
console.log(JSON.stringify(op));
A little more verbose way of doing it:
[Edit: added the function to convert it back]
function convert(arr) {
var retArr = [ [/* keys (retArr[0]) */], [/* values (retArr[1]) */] ]
arr.forEach(function(obj){
// create new array for new sets of values
retArr[1].push([])
// put all of the keys in the correct array
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
// does the key exist in the array yet?
if (retArr[0].indexOf(key) === -1) {
retArr[0].push(key)
}
// get last index of retArr[1] and push on the values
retArr[1][retArr[1].length - 1].push(obj[key])
}
}
})
return retArr
}
function reConvert(arr) {
var retArr = []
var keys = arr[0]
arr[1].forEach(function(itemArr){
var obj = {}
itemArr.forEach(function(item, i){
obj[keys[i]] = item
})
retArr.push(obj)
})
return retArr
}
var objArr = [
{
x:11,
y:12
},{
x:21,
y:22
},{
x:31,
y:32
},{
x:41,
y:42
}
]
var arrFromObj = convert(objArr)
var objFromArr = reConvert(arrFromObj)
console.log(arrFromObj)
console.log(objFromArr)
A solution using Underscore.
First work out what the keys are:
var keys = _.chain(data)
.map(_.keys)
.flatten()
.uniq()
.value();
Then map across the data to pick out the value for each key:
var result = [
keys,
_.map(data, item => _.map(keys, key => item[key]))
];
and back again:
var thereAndBackAgain = _.map(result[1], item => _.omit(_.object(result[0], item), _.isUndefined));
Lodash's version of object is zipObject and omit using a predicate is omitBy:
var thereAndBackAgain = _.map(result[1], item => _.omitBy(_.zipObject(result[0], item), _.isUndefined));
var data = [
{
x:11,
y:12,
aa: 9
},{
x:21,
y:22
},{
x:31,
y:32,
z: 0
},{
x:41,
y:42
}
];
var keys = _.chain(data)
.map(_.keys)
.flatten()
.uniq()
.value();
var result = [
keys,
_.map(data, item => _.map(keys, key => item[key]))
];
var thereAndBackAgain = _.map(result[1], item => _.omit(_.object(result[0], item), _.isUndefined));
console.log(result)
console.log(thereAndBackAgain)
<script src="
https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
In ES6 you can do it by reducing it with Object.values(), and Object.keys(). You can restore it using a combination of Array.prototype.map() and Array.prototype.reduce():
const convertStructure = (data) => data.reduce((s, item) => {
s[1].push(Object.values(item));
return s;
}, [Object.keys(data[0]), []]); // all objects should be the same, so we can take the keys from the 1st object
const restoreStructure = ([keys, data]) => data.map((item) => item.reduce((o, v, i) => {
o[keys[i]] = v;
return o;
}, {}));
const data = [{
x: 11,
y: 12
}, {
x: 21,
y: 22
}, {
x: 31,
y: 32
}, {
x: 41,
y: 42
}];
const convertedStructure = convertStructure(data);
console.log('convertedStructure:\n', convertedStructure);
const restoredStructure = restoreStructure(convertedStructure);
console.log('restoredStructure:\n', restoredStructure);