lodash: count values from array of objects - javascript

I have an array of objects like this:
[
{"question":"Q1","answer":"my answer 2"},
{"question":"Q1","answer":"my answer"}
{"question":"Q1","answer":"my answer"}
{"question":"Q2","answer":"answer 2"}
]
I would like to group by the question keys and return the counts of each answer.
e.g.
{
"Q1": [{
"answer": "my answer",
"count": 2
}, {
"answer": "my answer 2",
"count": 1
}],
"Q2": [{
"answer": "answer 2",
"count": 1
}]
}
,
I am able to groupBy questions using:
.groupBy("question") and count occurances of values using .countBy() but I am not sure how to combine the grouping and counting functions to achieve the desired output?

You can start with _.groupBy(array, 'question') - then use .map
For example:
var arr = [
{"question":"Q1","answer":"my answer 2"},
{"question":"Q1","answer":"my answer"},
{"question":"Q1","answer":"my answer"},
{"question":"Q2","answer":"answer 2"}
];
var result = _(arr)
.groupBy('question')
.map(function(item, itemId) {
var obj = {};
obj[itemId] = _.countBy(item, 'answer')
return obj
}).value();
console.log(JSON.stringify(result, null, 2));
See the working version at: http://jsbin.com/wixoronoqi/edit?js,console

Here is a native/vanilla js solution to this problem, using Array.reduce(), with and without the spread operator.
With spread operator, immutable style :
const questions = [
{"question":"Q1","answer":"my answer 2"},
{"question":"Q1","answer":"my answer"},
{"question":"Q1","answer":"my answer"},
{"question":"Q2","answer":"answer 2"}
];
const groupedQuestions = questions.reduce( (g, q) => {
return {
...g,
[q.question]: {
...g[q.question],
[q.answer] : (g[q.question] && g[q.question][q.answer] || 0) + 1
}
}
}, {})
document.write(JSON.stringify(groupedQuestions))
Without spread operator :
const questions = [
{"question":"Q1","answer":"my answer 2"},
{"question":"Q1","answer":"my answer"},
{"question":"Q1","answer":"my answer"},
{"question":"Q2","answer":"answer 2"}
];
const groupedQuestions = questions.reduce( (g, q) => {
typeof g[q.question] !== "undefined" || (g[q.question] = {});
typeof g[q.question][q.answer] !== "undefined" || (g[q.question][q.answer] = 0);
g[q.question][q.answer] += 1;
return g;
}, {})
document.write(JSON.stringify(groupedQuestions))

A "native javascript" solution using Array.forEach and Array.push functions:
var arr = [{"question":"Q1","answer":"my answer 2"},{"question":"Q1","answer":"my answer"}, {"question":"Q1","answer":"my answer"}, {"question":"Q2","answer":"answer 2"}];
var result = {};
arr.forEach(function(v){
var key = v['question'], Q = this[key], found = false;
if (Q) {
var len = Q.length;
while (len--) {
if (Q[len]['answer'] === v['answer']) {
Q[len]['count']++;
found = true;
}
}
if (!found) Q.push({'answer': v['answer'], 'count' : 1});
} else {
this[key] = [];
this[key].push({'answer': v['answer'], 'count' : 1});
}
}, result);
console.log(JSON.stringify(result, 0, 4));
The output:
{
"Q1": [
{
"answer": "my answer 2",
"count": 1
},
{
"answer": "my answer",
"count": 2
}
],
"Q2": [
{
"answer": "answer 2",
"count": 1
}
]
}

Related

Return additional properties using reduce and spread operator JS?

