Related
How would I assign an index to each object within a group in an array using javascript. So starting at zero and counting up inside each group, ordered by the group then the id.
Starting with this
let data = [
{ "id": "AB", "name": "Fred", "group": 1},
{ "id": "BC", "name": "Jane", "group": 2 },
{ "id": "CD", "name": "Mary", "group": 1 },
{ "id": "DE", "name": "Bob", "group": 2 },
{ "id": "EF", "name": "Chris", "group": 1 },
{ "id": "FG", "name": "Steve", "group": 2 },
{ "id": "GH", "name": "Jim", "group": 2 }
]
But adding the groupIndex field for each object.
dataGrouped = [
{ "id": "DE", "name": "Bob", "group": 2, "groupIndex": 1 },
{ "id": "EF", "name": "Chris", "group": 1, "groupIndex": 2 },
{ "id": "BC", "name": "Jane", "group": 2, "groupIndex": 0 },
{ "id": "FG", "name": "Steve", "group": 2, "groupIndex": 2 },
{ "id": "AB", "name": "Fred", "group": 1, "groupIndex": 0},
{ "id": "CD", "name": "Mary", "group": 1, "groupIndex": 1 },
{ "id": "GH", "name": "Jim", "group": 2, "groupIndex": 3 }
]
You could take an object for the indices.
const
indices = {},
data = [{ id: "AB", name: "Fred", group: 1 }, { id: "BC", name: "Jane", group: 2 }, { id: "CD", name: "Mary", group: 1 }, { id: "DE", name: "Bob", group: 2 }, { id: "EF", name: "Chris", group: 1 }, { id: "FG", name: "Steve", group: 2 }, { id: "GH", name: "Jim", group: 2 }],
result = data.map(o => {
indices[o.group] ??= 0;
return { ...o, groupIndex: indices[o.group]++ };
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Presented below is one possible way to achieve the desired objective (viz, assigning group index to array elements). This solution does not address: "ordered by the group then the id."
Code Snippet
// method to assign group-index
// the resulting array may get ordered based on group & group-index (ascending)
const assignGroupIndex = arr => (
Object.values(
arr?.reduce(
(acc, { group, ...rest }) => {
if (!(group in acc)) acc[group] = [];
acc[group].push({
group, ...rest, groupIndex: acc[group].length,
});
return acc;
},
{}
)
).flat()
);
/*
// method to assign group-index for each elt in array
const assignGroupIndex = arr => ( // arg "arr" is the input array
// extract the values from the intermediate result object generated below
Object.values(
// use ".reduce()" to iterate the array and generate an intermediate result
arr?.reduce(
// "acc" is the accumulator, destructure to access "group" & other props
(acc, { group, ...rest }) => {
// if "group" not already in accumulator, set the value as empty array
if (!(group in acc)) acc[group] = [];
// push to the array a new object with "groupIndex" as current length
acc[group].push({
group, ...rest, groupIndex: acc[group].length,
});
// always return the accumulator for each iteration of ".reduce()"
return acc;
},
{} // "acc" - the accumulator is initialized as empty object
)
).flat() // use ".flat()" to remove nested arrays from Object.values()
);
*/
let data = [
{ "id": "AB", "name": "Fred", "group": 1},
{ "id": "BC", "name": "Jane", "group": 2 },
{ "id": "CD", "name": "Mary", "group": 1 },
{ "id": "DE", "name": "Bob", "group": 2 },
{ "id": "EF", "name": "Chris", "group": 1 },
{ "id": "FG", "name": "Steve", "group": 2 },
{ "id": "GH", "name": "Jim", "group": 2 }
];
console.log(
'assigned group-index to array:\n',
assignGroupIndex(data)
);
.as-console-wrapper { max-height: 100% !important; top: 0 }
Explanation
Inline comments added to the snippet above.
let data = [
{ "id": "AB", "name": "Fred", "group": 1},
{ "id": "BC", "name": "Jane", "group": 2 },
{ "id": "CD", "name": "Mary", "group": 1 },
{ "id": "DE", "name": "Bob", "group": 2 },
{ "id": "EF", "name": "Chris", "group": 1 },
{ "id": "FG", "name": "Steve", "group": 2 },
{ "id": "GH", "name": "Jim", "group": 2 }
];
let groupCounts = {};
let dataGrouped = data.map(i=>({
...i,
groupIndex: groupCounts[i.group] = ++groupCounts[i.group] || 0
})).sort((a,b)=>a.group-b.group || a.id.localeCompare(b.id));
console.log(dataGrouped);
So assume I have the following array of objects:
var arr = [
{"name": "John", "score": "8.8"},
{"name": "John", "score": "8.6"},
{"name": "John", "score": "9.0"},
{"name": "John", "score": "8.3"},
{"name": "Tom", "score": "7.9"}
];
var count = 0;
var avgScore = arr.reduce(function (sum,person) {
if (person.name == "John") {
count+=1;
return sum + parseFloat(person.score);
}
return sum;
},0)/count);
Question: Is there a way way to calculate the average score for "John" without creating a global count variable. Ideally, the count would be internal to the anonymous function in the arr.reduce.
To avoid global variables, use a standard solution like IIFEs or block scopes. However I guess you're looking for a way to avoid a mutable counter.
The simplest would be to drop all other persons beforehand:
var johns = arr.filter(function(person) {
return person.name == "John";
});
var avgScore = johns.reduce(function (sum, person) {
return sum + parseFloat(person.score);
}, 0) / johns.length;
But you can also use a count that is passed along with the sum in an object:
var stats = arr.reduce(function ({count, sum}, person) {
return (person.name == "John")
? {count: count+1, sum: sum + parseFloat(person.score)}
: {count, sum};
}, {count:0, sum:0})
var avgScore = stats.sum / stats.count);
(using ES6 object property shorthands and destructuring)
You could return an object with the average in it, calculated on every loop with an update.
var arr = [{ name: "John", score: "8.8" }, { name: "John", score: "8.6" }, { name: "John", score: "9.0" }, { name: "John", score: "8.3" }, { name: "Tom", score: "7.9" }],
avgScore = arr.reduce(function (r, person) {
if (person.name === "John") {
r.sum += +person.score;
r.avg = r.sum / ++r.count;
}
return r;
}, { sum: 0, count: 0, avg: 0 }).avg;
console.log(avgScore);
A version with a closure and a direct return of the average.
var arr = [{ name: "John", score: "8.8" }, { name: "John", score: "8.6" }, { name: "John", score: "9.0" }, { name: "John", score: "8.3" }, { name: "Tom", score: "7.9" }],
avgScore = arr.reduce(function (sum, count) {
return function (avg, person) {
if (person.name === "John") {
sum += +person.score;
return sum / ++count;
}
return avg;
};
}(0, 0), 0);
console.log(avgScore);
Above as ES6
var arr = [{ name: "John", score: "8.8" }, { name: "John", score: "8.6" }, { name: "John", score: "9.0" }, { name: "John", score: "8.3" }, { name: "Tom", score: "7.9" }],
avgScore = arr.reduce(((sum, count) => (avg, person) => person.name === "John" ? (sum += +person.score) / ++count : avg)(0, 0), 0);
console.log(avgScore);
Here is yet another ES6 variant, which (ab)uses the third argument of reduce as temporary storage, and calls reduce again for a chained calculation of the average from the sum and count:
const arr = [
{"name": "John", "score": "8.8"},
{"name": "John", "score": "8.6"},
{"name": "John", "score": "9.0"},
{"name": "John", "score": "8.3"},
{"name": "Tom", "score": "7.9"}
];
const avg = arr.reduce( ([sum, count], {name, score}, i) =>
(i = name == 'John', [sum + i * score, count + i]), [0, 0] )
.reduce( (sum, count) => sum/count );
console.log(avg);
The solution using custom object as initialValue parameter for Array.prototype.reduce():
var arr = [{"name": "John", "score": "8.8"},{"name": "John", "score": "8.6"}, {"name": "John", "score": "9.0"}, {"name": "John", "score": "8.3"}, {"name": "Tom", "score": "7.9"}];
var result = arr.reduce(function (r, o) {
if (o.name === 'John') ++r.count && (r.sum += Number(o.score));
return r;
}, {sum: 0, count: 0});
console.log(result.sum/result.count); // `John's` average score
This is one line code to get average score
let avgScore = arr.reduce((sum, a) => { return sum + parseFloat(a.score) },0)/(arr.length||1)
You can use an IIFE to confine count to a private scope:
var arr = [
{"name": "John", "score": "8.8"},
{"name": "John", "score": "8.6"},
{"name": "John", "score": "9.0"},
{"name": "John", "score": "8.3"},
{"name": "Tom", "score": "7.9"}
];
var avgScore = arr.reduce(
(function() {
var count = 0;
return function (average, person) {
if (person.name == "John") {
count += 1;
return average * (count - 1) / count + parseFloat(person.score) / count;
}
return average;
};
})(),
0
);
console.log(avgScore);
A 2-pass works well without extra calculations, globals, or wrapper objects:
var arr = [
{"name": "John", "score": "8.8"},
{"name": "John", "score": "8.6"},
{"name": "John", "score": "9.0"},
{"name": "John", "score": "8.3"},
{"name": "Tom", "score": "7.9"}
];
var avgScore = arr.filter(x=>x.name=="John")
.reduce(function(v, n, c, r) {
return r.length-1 === c ?
(v + +n.score) / r.length :
v + +n.score;
},0);
console.log(avgScore);
If you are doing several different shapes, you should work in arrays of primitives so you can re-use methods:
var arr = [
{"name": "John", "score": "8.8"},
{"name": "John", "score": "8.6"},
{"name": "John", "score": "9.0"},
{"name": "John", "score": "8.3"},
{"name": "Tom", "score": "7.9"}
];
// define a few simple helpers
function pluck(o){ return o[this];}
function avg (v, n, c, r) { // calcs an average w/o a sum
return r.length-1 === c ?
(v + n) / r.length :
v + n ;
}
//now use the helpers to write succinct custom code:
var avgScore = arr.filter(x=>x.name=="John")
.map(pluck, "score")
.reduce(avg, 0);
console.log(avgScore);
The orig idea came from a custom report generator where users could pass in parameters and do some calcs on the backend without running custom code. the lib of generic methods such as avg can be used without a custom callback function. it's different, so i mention it...
This function takes the filter as an argument if you would want to filter on something else another time. It also uses filteredPersons.length; instead of count.
var arr = [
{"name": "John", "score": "8.8"},
{"name": "John", "score": "8.6"},
{"name": "John", "score": "9.0"},
{"name": "John", "score": "8.3"},
{"name": "Tom", "score": "7.9"}
];
function filterJohn(person){
return person.name === 'John';
};
function calculateAverageScore(persons, filterFunc){
const filteredPersons = persons.filter(filterFunc);
return filteredPersons.reduce((sum, person) => { return sum + parseFloat(person.score); }, 0)/filteredPersons.length;
};
calculateAverageScore(arr, filterJohn);
var arr = [
{"name": "John", "score": "8.8"},
{"name": "John", "score": "8.6"},
{"name": "John", "score": "9.0"},
{"name": "John", "score": "8.3"},
{"name": "Tom", "score": "7.9"}
];
const johnAvgScore = function(scores) {
return scores.filter(score => score.name === 'John').reduce((acc, score, i, arr) => acc + parseFloat(score.score)/ arr.length, 0)
}
console.log(johnAvgScore(arr))
I have an object table in which there is the score and the name of a character and I would like to retrieve the index with the highest score to be able to make a scoreboard.
This is what my array looks like
[
{
"score": 51,
"name": "toto"
},
{
"score": 94,
"name": "tata"
},
{
"score": 27,
"name": "titi"
},
{
"score": 100,
"name": "tutu"
}
]
In this case, I would like to get the index of the person who has the highest score, in this case, the index is 3 because it is tutu who has the highest score.
Thank advance for ur help
The sort function should do it:
var raw_scores = [
{
"score": 51,
"name": "toto"
},
{
"score": 94,
"name": "tata"
},
{
"score": 27,
"name": "titi"
},
{
"score": 100,
"name": "tutu"
}
]
var sorted_scores = raw_scores.sort(function(a,b){return b.score - a.score})
More info at w3schools
Using for loop
var index = 0;
var max = 0;
for (var i = 0; i < scores.length; i++) {
if (s[i].score > max) {
max = s[i].score;
index = i;
}
}
console.log(index);
You can use the reduce function
const array = [
{
"score": 51,
"name": "toto"
},
{
"score": 94,
"name": "tata"
},
{
"score": 27,
"name": "titi"
},
{
"score": 100,
"name": "tutu"
}
];
const highestScore = array.reduce((last, item) => {
// return the item if its score is greater than the highest score found.
if(!last || last.score < item.score) {
return item;
}
return last;
});
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
var data = [{
"score": 51,
"name": "toto"
},
{
"score": 94,
"name": "tata"
},
{
"score": 27,
"name": "titi"
},
{
"score": 100,
"name": "tutu"x
}
];
var max_score = Math.max.apply(Math, data.map(function(o) {
return o.score;
}))
console.log(data.filter(i => i.score === max_score))
[...].reduce((acc, item, idx) => (item.score > acc.score ? {score: item.score, index: idx} : acc), {score: 0, index:0}).index
So assume I have the following array of objects:
var arr = [
{"name": "John", "score": "8.8"},
{"name": "John", "score": "8.6"},
{"name": "John", "score": "9.0"},
{"name": "John", "score": "8.3"},
{"name": "Tom", "score": "7.9"}
];
var count = 0;
var avgScore = arr.reduce(function (sum,person) {
if (person.name == "John") {
count+=1;
return sum + parseFloat(person.score);
}
return sum;
},0)/count);
Question: Is there a way way to calculate the average score for "John" without creating a global count variable. Ideally, the count would be internal to the anonymous function in the arr.reduce.
To avoid global variables, use a standard solution like IIFEs or block scopes. However I guess you're looking for a way to avoid a mutable counter.
The simplest would be to drop all other persons beforehand:
var johns = arr.filter(function(person) {
return person.name == "John";
});
var avgScore = johns.reduce(function (sum, person) {
return sum + parseFloat(person.score);
}, 0) / johns.length;
But you can also use a count that is passed along with the sum in an object:
var stats = arr.reduce(function ({count, sum}, person) {
return (person.name == "John")
? {count: count+1, sum: sum + parseFloat(person.score)}
: {count, sum};
}, {count:0, sum:0})
var avgScore = stats.sum / stats.count);
(using ES6 object property shorthands and destructuring)
You could return an object with the average in it, calculated on every loop with an update.
var arr = [{ name: "John", score: "8.8" }, { name: "John", score: "8.6" }, { name: "John", score: "9.0" }, { name: "John", score: "8.3" }, { name: "Tom", score: "7.9" }],
avgScore = arr.reduce(function (r, person) {
if (person.name === "John") {
r.sum += +person.score;
r.avg = r.sum / ++r.count;
}
return r;
}, { sum: 0, count: 0, avg: 0 }).avg;
console.log(avgScore);
A version with a closure and a direct return of the average.
var arr = [{ name: "John", score: "8.8" }, { name: "John", score: "8.6" }, { name: "John", score: "9.0" }, { name: "John", score: "8.3" }, { name: "Tom", score: "7.9" }],
avgScore = arr.reduce(function (sum, count) {
return function (avg, person) {
if (person.name === "John") {
sum += +person.score;
return sum / ++count;
}
return avg;
};
}(0, 0), 0);
console.log(avgScore);
Above as ES6
var arr = [{ name: "John", score: "8.8" }, { name: "John", score: "8.6" }, { name: "John", score: "9.0" }, { name: "John", score: "8.3" }, { name: "Tom", score: "7.9" }],
avgScore = arr.reduce(((sum, count) => (avg, person) => person.name === "John" ? (sum += +person.score) / ++count : avg)(0, 0), 0);
console.log(avgScore);
Here is yet another ES6 variant, which (ab)uses the third argument of reduce as temporary storage, and calls reduce again for a chained calculation of the average from the sum and count:
const arr = [
{"name": "John", "score": "8.8"},
{"name": "John", "score": "8.6"},
{"name": "John", "score": "9.0"},
{"name": "John", "score": "8.3"},
{"name": "Tom", "score": "7.9"}
];
const avg = arr.reduce( ([sum, count], {name, score}, i) =>
(i = name == 'John', [sum + i * score, count + i]), [0, 0] )
.reduce( (sum, count) => sum/count );
console.log(avg);
The solution using custom object as initialValue parameter for Array.prototype.reduce():
var arr = [{"name": "John", "score": "8.8"},{"name": "John", "score": "8.6"}, {"name": "John", "score": "9.0"}, {"name": "John", "score": "8.3"}, {"name": "Tom", "score": "7.9"}];
var result = arr.reduce(function (r, o) {
if (o.name === 'John') ++r.count && (r.sum += Number(o.score));
return r;
}, {sum: 0, count: 0});
console.log(result.sum/result.count); // `John's` average score
This is one line code to get average score
let avgScore = arr.reduce((sum, a) => { return sum + parseFloat(a.score) },0)/(arr.length||1)
You can use an IIFE to confine count to a private scope:
var arr = [
{"name": "John", "score": "8.8"},
{"name": "John", "score": "8.6"},
{"name": "John", "score": "9.0"},
{"name": "John", "score": "8.3"},
{"name": "Tom", "score": "7.9"}
];
var avgScore = arr.reduce(
(function() {
var count = 0;
return function (average, person) {
if (person.name == "John") {
count += 1;
return average * (count - 1) / count + parseFloat(person.score) / count;
}
return average;
};
})(),
0
);
console.log(avgScore);
A 2-pass works well without extra calculations, globals, or wrapper objects:
var arr = [
{"name": "John", "score": "8.8"},
{"name": "John", "score": "8.6"},
{"name": "John", "score": "9.0"},
{"name": "John", "score": "8.3"},
{"name": "Tom", "score": "7.9"}
];
var avgScore = arr.filter(x=>x.name=="John")
.reduce(function(v, n, c, r) {
return r.length-1 === c ?
(v + +n.score) / r.length :
v + +n.score;
},0);
console.log(avgScore);
If you are doing several different shapes, you should work in arrays of primitives so you can re-use methods:
var arr = [
{"name": "John", "score": "8.8"},
{"name": "John", "score": "8.6"},
{"name": "John", "score": "9.0"},
{"name": "John", "score": "8.3"},
{"name": "Tom", "score": "7.9"}
];
// define a few simple helpers
function pluck(o){ return o[this];}
function avg (v, n, c, r) { // calcs an average w/o a sum
return r.length-1 === c ?
(v + n) / r.length :
v + n ;
}
//now use the helpers to write succinct custom code:
var avgScore = arr.filter(x=>x.name=="John")
.map(pluck, "score")
.reduce(avg, 0);
console.log(avgScore);
The orig idea came from a custom report generator where users could pass in parameters and do some calcs on the backend without running custom code. the lib of generic methods such as avg can be used without a custom callback function. it's different, so i mention it...
This function takes the filter as an argument if you would want to filter on something else another time. It also uses filteredPersons.length; instead of count.
var arr = [
{"name": "John", "score": "8.8"},
{"name": "John", "score": "8.6"},
{"name": "John", "score": "9.0"},
{"name": "John", "score": "8.3"},
{"name": "Tom", "score": "7.9"}
];
function filterJohn(person){
return person.name === 'John';
};
function calculateAverageScore(persons, filterFunc){
const filteredPersons = persons.filter(filterFunc);
return filteredPersons.reduce((sum, person) => { return sum + parseFloat(person.score); }, 0)/filteredPersons.length;
};
calculateAverageScore(arr, filterJohn);
var arr = [
{"name": "John", "score": "8.8"},
{"name": "John", "score": "8.6"},
{"name": "John", "score": "9.0"},
{"name": "John", "score": "8.3"},
{"name": "Tom", "score": "7.9"}
];
const johnAvgScore = function(scores) {
return scores.filter(score => score.name === 'John').reduce((acc, score, i, arr) => acc + parseFloat(score.score)/ arr.length, 0)
}
console.log(johnAvgScore(arr))
I have an nested array that i want to rebuild based on age value:
//Data
data = {"people":
[{"male_1": [
{"name": "Bob" ,"age": "32"},
{"name":"Mike", "age":"31"}
]},
{"female_2": [
{"name":"Jessica", "age": "24"},
{"name":"Ann", "age": "23"}
]}
[{"male_3": [
{"name": "Tom" ,"age": "31"},
{"name":"John", "age":"29"}
]}, ...
]}
New array should looks like:
people = [{"male_1": [
{"name": "Bob" ,"age": "32"}
]},
[{"male_3": [
{"name": "Tom" ,"age": "31"}
]},
{"female_2": [
{"name":"Jessica", "age": "24"}
]}, ...
]}
Based on this example i need to find the largest age of all "persons" then add this person to array then do same with the next one. The age can be same in this case there is no difference who goes first.
With the next function i can find first one and push it to new array, but how find next one?
var age = 0;
data["people"].forEach(function(item) {
for (var key in item) {
if (item.hasOwnProperty(key)) {
item[key].forEach(function(person) {
if (person.age > age) {
age = person.age;
oldest_person = person
}
});
}
}
});
console.log(oldest_person);
here is another interpretation. This one uses the native Array.prototype.sort as the helper function.
var data = { "people": [{ "male_1": [{ "name": "Bob", "age": "32" }, { "name": "Mike", "age": "31" }] }, { "female_2": [{ "name": "Jessica", "age": "24" }, { "name": "Ann", "age": "23" }] }, { "male_3": [{ "name": "Tom", "age": "31" }, { "name": "John", "age": "29" }] }] },
oldies = [],
peopleByAge = data.people.map(function(group){
for( var name in group ){
group[name] = group[name].sort(sortBy('age'));
oldies.push( group[name][0] );
}
return group;
});
// sort by an object key
function sortBy( key ){
return function(a, b){
return parseInt(a[ key ]) < parseInt(b[ key ]);
}
}
document.write('<pre>' + JSON.stringify({ oldies: oldies.sort(sortBy('age')), peopleByAge: peopleByAge }, 0, 2) + '</pre>');
Try this:
var age = 0;
var oldest_person = [];
var data = {"people":
[
{"male_1": [
{"name": "Bob" ,"age": "32"},
{"name":"Mike", "age":"31"}
]},
{"female_2": [
{"name":"Jessica", "age": "24"},
{"name":"Ann", "age": "23"}
]},
{"male_3": [
{"name": "Tom" ,"age": "31"},
{"name":"John", "age":"29"}
]}
]};
data["people"].forEach(function(item) {
for (var key in item) {
if (item.hasOwnProperty(key)) {
var age = 0;
var name = key;
var oldest = null;
item[key].forEach(function(person) {
// Determine the oldest person in each group ("male_1", "female_2", "male_3", ...)
if (person.age > age) {
age = person.age;
oldest = person;
}
});
// Push the oldest person into the 'oldest_person' array
var group = {};
group[name] = [ oldest ];
oldest_person.push(group);
}
}
});
console.log(oldest_person);
You can use some Array methods, like Array.prototype.forEach(), Array.prototype.reduce() and Object.keys().
var data = { "people": [{ "male_1": [{ "name": "Bob", "age": "32" }, { "name": "Mike", "age": "31" }] }, { "female_2": [{ "name": "Jessica", "age": "24" }, { "name": "Ann", "age": "23" }] }, { "male_3": [{ "name": "Tom", "age": "31" }, { "name": "John", "age": "29" }] }] },
people = [];
data.people.forEach(function (a) {
Object.keys(a).forEach(function (k) {
var o = {};
o[k] = a[k].reduce(function (c, d) {
return c.age > d.age ? c : d;
});
people.push(o);
});
});
document.write('<pre>' + JSON.stringify(people, 0, 4) + '</pre>');