Related
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 help in transforming data in a particular way to plot a graph. The data which I get from API is a different format. Please guide me on how to transform it
const demo = [
{
label: 'ABC',
vMini: 28,
vMaxi: 56,
dMini: 2,
dMaxi: 50,
},
{
label: 'BCD',
vMini: 2,
vMaxi: 56,
dMini: 3,
dMaxi: 50,
},
];
end result which i want is
[
{
section: "vMini",
"ABC": 28,
"BCD": 2,
},
{
section: "vMaxi",
"ABC": 56,
"BCD": 56
}
{
section: "dMini",
"ABC": 2,
"BCD": 3,
},
{
section: "dMaxi",
"ABC": 50,
"BCD": 50
}
]
I have started working on it and got confused with second loop.
for (let i = 0; i < demo.length; i += 1) {
for (let j in demo[i]) {
if (j === 'label') {
}
}
}
This one is a bit tricky with the way the data is structured, but you should be able to do this with array.reduce, like so:
const demo = [{label:"ABC",vMini:28,vMaxi:56,dMini:2,dMaxi:50},{label:"BCD",vMini:2,vMaxi:56,dMini:3,dMaxi:50}];
// get array of keys, and create a new object for each one except label
// ["label", "vMini", "vMaxi", "dMini", "dMaxi"]
let results = Object.keys(demo[0]).reduce((res, key) => {
if (key === "label") { return res; }
else {
// for each item in demo, create a key for the label and grab the key's value
let newObj = demo.reduce((_res, obj) => {
_res[obj.label] = obj[key];
return _res;
}, {section: key})
// push the new object into the results array
res.push(newObj);
}
return res;
}, [])
console.log(results);
Using reduce() and Map()
const demo = [{label:"ABC",vMini:28,vMaxi:56,dMini:2,dMaxi:50},{label:"BCD",vMini:2,vMaxi:56,dMini:3,dMaxi:50}];
const resMap = demo.reduce((a, v) => {
let label = v.label
for (let k in v) {
if (k == 'label') continue
a.has(k) || a.set(k, { section: k })
let o = a.get(k)
o[label] = v[k]
}
return a
}, new Map())
const resArr = [...resMap.values()]
console.log(resArr)
Here are some data:
data = [
{"Age":26,"Level":8},
{"Age":37,"Level":9},
{"Age":null,"Level":15},
{"Age":null,"Level":45}
];
from which I'm trying to calculate average for their properties:
var avg = {};
var rows = data.length;
data.forEach(obj => {
Object.keys(obj).forEach(k => {
if(obj[k] != null){
avg[k] = (avg[k] || 0) + obj[k] / rows;
}
});
});
return avg;
but the problem is in items that has properties with null values, where I'm trying to exclude null values from the calculation, and if you take a look at the the codepen there is Age: 15.75 instead of 31.5
because length of the data is always 4 (and should be 2 since 2 of them are null). How would be the best way to get the length to not be including the nulls?
You can have an object with nested object which has two properties value and count
const data = [
{"Age":26,"Level":8},
{"Age":37,"Level":9},
{"Age":null,"Level":15},
{"Age":null,"Level":45}
];
let avg = {}
data.forEach(x => {
for(let k in x){
if(!avg[k]){
avg[k] = {value:0,count:0};
}
if(x[k] !== null){
avg[k].value += x[k]
avg[k].count++;
}
}
})
avg = Object.fromEntries(Object.entries(avg).map(([k,v]) => ([k,v.value/v.count])))
console.log(avg)
let data = [
{"Age": 26, "Level": 8},
{"Age": 37, "Level": 9},
{"Age": null, "Level": 15},
{"Age": null, "Level": 45}
];
let averages = data.reduce((values, o) => {
Object.entries(o).forEach(([k, v]) => {
if (v !== null)
values[k] = (values[k] || []).concat(v);
});
return values;
}, {});
Object.entries(averages).forEach(([k, vs]) =>
averages[k] = vs.reduce((a, b) => a + b) / vs.length);
console.log(averages);
You can use a simple for...of and for...in loop to get the sum and count for each non-null item. You can add a get property to automatically calculate the average based on the sum and the count properties in the counter
const data = [{Age:26,Level:8},{Age:37,Level:9},{Age:null,Level:15},{Age:null,Level:45}];
let counter = {}
for (const item of data) {
for (const key in item) {
if (item[key] !== null) {
counter[key] = counter[key] || {
sum: 0,
count: 0,
get average() { return this.sum/this.count }
};
counter[key].sum += item[key]
counter[key].count++
}
}
}
console.log(counter)
I would do something like this: (not tested yet)
var data = [
{"Age":26,"Level":8},
{"Age":37,"Level":9},
{"Age":null,"Level":15},
{"Age":null,"Level":45}
];
var sum = { "Age": 0, "Level": 0 };
var average = { "Age": 0, "Level": 0 };
var sumCount = { "Age": 0, "Level": 0 };
// sum up all objects
for (var i = 0; i < data.length; i++) {
Object.keys(data[i]).forEach(function (key) {
if (data[i][key] == null || data[i][key] == undefined)
return;
sumCount[key]++;
sum[key] = sum[key] + data[i][key];
});
}
// make average object
Object.keys(average).forEach(function (key) {
average[key] = sum[key] / sumCount[key];
});
You could store the sum and count for every key independently.
var data = [{ "Age": 26, "Level": 8 }, { "Age": 37, "Level": 9 }, { "Age": null, "Level": 15 }, { "Age": null, "Level": 45 }],
avg = {},
temp = {};
data.forEach(obj => Object.keys(obj).forEach(k => {
if (obj[k] === null) return;
temp[k] = temp[k] || { sum: 0, count: 0 };
temp[k].sum += obj[k];
temp[k].count++;
avg[k] = temp[k].sum / temp[k].count;
}));
console.log(avg);
console.log(temp);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could also do it in a relatively concise manner with just Array.reduce and inside of it just iterate over the Object.keys:
var data = [ {"Age":26,"Level":8}, {"Age":37,"Level":9}, {"Age":null,"Level":15}, {"Age":null,"Level":45} ];
let result = data.reduce((r, c) => (Object.keys(c).forEach(k => {
r[k] = (r[k] || { Sum: 0, Count: 0, Avg: 0 })
r[k].Sum += c[k] || 0
r[k].Count += c[k] ? 1 : 0
r[k].Avg = r[k].Sum / r[k].Count
}), r), {})
console.log(result)
let say i have 1 multidimensional array and i want to exclude values that not equal in javascript.
here is the example array.
var filter = ["big_number", "odds_number"];
var arrays = {
"first" : {
"big_number" : [50,51,52],
"odds_number" : [39,41,51,53]
},
"second" : {
"big_number" : [61,62,63,64,65,70,72,73],
"odds_number" : [13,15,17,19,61,63,65,73]
}
};
i want to convert that array to be like this.
var new_arrays = {
"first" : [51],
"second" : [61,63,65,73]
};
here is my code
var newArray = {
"first" : [],
"second" : []
};
for (var k in arrays){
if (arrays.hasOwnProperty(k)) {
for(var f=0; f<filter.length; f++) {
newArray[k].push(arrays[k][filter[f]].filter(value => -1 !== arrays[k][filter[f]].indexOf(value))));
}
}
}
console.log(newArray);
actually i could do this code
var newArray = {
"first" : [],
"second" : []
};
for (var k in arrays){
if (arrays.hasOwnProperty(k)) {
newArray[k].push(arrays[k]["big_number"].filter(value => -1 !== arrays[k]["odds_number"].indexOf(value))));
}
}
console.log(newArray);
but i need to convert it through filter variable.
i could not use filter[0] and filter[1], because that values could change dynamically and could be more than 2 values in array.
You could loop through the keys and update the values using filter and includes:
var arrays={"first":{"big_number":[50,51,52],"odds_number":[39,41,51,53]},"second":{"big_number":[61,62,63,64,65,70,72,73],"odds_number":[13,15,17,19,61,63,65,73]}};
for (let key in arrays) {
arrays[key] = arrays[key]["big_number"]
.filter(n => arrays[key]["odds_number"].includes(n));
}
console.log(arrays)
If you don't want to mutate the original object then use Object.entries and reduce:
var arrays={"first":{"big_number":[50,51,52],"odds_number":[39,41,51,53]},"second":{"big_number":[61,62,63,64,65,70,72,73],"odds_number":[13,15,17,19,61,63,65,73]}};
const newObject = Object.entries(arrays).reduce((r, [key, {big_number, odds_number}]) => {
r[key] = big_number.filter(n => odds_number.includes(n));
return r
}, {})
console.log(newObject)
If you have more than 2 array properties, you can do something like this: Get all the arrays using Object.values and then use reduce to run the previous code recursively
var arrays = {
"first": {
"big_number": [50, 51, 52],
"odds_number": [39, 41, 51, 53],
"another_key": [41, 51, 53]
},
"second": {
"big_number": [61, 62, 63, 64, 65, 70, 72, 73],
"odds_number": [13, 15, 17, 19, 61, 63, 65, 73],
"another_key": [63, 65]
}
};
for (let key in arrays) {
arrays[key] = Object.values(arrays[key])
.reduce((a, b) => a.filter(c => b.includes(c)))
}
console.log(arrays)
Here is a little intersection snippet:
function intersect(a,b){
b.slice()
return a.filter(item=>{
if(b.includes(item)){
b.splice(b.indexOf(item),1)
return true
}
})
}
Using that, you can do this easily:
function intersect(a,b){
b.slice()
return a.filter(item=>{
if(b.includes(item)){
b.splice(b.indexOf(item),1)
return true
}
})
}
var filter = ["big_number", "odds_number"];
var output={}
var arrays = {
"first" : {
"big_number" : [50,51,52],
"odds_number" : [39,41,51,53]
},
"second" : {
"big_number" : [61,62,63,64,65,70,72,73],
"odds_number" : [13,15,17,19,61,63,65,73]
}
};
for(x in arrays){
output[x]=arrays[x][filter[0]]
for(let i=1;i<filter.length;i++){
output[x]=intersect(output[x],arrays[x][filter[i]])
}
}
console.log (output)
use Object.entries to get keys and values and then use reduce
var arrays = {
"first" : {
"big_number" : [50,51,52],
"odds_number" : [39,41,51,53]
},
"second" : {
"big_number" : [61,62,63,64,65,70,72,73],
"odds_number" : [13,15,17,19,61,63,65,73]
}
};
const output =Object.entries(arrays).reduce((accu, [key, {big_number}]) => {
if(!accu[key]) accu[key] = [];
big_number.forEach(num => {
if(num%2 !==0)
accu[key].push(num);
})
return accu;
}, {});
console.log(output);
You can get the unique values from both the arrays using Set and then using filter get only the common values.
var arrays = {"first": {"big_number": [50, 51, 52],"odds_number": [39, 41, 51, 53]},"second": {"big_number": [61, 62, 63, 64, 65, 70, 72, 73],"odds_number": [13, 15, 17, 19, 61, 63, 65, 73]}},
result = Object.keys(arrays).reduce((r,k) => {
let setB = new Set(arrays[k]["big_number"]);
r[k] = [...new Set(arrays[k]["odds_number"])].filter(x => setB.has(x));
return r;
},{});
console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have the following structure
var nights = [
{ "2016-06-25": 32, "2016-06-26": 151, "2016-06-27": null },
{ "2016-06-24": null, "2016-06-25": null, "2016-06-26": null },
{ "2016-06-26": 11, "2016-06-27": 31, "2016-06-28": 31 },
];
And I want to transform it to:
{
"2016-06-24": [null],
"2016-06-25": [32, null],
"2016-06-26": [151, null, 11],
"2016-06-27": [null, 31],
"2016-06-28": [31]
}
What's the shortest way to solve this? I have no problems with using Underscore, Lodash or jQuery.
My current code is:
var out = {};
for (var i = 0; i < nights.length; i++) {
for (var key in nights[i]) {
if (out[key] === undefined) {
out[key] = [];
}
out[key].push(nights[i][key]);
}
}
It's similar to Convert array of objects to object of arrays using lodash but that has all keys present in each object.
You can do it with the following snippet (no need for lodash etc):
const x = [{ '2016-06-25': 32, '2016-06-26': 151, '2016-06-27': null }, { '2016-06-24': null, '2016-06-25': null, '2016-06-26': null }, { '2016-06-26': 11, '2016-06-27': 31, '2016-06-28': 31 }, ];
let y = {};
x.forEach(obj => {
Object.keys(obj).forEach(key => {
y[key] = (y[key] || []).concat([obj[key]]);
});
});
console.log(y)
Here is my one-liner. Uses map to map keys into array of objects with properties of the keys. Then maps the original array to only that property and sets it as the value of the property. One issue with this way is that it will only use the properties of the first object in the array. So if other objects have properties that aren't in the first object they will be ignored.
const output = Object.assign({}, ...Object.keys(input[0]).map(props => ({[props]: input.map(prop => prop[props])})))
Edit: the output was in the wrong format, fixed
You could iterate over the array and the over the keys of the object and build a new object with the keys as new keys.
var data = [{ '2016-06-25': 32, '2016-06-26': 151, '2016-06-27': null }, { '2016-06-24': null, '2016-06-25': null, '2016-06-26': null }, { '2016-06-26': 11, '2016-06-27': 31, '2016-06-28': 31 }, ],
result = {};
data.forEach(function (o) {
Object.keys(o).forEach(function (k) {
result[k] = result[k] || [];
result[k].push(o[k]);
});
});
console.log(result);
Used Typescript -- obviously you can remove the types while working in JavaScript.
Assumption: The array of objects coming into the function will always have the same kind and number of object keys
mapperArrayOfJsonsToJsonOfArrays(inputArrayOfJsons: any): any {
if (inputArrayOfJsons.length > 0) {
let resultMap = {};
let keys: any[] = Object.keys(inputArrayOfJsons[0]);
keys.forEach((key: any) => {
resultMap[key] = [];
});
inputArrayOfJsons.forEach((element: any) => {
let values: any[] = Object.values(element);
let index = 0;
values.forEach((value: any) => {
resultMap[keys[index]].push(value);
index = index + 1;
});
});
return resultMap;
}
return {};
}