I am using the reduce function below to count how many times a players name is mentioned and then list them based on who was mentioned the most to the least.
I am trying to return the 2nd property [`${value.subtitles[0].name} + ${index}`] : value.subtitles[0].url with my object and sort it. However it is not sorting properly. When only returning the first property [value.title]: (acc[value.title] || 0) + 1, everything works as intended. But the second property is making it sort incorrectly. It is supposed to be sorting based on the title property value which is an integer of how many times that player was mentioned, from most to least. Why is this happening?
Thanks for the help!
const players = [
{
"title": "Mike",
"titleUrl": "https://mikegameplay",
"subtitles": [
{
"name": "Mike Channel",
"url": "https://channel/mike"
}
]
},
{
"title": "Cindy",
"titleUrl": "https://cindy",
"subtitles": [
{
"name": "Cindy Channel",
"url": "https://channel/cindy"
}
]
},
{
"title": "Mike",
"titleUrl": "https://mike",
"subtitles": [
{
"name": "Mike Channel",
"url": "https://channel/mike"
}
]
},
{
"title": "Haley",
"titleUrl": "https://Haley",
"subtitles": [
{
"name": "Haley Channel",
"url": "https://channel/haley"
}
]
},
{
"title": "Haley",
"titleUrl": "https://Haley",
"subtitles": [
{
"name": "Haley Channel",
"url": "https://channel/haley"
}
]
},
{
"title": "Haley",
"titleUrl": "https://Haley",
"subtitles": [
{
"name": "Haley Channel",
"url": "https://channel/haley"
}
]
}
]
const counts = players.reduce((acc, value, index) => ({
...acc,
[value.title]: (acc[value.title] || 0) + 1,
[`${value.subtitles[0].name} + ${index}`] : value.subtitles[0].url
}), {});
const sortedValues = [];
for (const value in counts) {
sortedValues.push([value, counts[value]]);
};
sortedValues.sort((a, b) => b[1] - a[1]);
console.log(sortedValues)
try this
var groupBy = function (xs, key) {
return xs.reduce(function (rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
var pl = groupBy(players, "title");
console.log(pl);
let sortable = [];
for (var item in pl) {
sortable.push([item, pl[item].length, pl[item][0].subtitles[0].url]);
}
sortable.sort(function (a, b) {
return b[1] - a[1];
});
console.log(JSON.stringify(sortable));
result
[["Haley",3,"https://channel/haley"],["Mike",2,"https://channel/mike"],["Cindy",1,"https://channel/cindy"]]

Unable to update a JSON value

I'm writing a code where I need to filter a JSON array and update a value of a key. Here is my code.
var data = [{
"Id": "1",
"ab": '123',
"afb_Educational_expense_types_for_school__c": "Books or supplies"
}, {
"Id": "2",
"ab": '343',
"afb_Educational_expense_types_for_school__c": "Mandatory fees"
}, {
"Id": "3",
"ab": '34',
}];
var itemVar = data.filter(item => item.Id == '3');
itemVar['ab'] = '22';
console.log(itemVar);
Here I'm trying to set 'ab' to 22 but it is not working. Where am I going wrong?
Your itemVar is an array, because .filter always returns an array. You have to specify that you want the first element in the array [0]
itemVar[0]['ab'] = '22';
You can use findIndex and then update the relevant item of the array:
const data = [
{ "Id": "1", "ab": '123', "afb_Educational_expense_types_for_school__c": "Books or supplies" },
{ "Id": "2", "ab": '343', "afb_Educational_expense_types_for_school__c": "Mandatory fees" },
{ "Id": "3", "ab": '34' }
];
let index = data.findIndex((item) => item.Id == '3');
if (index !== -1) data[index].ab = '22';
console.log(data);
var itemVar = data.find((item) => item.Id == '3')
itemVar.ab = '22'
Thats the easiest way to solve it.

Looping inside an array of object

I need some help with my code here. It's a jquery-bracket project.
I have an object that has an array inside, there's a line of array i want to loop so I don't have to manually generated those lines
var team = ["Team 1", "Team 2", "Team 3", "Team 4"];
var result = [1, 2, 2, 1];
var teams = "";
for (i = 0; i < team.length; i++) {
teams += [`["${team[i++]}"`, ` "${team[i]}"], \n`]
}
var singleElimination = {
"teams": [
// line that I needed for loop
[team[0], team[1]],
[team[2], team[3]],
],
"results": [
[
[
// also line that I needed for loop
[result[0], result[1]]
]
]
]
}
I have tried to pass the loop into a variable and passing them inside of an array, but it doesn't seems work.
sorry for my bad English, looking forward for the answer!
demo : https://quizzical-stonebraker-2d808a.netlify.com/
You can simply use team.join(',');
eg:-
var singleElimination = {
"teams": [
[team.join(',')]
var team = ["Team 1", "Team 2", "Team 3", "Team 4"];
var result = [1, 2, 2, 1];
var obj = {}
for(var i=0; i<team.length; i++){
obj[team[i]] = result[i];
}
console.log(obj)
var team = ["Team 1", "Team 2", "Team 3", "Team 4"];
var result = [1, 2, 2, 1];
var singleElimination = {
teams: [
// line that I needed for loop
[team[0], team[1]],
[team[2], team[3]]
],
results: [
[
[
// also line that I needed for loop
[result[0], result[1]],
[result[2], result[3]]
]
]
]
};
console.log("singleElimination", singleElimination);
var _teams = "";
singleElimination.teams.forEach(element => {
element.forEach((team, index) => {
_teams += element[index] + ", ";
});
});
var _teamResults = "";
singleElimination.results.forEach(element => {
element.forEach((team, index) => {
_teamResults += element[index] + ", ";
});
});
console.log("_teams", _teams);
console.log("_teamResults", _teamResults);

map and reduce an array at the same time in javascript

I have an array of ~1800 object represents games played in a league. I need a new array that has an object for each team, and will include 4 new fields (wins, losses, ties, and points). Here is a sample of the array I am working with:
[
{
"homeGoals": 2,
"gameId": "12221",
"homeTeam": {
"id": "aasfdsf1",
"teamName": "Team 1"
},
"awayTeam": {
"id": "aasfdsf2",
"teamName": "Team 2"
},
"id": "ggaew1",
"awayGoals": 4
},
{
"homeGoals": 5,
"gameId": "12222",
"homeTeam": {
"id": "aasfdsf1",
"teamName": "Team 1"
},
"awayTeam": {
"id": "aasfdsf3",
"teamName": "Team 3"
},
"id": "ggaew2",
"awayGoals": 1
},
{
"homeGoals": 4,
"gameId": "12223",
"homeTeam": {
"id": "aasfdsf2",
"teamName": "Team 2"
},
"awayTeam": {
"id": "aasfdsf3",
"teamName": "Team 3"
},
"id": "ggaew3",
"awayGoals": 4
},
{
"homeGoals": null,
"gameId": "12223",
"homeTeam": {
"id": "aasfdsf2",
"teamName": "Team 2"
},
"awayTeam": {
"id": "aasfdsf3",
"teamName": "Team 3"
},
"id": "ggaew4",
"awayGoals": null
}
]
And here is an example of what I need the result to look like:
[
{
"id": "aasfdsf1",
"name": "Team 1",
"wins": 1,
"losses": 1,
"ties": 0,
"points": 2
},
{
"id": "aasfdsf2",
"name": "Team 2",
"wins": 1,
"losses": 0,
"ties": 1,
"points": 3
},
{
"id": "aasfdsf3",
"name": "Team 3",
"wins": 0,
"losses": 1,
"ties": 1,
"points": 1
}
]
Some games have not been played, so the homeGoals and awayGoals fields will be null.
So far I have a list of unique teams, only where the games have been completed:
const completedGames = games.filter(x => x.homeGoals !== null)
const homeTeams = [...new Set(completedGames.map(x => x['homeTeam']))];
const awayTeams = [...new Set(completedGames.map(x => x['awayTeam']))];
const teams = [...new Set([...homeTeams, ...awayTeams])]
I know I need to do some sort of reduce function, but am having trouble figuring it. I am pretty sure the step I just did before would be irrelevant if I had a proper map reduce function. Any help would be greatly appreciated !
This can be expressed in a simpler way with flatMap. It's not built-in in JS, but easy to implement:
let flatMap = (a, fn) => [].concat(...a.map(fn));
Now, on the map step, you can emit two "result" object per game (or no results at all if the game is incomplete):
results = flatMap(data, g => {
if (g.homeGoals === null || g.awayGoals === null)
return [];
if (g.homeGoals > g.awayGoals)
return [
{id: g.homeTeam.id, r: 'win'},
{id: g.awayTeam.id, r: 'loss'},
];
if (g.homeGoals < g.awayGoals)
return [
{id: g.homeTeam.id, r: 'loss'},
{id: g.awayTeam.id, r: 'win'},
];
if (g.homeGoals === g.awayGoals)
return [
{id: g.homeTeam.id, r: 'tie'},
{id: g.awayTeam.id, r: 'tie'},
];
});
This creates an array like
{ id: 'aasfdsf1', r: 'loss' },
{ id: 'aasfdsf2', r: 'win' },
{ id: 'aasfdsf1', r: 'win' }, etc
which is easy to reduce:
summary = results.reduce((m, {id, r}) => {
let e = m[id] || {};
e[r] = (e[r] || 0) + 1;
return Object.assign(m, {[id]: e})
}, {});
You can also make is less verbose by encoding wins, losses and ties by 1, -1, 0 respectively, in which case the mapper becomes:
results = flatMap(
data.filter(g => g.homeGoals !== null),
g => {
let d = g.homeGoals - g.awayGoals;
return [
{id: g.homeTeam.id, r: Math.sign(+d)},
{id: g.awayTeam.id, r: Math.sign(-d)},
]
});
I think you are looking to something like this:
const hashMapTeams = games.filter(x => x.homeGoals !== null)
.reduce((res, match)=>{
/* do the calculations here */
/* put the values on the res object, using res as a HashMap*/
res["/*the home team id*/"].id = /*id value*/
res["/*the home team id*/"].name = /*name value*/
res["/*the home team id*/"].wins= /* the right value */;
res["/*the home team id*/"].losses= /* the right value */;
res["/*the home team id*/"].ties= /* the right value */;
res["/*the home team id*/"].points= /* the right value */;
res["/*the away team id*/"].id = /*id value*/
res["/*the away team id*/"].name = /*name value*/
res["/*the away team id*/"].wins= /* the right value */;
res["/*the away team id*/"].losses= /* the right value */;
res["/*the away team id*/"].ties= /* the right value */;
res["/*the away team id*/"].points= /* the right value */;
},{});
/* This will convert again the object to an array */
const arrayTeams = Object.keys(hashMapTeams).map(function (key) { return hashMapTeams[key]; });
This gets the exactly result you are looking for:
{
"id": "aasfdsf1",
"name": "Team 1",
"wins": 1,
"losses": 1,
"ties": 0,
"points": 2
},
I used tenary and brackets to show you more than one way to approach that, you can use either one.
let result = [];
your1800ArrayObj.map(data => {
let wins = data.wins ? data.wins : 0;
let losses = data.losses ? data.losses : 0;
let ties = data['ties'] || 0;
let points = data['points'] || 0;
if (data.homeGoals === null && data.awayGoals === null) {
console.log('game not played')
} else {
if (data.homeGoals > data.awayGoals) {
wins += 1
points += 1
} else if (data.homeGoals < data.awayGoals) {
losses += 1
} else {
ties += 1
}
}
result.push({
id: data.id,
name: data.homeTeam.teamName ,
wins: wins,
losses: losses,
ties: ties,
points: points
})
})
return result
}

Javascript merge 2 arrays into a 3rd array to get all data required

I have 2 separate arrays which I need to merge into a third one so I can get all the data required.
Basically the 1st array has an id, and name and in order to get the address I need to search inside the 2nd array and match the id's so I can have all the data from the person.
Here is the data and code:
//Array 1
var myPeopleArray = [{"people":[{"id":"123","name":"name 1"},{"id":"456","name":"name 2"}]}];
//Array 2
var myPersonArray = [{"person":[{"id":"123","address":"address 1"},{"id":"456","address":"address 2"}]}];
var arrayLength = myPeopleArray[0].people.length;
for (var i = 0; i < arrayLength; i++) {
console.log("id: " + myPeopleArray[0].people[i].id);
}
//Wanted Result:
[{"people":[
{
"id":"123",
"name":"name 1",
"address":"address 1"
},
{
"id":"456",
"name":"name 2",
"address":"address 2"
}
]
}]
How can I do this?
var myPeopleArray = [{"people":[{"id":"123","name":"name 1"}, {"id":"456","name":"name 2"}]}];
var myPersonArray = [{"person":[{"id":"123","address":"address 1"}, {"id":"456","address":"address 2"}]}];
for(var i=0;i<myPeopleArray[0].people.length;i++)
{
myPeopleArray[0].people[i].address = myPersonArray[0].person[i].address;
}
document.write(JSON.stringify(myPeopleArray));
You could iterate both arrays and build new object with the joined properties.
var myPeopleArray = [{ "people": [{ "id": "123", "name": "name 1" }, { "id": "456", "name": "name 2" }] }],
myPersonArray = [{ "person": [{ "id": "123", "address": "address 1" }, { "id": "456", "address": "address 2" }] }],
hash = Object.create(null),
joined = [],
joinById = function (o) {
if (!(o.id in hash)) {
hash[o.id] = {};
joined.push(hash[o.id]);
}
Object.keys(o).forEach(function (k) {
hash[o.id][k] = o[k];
});
};
myPeopleArray[0].people.forEach(joinById);
myPersonArray[0].person.forEach(joinById);
console.log(joined);

Categories

Resources