map and reduce an array at the same time in javascript - 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
}

Related

how to get max value from a nested json array

I have a nested json array and I am trying to get the maximum value of the points attribute in this array.
data = {
"name": "KSE100",
"children": [
{
"name": "TECHNOLOGY & COMMUNICATION",
"children": [
{
"name": "TRG",
'points': -21
},
{
"name": "SYS",
},
]
},
{
"name": "OIL",
"children": [
{
"name": "PPL",
'points': 9
},
{
"name": "PSO",
'points': -19
},
]
},
]
}
I want the max value of points from under the children sections. I mean from under technology and oil sectors.
What I've done so far:
var max;
for (var i in data.children.length) {
for (var j in data.data[i]) {
var point = data.data[i].children[j]
}
}
Try the following:
data = {
"name": "KSE100",
"children": [
{
"name": "TECHNOLOGY & COMMUNICATION",
"children": [
{
"name": "TRG",
'points': -21
},
{
"name": "SYS",
},
]
},
{
"name": "OIL",
"children": [
{
"name": "PPL",
'points': 9
},
{
"name": "PSO",
'points': -19
},
]
},
]
}
var array = [];
for (var first of data.children) {
for (var second of first.children) {
if(second.points != undefined)
{
array.push(second);
}
}
}
var maximumValue = Math.max.apply(Math, array.map(function(obj) { return obj.points; }));
console.log(maximumValue);
you can use the reduce method on the array object to do this
const maxValues = []
data.children.forEach(el => {
if (el.name === 'OIL' || el.name === 'TECHNOLOGY & COMMUNICATIO'){
const max = el.children.reduce((current, previous) => {
if (current.points > previous.points) {
return current
}
}, 0)
maxValues.append({name: el.name, value: max.points})
}
})
This will give you an array of the objects with the name and max value.
First you can convert your object to a string through JSON.stringify so that you're able to use a regular expression
(?<=\"points\":)-?\\d*
To matchAll the values preceded by the pattern \"points\": that are or not negative values. After it, convert the result to a array through the spread operator ... and then reduce it to get the max value.
const data = {name:"KSE100",children:[{name:"TECHNOLOGY & COMMUNICATION",children:[{name:"TRG",points:-21},{name:"SYS"}]},{name:"OIL",children:[{name:"PPL",points:9},{name:"PSO",points:-19}]}]};
console.log(
[ ...JSON.stringify(data).matchAll('(?<=\"points\":)-?\\d*')]
.reduce((acc, curr) => Math.max(curr, acc))
)
I wasn't 100% sure, what your exact goal is, so I included a grouped max value and and overall max value with a slight functional approach.
Please be aware that some functionalities are not working in older browsers i.e. flatMap. This should anyways help you get started and move on.
const data = {
name: "KSE100",
children: [
{
name: "TECHNOLOGY & COMMUNICATION",
children: [
{
name: "TRG",
points: -21,
},
{
name: "SYS",
},
],
},
{
name: "OIL",
children: [
{
name: "PPL",
points: 9,
},
{
name: "PSO",
points: -19,
},
],
},
],
};
const maxPointsByGroup = data.children.reduce(
(acc, entry) => [
...acc,
{
name: entry.name,
max: Math.max(
...entry.children
.map((entry) => entry.points)
.filter((entry) => typeof entry === "number")
),
},
],
[]
);
console.log("grouped max:", maxPointsByGroup);
const overallMax = Math.max(
...data.children
.flatMap((entry) => entry.children.flatMap((entry) => entry.points))
.filter((entry) => typeof entry === "number")
);
console.log("overall max:", overallMax);

Filter on two arrays same time?

I have two arrays:
const array1 = [{
"id": "4521",
"name": "Tiruchirapalli",
"stateId": "101"
},
{
"id": "1850",
"name": "Tenkasi",
"stateId": "101"
},
{
"id": "202",
"name": "Thanjavur",
"stateId": "101"
},
{
"id": "505",
"name": "Ernakulam",
"stateId": "102"
},
];
And now array2
const array2 = [{
"id": 1850,
"cityName": "Tenkasi",
"aliasNames": [
"Thenkasi"
]
},
{
"id": 4521,
"cityName": "Tiruchirapalli",
"aliasNames": [
"Trichy"
]
},
{
"id": 202,
"cityName": "Thanjavur",
"aliasNames": [
"Tanjore"
]
},
{
"id": 505,
"cityName": "Ernakulam",
"aliasNames": [
"Kochi",
"Cochin"
]
},
];
what i need to do is, how to filter both the arrays at same time ( or filter first one and then second which ever one is performance effective ).
For instance, when user types "Kochi", first it should check on array1 to find if its has name="Kochi", if it has then we can set the state with that and if it doesnt have we need to find it on array2 and the update the state !
Which is fast and effective way to handle this - ( array1 has 2500 records and array2 has 990 records ) so performance / speed is also a concern
My attempt:
searchFilterFunction = text => {
this.setState({ typedText: text });
const newData = array1.filter(item => {
const itemData = `${item.name.toUpperCase()}`;
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
this.setState({ data: newData});
};
How to implement the second filter in optimized way ?
For instance, when user types "Kochi", first it should check on array1
to find if its has name="Kochi", if it has then we can set the state
with that and if it doesnt have we need to find it on array2 and the
update the state !
I would do something like this with Array.find.
if( array1.find(item=>item.name.toUpperCase() === text) ) {
// set state
} else if( array2.find(item=>item.cityName.toUpperCase() === text) ) {
// set state
}
A refined form would be
let result = array1.find(item=>item.name.toUpperCase() === text);
// check in array 2 as we cannot find in array 1
if(!result) {
result = array2.find(item=>{
// check in aliasNames and in cityName
return item.cityName.toUpperCase() === text || item.aliasNames.includes(text);
}
);
}
if(result) {
setState(result);
} else {
// place not found
}
Regarding the performance based on your array count you will not see much difference. If you want to save some milliseconds you can check the array with least count first as mentioned in one of the comments. But the time also varies based on were the element is in array.
I think this is the most optimal solution because nesting the two filter won't work as you need to filter from first array and then second.
const array1 = [{
"id": "4521",
"name": "Tiruchirapalli",
"stateId": "101"
},
{
"id": "1850",
"name": "Tenkasi",
"stateId": "101"
},
{
"id": "202",
"name": "Thanjavur",
"stateId": "101"
},
{
"id": "505",
"name": "Ernakulam",
"stateId": "102"
},
];
const array2 = [{ "id": 1850, "cityName": "Tenkasi",
"aliasNames": [
"Thenkasi"
]
},{"id": 4521,"cityName": "Tiruchirapalli",
"aliasNames": [
"Trichy"
]
},
{
"id": 202,
"cityName": "Thanjavur",
"aliasNames": [
"Tanjore"
]
},
{
"id": 505,
"cityName": "Ernakulam",
"aliasNames": [
"Kochi",
"Cochin"
]
},
];
function filter(text) {
// Complexity Linear
const filter_array = array1.filter((a) => {
return (a.name === text)
});
if (filter_array.length > 0) {
//Set State and return
}
//Complexity Linear and includes complexity Linear O(sq(m*n)) where n is //the aliasName record
const filter_array2 = array2.filter((a) => {
return a.cityName === text || a.aliasNames.includes(text);
});
return filter_array2 //Set State filter array 2
}
console.log(filter("Kochi"));

How to check if user was already with that property?

I have this example array:
[
{
"id": 1,
"name": "Bob",
},
{
"id": 2,
"name": "John",
},
{
"id": 3,
"name": "Bob",
},
{
"id": 4,
"name": "Bob",
},
{
"id": 5,
"name": "Bob",
}
]
In this array I want to add a property color to each object from array with an Loop, but if User already has that property color assigned don't add any other color KEEP the first one assigned.
What it looks like now is adding random color to user, and can't check if user have color RED already and doesn't need another one (needs to KEEP the FIRST ONE - RED)
What I need to achieve is:
Whenever the user exists - add ONLY ONE color to it, and for other users same thing UNIQUE color for EACH USER in part.
const myArr = [
{
"id": 1,
"name": "Bob",
},
{
"id": 2,
"name": "John",
},
{
"id": 3,
"name": "Bob",
},
{
"id": 4,
"name": "Bob",
},
{
"id": 5,
"name": "Bob",
}
];
for (var i = 0, len = myArr.length; i < len; ++i) {
var colors = ["#A43548", "#35A4A0", "#55B747", "#1F85DE"];
var random = Math.floor((Math.random() * colors.length))
var checker = myArr.hasOwnProperty("avatarcolor")
if(checker == false) {
myArr[i].avatarcolor = colors[random]; // add color
} else {
console.log(`Already we have color to that user!`)
}
}
console.log(myArr)
Idea is when Loop assigned color RED to Bob, I don't want to assign
any more colors to it ONLY that color RED
Use array reduce & inside callback check use find. In find callback check if there exist an array where name matches and it also have the property. find will return undefined if it does not find that object. In that case add the new property to the object
const myArr = [{
id: 1,
name: "Bob"
},
{
id: 2,
name: "John"
},
{
id: 3,
name: "Bob"
}
];
var colors = ["#A43548", "#35A4A0", "#55B747", "#1F85DE"];
myArr.reduce((acc, curr) => {
const x = acc.find(
item => item.name === curr.name && item.hasOwnProperty("avatarcolor")
);
if (!x) {
curr["avatarcolor"] = colors[Math.floor(Math.random() * colors.length)];
} else {
curr["avatarcolor"] = x.avatarcolor;
}
acc.push(curr);
return acc;
}, []);
console.log(myArr);

Convert string value to variable reference in javascript

I have the following code where I'm attempting to reference the values of one JSON object with the variables of another:
const ch = {
"columns": {
"COL1": {
"position": 1,
"composites": ["VAR1", "VAR3"]
},
"COL2": {
"position": 3,
"composites": ["VAR2"]
},
"COL3": {
"position": 2,
"composites": ["VAR4"]
}
}
}
const dataset = [{
"VAR1": "alpha",
"VAR2": 2,
"VAR3": "1015",
"VAR4": "z",
},
{
"VAR1": "beta",
"VAR2": 701,
"VAR3": "1023",
"VAR4": "z"
}
]
for (let l = 0; l < dataset.length; l++) {
for (const {
position,
composites
} of Object.values(ch.columns).sort((a, b) => a.position - b.position)) {
console.log(position, composites[0], dataset[l].VAR1)
/* eval[dataset[l].composites[0]], this[dataset[l].composites[0]]*/
}
}
The program correctly orders the columns and I can refer both values from 'ch', but I would like to use the first composites value as a variable reference to the dataset. Having googled the question I followed a couple of recommendations to either use 'this' or 'eval', but neither work. Where am I going wrong?
Ideally, if I could get the commented out code working the log should look like the following:
1 VAR1 alpha alpha
2 VAR4 alpha z
3 VAR2 alpha 2
1 VAR1 beta beta
2 VAR4 beta z
3 VAR2 beta 701
Use dataset[l][composites[0]] to get the additional column. See Dynamically access object property using variable
const ch = {
"columns": {
"COL1": {
"position": 1,
"composites": ["VAR1", "VAR3"]
},
"COL2": {
"position": 3,
"composites": ["VAR2"]
},
"COL3": {
"position": 2,
"composites": ["VAR4"]
}
}
}
const dataset = [{
"VAR1": "alpha",
"VAR2": 2,
"VAR3": "1015",
"VAR4": "z",
},
{
"VAR1": "beta",
"VAR2": 701,
"VAR3": "1023",
"VAR4": "z"
}
]
for (let l = 0; l < dataset.length; l++) {
for (const {
position,
composites
} of Object.values(ch.columns).sort((a, b) => a.position - b.position)) {
console.log(position, composites[0], dataset[l].VAR1, dataset[l][composites[0]])
}
}

Javascript - Get occurence of json array with ESLINT 6

I can't set up an algo that counts my occurrences while respecting ESlint's 6 standards in javascript.
My input table is :
[
{
"id": 2,
"name": "Health",
"color": "0190fe"
},
{
"id": 3,
"name": "Agriculture",
"color": "0190fe"
},
{
"id": 1,
"name": "Urban planning",
"color": "0190fe"
},
{
"id": 1,
"name": "Urban planning",
"color": "0190fe"
}
]
And i want to get :
{"Urban planning": 2, "Health": 1, ...}
But that does not work with ESLINT / REACT compilation...
This is my code :
const jsonToIterate = *'MyPreviousInputJson'*
const names = []
jsonToIterate.map(item => (names.push(item.name)))
const count = []
names.forEach(item => {
if (count[item]){
count.push({text: item, value: 1})
} else {
count.forEach(function(top){top.text === item ? top.value =+ 1 : null})
}
})
Thank you so much
Well, you want an object in the end, not an array, so count should be {}. I also wouldn't use map if you're not actually returning anything from the call. You can use reduce for this:
let counts = topicsSort.reduce((p, c, i, a) => {
if (!p.hasOwnProperty(c.name)) p[c.name] = 0;
p[c.name]++;
return p;
}, {});
I'm half exppecting someone to close this as a duplicate because all you've asked for is a frequency counter. But here's an answer anyway:
const jsonToIterate = *'MyPreviousInputJson'*;
const names = {};
jsonToIterate.map(obj => {
if(obj.name in names){
names[obj.name]++
}
else{
names[obj.name] = 1;
}
})

Categories

Resources