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)
Related
The code i tried
function findHighest(){
var highest = 0; // assum min 0
var highestItem;
$('tr').each(function(index, item){
if(index > 0){
var math = $(item).find('td').eq(1).text();
var eng = $(item).find('td').eq(2).text();
var lit = $(item).find('td').eq(3).text();
//alert(math)
var sum = parseFloat(math) + parseFloat(eng) + parseFloat(lit)
if (sum > highest){
highest = sum;
highestItem = item;
}
}
})
$(highestItem).css({ 'font-style': 'italic', 'color': 'red' });
}
I am trying to find name of student who got highest marks in class in at least two subjects in JavaScript?
const highestMarks=[];
const studentsWithHighestMarks=[];
const students = [{ name: "mini", subject: [{ maths : 20}, {english: 23}, { science: 25}, { sports: 24}] }, { name: "jerry", subject: [{ maths : 22}, {english: 20}, { science: 20}, { sports: 21}] }, { name: "john", subject: [{ maths : 23}, {english: 25}, { science: 20}, { sports: 21}] }];
students.forEach(student=>{
student.subject.forEach(subject=>{
for(let key in subject){
var index = highestMarks.findIndex(obj => {
return obj.subject === key
});
if (index===-1) {
highestMarks.push({
subject:key,
marks:subject[key],
students:[student.name]
})
}else if(highestMarks[index].marks<subject[key]){
highestMarks[index].marks=subject[key];
highestMarks[index].students=[student.name];
}
else if(highestMarks[index].marks==subject[key]){
highestMarks[index].marks=subject[key];
highestMarks[index].students.push(student.name);
}
}
})
});
students.forEach(student=>{
let count=0;
highestMarks.forEach(item=>{
if(item.students.includes(student.name)){
count++;
}
});
if(count>=2){
studentsWithHighestMarks.push(student.name)
}
})
console.log(studentsWithHighestMarks)
const subjectsConsidering = 2;
const getStudentMarks = (student) => {
const studentMarksList = [];
student.subject.forEach((subjectData) => {
studentMarksList.push(Object.values(subjectData)[0]);
});
const sum = studentMarksList.sort().reverse().reduce((sum, a, index) => {
// restrict only for first 2 subjects
if (index < subjectsConsidering) {
return sum + a;
}
return sum + 0;
});
return sum;
}
students.sort((studentA, studentB) => {
//return 0 for equal marks
return getStudentMarks(studentA) > getStudentMarks(studentB) ? -1 : 1;
});
console.log(students);
//This gives the sorted array of students having the highest marks in at least 2 subjects.
// Transform the data into a more manageable format
// { "subject": [["name", grade], ["name", grade], ["name", grade]] }
let transformed = students.reduce(
(data, student) => {
student.subject.forEach(subject => {
const key = keys(subject)[0];
const grade = [student.name, subject[key]];
if (!(key in data)) {
data[key] = [];
}
data[key].push(grade);
})
return data;
},
{}
)
// Utility function to compare grades
function gradeCompare(a, b) {
return a[1] > b[1] ? -1 : 1;
}
// List the top student in each subject
let names = Object.keys(transformed).map((subject) => {
return transformed[subject].sort(gradeCompare)[0][0];
});
// names :: [ "john", "john", "mini", "mini" ]
// Count the student names
let counts = names.reduce((acc, current) => {
acc[current] = (acc[current] || 0) + 1;
return acc;
}, {});
// counts :: { john: 2, mini: 2 }
// Find the maximum occurring count
let maxCount = Math.max(...Object.values(counts));
// maxCount :: 2
// Filter the keys that have that count
let topStudents = Object.keys(counts).filter(k => counts[k] === maxCount);
// topStudents :: [ "john", "mini" ]
I have arr array of objects, I need to pivot it with product,calorie and apply (grouping & sum) on remaining parameters.
And then require data in single object.
I tried below code, it works fine but I divided code in 3 parts.
Could I have better code than this or it is ok.
var arr = [{
"product": "Jam",
"calorie": 2000,
"A": 300,
"B": 500,
"type": "Daily"
},
{
"product": "Sugar",
"calorie": 1000,
"A": 100,
"B": 200,
"type": "Daily"
}
]
var a1 = {}
var a2 = {}
//Step-1 Pivot
for (let i = 0; i < arr.length; i++) {
a1[arr[i]['product']] = arr[i]['calorie'];
}
//Step-2 Group and sum
a2 = groupAndSum(arr, ['type'], ['A', 'B'])[0];
//Step-3 merging.
console.log({ ...a1,
...a2
})
//General grouping and summing function that accepts an
//#Array:Array of objects
//#groupKeys: An array of keys to group by,
//#sumKeys - An array of keys to sum.
function groupAndSum(arr, groupKeys, sumKeys) {
return Object.values(
arr.reduce((acc, curr) => {
const group = groupKeys.map(k => curr[k]).join('-');
acc[group] = acc[group] || Object.fromEntries(groupKeys.map(k => [k, curr[k]]).concat(sumKeys.map(k => [k, 0])));
sumKeys.forEach(k => acc[group][k] += curr[k]);
return acc;
}, {})
);
}
Here a single function which takes 3 params:
const func = (arr, pivot_vals, sum_vals) => {
return arr.reduce((a, v) => {
pivot_vals.forEach((pivot) => {
a[v[pivot[0]]] = v[pivot[1]];
});
sum_vals.forEach((key) => {
if (!a[key]) a[key] = 0;
a[key] += v[key];
});
return a;
},{});
};
arr
containing the data
sum_vals
array with all props you want do be summed
pivot_vals
nested array with the props which should be linked
I wans't sure what to do with the type, since it is a string it can`t be summed. Did you want to count the amount of types ?
let arr = [
{
product: "Jam",
calorie: 2000,
A: 300,
B: 500,
type: "Daily",
},
{
product: "Sugar",
calorie: 1000,
A: 100,
B: 200,
type: "Daily",
},
];
let sum_vals = ["A","B"]
let pivot_vals = [["product", "calorie"]];
const func = (arr, pivot_vals, sum_vals) => {
return arr.reduce((a, v) => {
pivot_vals.forEach((pivot) => {
a[v[pivot[0]]] = v[pivot[1]];
});
sum_vals.forEach((key) => {
if (!a[key]) a[key] = 0;
a[key] += v[key];
});
return a;
},{});
};
console.log(func(arr, pivot_vals, sum_vals));
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 have this array -
let array = [
{
"id": 123,
"pair": 312
},
{
"id": 321,
"pair": 111
},
{
"id": 312,
"pair": 123
},
{
"id": 111,
"pair": 321
}
];
And i need it to be sorted like this =
let array = [
{
"id": 123,
"pair": 312
},
{
"id": 312,
"pair": 123
},
{
"id": 321,
"pair": 111
},
{
"id": 111,
"pair": 321
}
];
Which means that i need to find the matched value of the pair key in the object, and put it right after the first element (eventually i need the to be sorted in pairs order - of course the array will be way bigger and mixed)
i could not find a efficient way to achieve this.
this is what i tried - it feels very unefficient
products is the array i get from the server.
let pairs = [];
let prods = [...products];
for(let product of prods){
if(product.matched){
continue;
}
let pairStock = product.pairStock;
let companyId = product.company;
let matched = prods.filter(prod => prod.productId === pairStock && String(prod.company) === String(companyId));
if(matched.length > 0){
pairs.push(product);
pairs.push(matched[0]);
let index = prods.findIndex(prod => prod.productId === matched[0].productId);
prods[index].matched = true;
}
};
this will sort data when the number of items that are linked togather is between 0 and array.length.
let products = [
{ productId: 'PK0154', pairStock: 'PK0112-02' },
{ productId: 'PK0112-02', pairStock: 'PK0154' },
{ productId: 'MGS-140', pairStock: 'MGS-136' },
{ productId: 'GM-0168', pairStock: 'GM-0169' },
{ productId: 'GM-0169', pairStock: 'GM-0168' },
{ productId: 'MGS-136', pairStock: 'MGS-140' },
]
function sort(data) {
var mappedArray = {}
data.forEach(obj => (mappedArray[obj.productId] = obj))
data.sort((a, b) => a.productId.localeCompare( b.productId) )
var addToRes = (res, id) => {
if (id !== undefined && mappedArray[id] !== undefined) {
var obj = mappedArray[id]
mappedArray[id] = undefined
res.push(obj)
addToRes(res, obj.pairStock)
}
}
var result = []
data.forEach(item => addToRes(result, item.productId))
return result
}
console.log(sort(products))
its results
0: {productId: "GM-0168", pairStock: "GM-0169"}
1: {productId: "GM-0169", pairStock: "GM-0168"}
2: {productId: "MGS-136", pairStock: "MGS-140"}
3: {productId: "MGS-140", pairStock: "MGS-136"}
4: {productId: "PK0112-02", pairStock: "PK0154"}
5: {productId: "PK0154", pairStock: "PK0112-02"}
The performant way (store a map of products and look up each product's pair by it's id) - O(n*2):
const productsObj = {};
products.forEach(product => productsObj[product.id] = product);
const [seenProducts, pairedProducts] = [{}, []];
products.forEach(product => {
const productPair = productsObj[product.pair);
if (!seenProducts[product.id]) pairedProducts.push(...[product, productPair])
seenProducts[productPair.id] = true;
});
console.log(pairedProducts)
The intuitive way O(n^2):
// Find the next pair if a pair doesn't already exist
const arr = [];
for (let i = 0; i < products.length; i++) {
const containsPair = arr.some(item => item.pair === products[i].id);
if (containsPair === false) {
const productPair = products.slice(i).find(({ id }) => id === item.pair));
arr.push(...[products[i], productPair]);
}
}
console.log(arr)
I have an array which i need to compare it's values - and if there are duplication - i want to store them in array, for example :
obj1 = [{"manager_id":1,"name":"john"},{"manager_id":1,"name":"kile"},
{"manager_id":2,"name":"kenny"},
{"manager_id":4,"name":"stan"}]
obj2 = [{"employees_id":1,"name":"dan"},
{"employees_id":1,"name":"ben"},{"employees_id":1,"name":"sarah"},
{"employees_id":2,"name":"kelly"}]
If "manger_id" === "employees_id - then the result would be :
// {1:[{"manager_id":1,"name":"john"},{"manager_id":1,"name":"kile"},
{"employees_id":1,"name":"dan"}, {"employees_id":1,"name":"ben"},
{"employees_id":1,"name":"sarah"}]};
I've tried :
var obj1 = [{
"manager_id": 1,
"name": "john"
}, {
"manager_id": 1,
"name": "kile"
}, {
"manager_id": 2,
"name": "kenny"
}, {
"manager_id": 4,
"name": "stan"
}];
var obj2 = [{
"employees_id": 1,
"name": "dan"
}, {
"employees_id": 1,
"name": "ben"
}, {
"employees_id": 1,
"name": "sarah"
}, {
"employees_id": 2,
"name": "kelly"
}];
var res = obj1.concat(obj2).reduce(function(r, o) {
r[o.manager_id] = r[o.employees_id] || [];
r[o.manager_id].push(o);
return r;
}, {});
console.log(res);
.as-console-wrapper {
max-height: 100% !important;
top: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div></div>
As you can the results of the "manager_id" aren't added - only one - when there should be more
if manager_id === employees_id // should output in the first key
{1:[{"manager_id":1,"name":"john"},{"manager_id":1,"name":"kile"},
{"employees_id":1,"name":"dan"}, {"employees_id":1,"name":"ben"},
{"employees_id":1,"name":"sarah"}]};
As you can see there are several common id's
r[o.manager_id] = r[o.employees_id] || []; in this statement if a manager didn't have an employee_id the array was being reset for that id.
One way doing it right is this:
var res = obj1.concat(obj2).reduce(function(r, o) {
var id;
if(o.hasOwnProperty('manager_id')) {
id = o['manager_id'];
}
else {
id = o['employees_id'];
}
r[id] = r[id] || [];
r[id].push(o);
return r;
}, {});
The problem relies on this line:
r[o.manager_id] = r[o.employees_id] || [];
You should have in mind that some objects in your arrays have the manager_id and some other don't, they have the employees_id instead, so you have to evaluate that first with this line:
var itemId = o.manager_id || o.employees_id;
Try this code:
var res = obj1.concat(obj2).reduce(function(r, o) {
var itemId = o.manager_id || o.employees_id;
r[itemId] = r[itemId] || [];
r[itemId].push(o);
return r;
}, {});