Missing data should be on the next array - javascript

I am making a matchmaking system where 2 players with the same level will be joined in 1 array. However, when there are 4 players with the same level, the other 2 players are disappearing. My target is to show those 2 players with the same level in another array. I provided an image below and my codes. Any help will be appreciated.
const source = [{
id: 1,
name: 'player1',
level: 1
},
{
id: 2,
name: 'player2',
level: 1
},
{
id: 3,
name: 'player3',
level: 2
},
{
id: 4,
name: 'player4',
level: 2
},
{
id: 5,
name: 'player5',
level: 3
},
{
id: 6,
name: 'player6',
level: 3
},
{ // this data is missing
id: 7,
name: 'player7',
level: 1
},
{ // this data is missing
id: 8,
name: 'player8',
level: 1
},
]
const combine = (source) => {
return source.reduce((acc, curr) => {
if (acc[curr.level] && acc[curr.level].length > 1)
return acc;
if (acc[curr.level])
acc[curr.level].push(curr);
else
acc[curr.level] = [curr];
return acc;
}, {})
}
var result = combine(source)
var html = ""
var keys = Object.keys(result) //if there more then one keys i.e : 2..
for (var i = 0; i < keys.length; i++) {
console.log("Keys " + keys[i])
//loop through json array
result[keys[i]].forEach(function (val, index) {
//check if index value is `0`..change name.
var ids = index == 0 ? "id[]" : "idside[]"
var name = index == 0 ? "name[]" : "nameside[]"
var levels = index == 0 ? "level[]" : "levelside[]"
html += `<input type="text" name="${ids}" value="${val.id}">
<input type="text" name="${name}" value="${val.name}">
<input type="text" name="${levels}" value="${val.level}">`
})
}
document.getElementById("result").innerHTML = html //add html to div
console.log(result);
<div id="result">
</div>

Problem is on these two lines
if (acc[curr.level] && acc[curr.level].length > 1)
return acc;
If there already is level 1 in acc and has two or more players, you just skip all other level 1 players.
My solution, even though probably bit slower, would be to sort and group the input data based on level and then push pairs into an array.

Target achieved. Super thanks to: Swati. Also, thank you for your suggestions.
const source = [{
id: 1,
name: 'player1',
level: 1
},
{
id: 2,
name: 'player2',
level: 1
},
{
id: 3,
name: 'player3',
level: 2
},
{
id: 4,
name: 'player4',
level: 2
},
{
id: 5,
name: 'player5',
level: 3
},
{
id: 6,
name: 'player6',
level: 3
},
{ // this data is missing
id: 7,
name: 'player7',
level: 1
},
{ // this data is missing
id: 8,
name: 'player8',
level: 1
},
]
const combine = (source) => {
return source.reduce((acc, curr) => {
if (acc[curr.level]) {
const levelArr = acc[curr.level];
const last = levelArr[levelArr.length - 1];
if (last.length === 2) {
levelArr.push([curr])
} else {
last.push(curr);
}
} else {
acc[curr.level] = [
[curr]
];
}
return acc;
}, {})
};
var result = combine(source)
var html = ""
var keys = Object.keys(result) //if there more then one keys i.e : 2..
for (var i = 0; i < keys.length; i++) {
result[keys[i]].forEach(function(val) {
val.forEach(function(value, index) {
var ids = index == 0 ? "id[]" : "idside[]"
var name = index == 0 ? "name[]" : "nameside[]"
var levels = index == 0 ? "level[]" : "levelside[]"
html += `<input type="text" name="${ids}" value="${value.id}"> <input type="text" name="${name}" value="${value.name}">
<input type="text" name="${levels}" value="${value.level}">`
})
})
}
document.getElementById("result").innerHTML = html //add html to div
<div id="result">
</div>

Related

How to get unique element from two arrays in js?

I'm trying to get unique (by id) values from two arrays.
But it returns whole array instead of { id: 3 }
const a = [{ id: 1 }, { id: 2 }];
const b = [{ id: 1 }, { id: 2 }, { id: 3 }];
const array3 = b.filter((obj) => a.indexOf(obj) == -1);
console.log(array3);
What's wrong here?
You cannot compare objects you should check that an element with that id doesn't exists in the other array
here I used some that returns a boolean if he can find a match
const a = [{
id: 1
}, {
id: 2
}];
const b = [{
id: 1
}, {
id: 2
}, {
id: 3
}];
const array3 = b.filter(obj => !a.some(({id}) => obj.id === id));
console.log(array3)
In your case, the following code gives all unique objects as an array, based on the id.
const a = [{
id: 1
}, {
id: 2
}];
const b = [{
id: 1
}, {
id: 2
}, {
id: 3
}];
const array3 = b.filter(objB => a.some((objA) => objB.id !== objA.id));
console.log(array3)
A different approach with a symmetrically result.
const
take = m => d => o => m.set(o.id, (m.get(o.id) || 0) + d),
a = [{ id: 1 }, { id: 2 }],
b = [{ id: 1 }, { id: 2 }, { id: 3 }],
map = new Map(),
add = take(map),
result = [];
a.forEach(add(1));
b.forEach(add(-1));
map.forEach((v, id) => v && result.push({ id }));
console.log(result);

Return similar values ​from multiple array of objects in Javascript

Guys I made a simple example to illustrate my problem. I have 3 object arrays, datasOne, datasTwo and datasThree and what I want is to return a new array only with the objects that are in the 3 arrays. For example, if there is only Gustavo in the 3 arrays, then he will be returned. But there is a detail that if the datasThree is an empty array, then it will bring the data in common only from datasOne and datasTwo and if only the datasTwo which has data and the other two arrays have empty, then it will return data only from datasTwo. In other words it is to return similar data only from arrays that have data. I managed to do this algorithm and it works the way I want, but I would like to know another way to make it less verbose and maybe simpler and also work in case I add more arrays to compare like a dataFour for example. I appreciate anyone who can help me.
My code below:
let datasOne = [
{ id: 1, name: 'Gustavo' },
{ id: 2, name: 'Ana' },
{ id: 3, name: 'Luiz' },
{ id: 8, name: 'Alice' }
]
let datasTwo = [
{ id: 1, name: 'Gustavo' },
{ id: 3, name: 'Luiz' },
{ id: 8, name: 'Alice' }
]
let datasThree = [
{ id: 1, name: 'Gustavo' },
{ id: 3, name: 'Luiz' },
{ id: 2, name: 'Ana' },
{ id: 5, name: 'Kelly' },
{ id: 4, name: 'David' }
]
let filtered
if (datasOne.length > 0 && datasTwo.length > 0 && datasThree.length > 0) {
filtered = datasOne.filter(firstData => {
let f1 = datasThree.filter(
secondData => firstData.id === secondData.id
).length
let f2 = datasTwo.filter(
secondData => firstData.id === secondData.id
).length
if (f1 && f2) {
return true
}
})
} else if (datasOne.length > 0 && datasTwo.length > 0) {
filtered = datasOne.filter(firstData => {
return datasTwo.filter(secondData => firstData.id === secondData.id).length
})
} else if (datasOne.length > 0 && datasThree.length > 0) {
filtered = datasOne.filter(firstData => {
return datasThree.filter(secondData => firstData.id === secondData.id)
.length
})
} else if (datasTwo.length > 0 && datasThree.length > 0) {
filtered = datasTwo.filter(firstData => {
return datasThree.filter(secondData => firstData.id === secondData.id)
.length
})
} else if (datasThree.length > 0) {
filtered = datasThree
} else if (datasTwo.length > 0) {
filtered = datasTwo
} else if (datasOne.length) {
filtered = datasOne
}
console.log(filtered)
1) You can first filter the array which is not empty in arrs.
const arrs = [datasOne, datasTwo, datasThree].filter((a) => a.length);
2) Flatten the arrs array using flat().
arrs.flat()
3) Loop over the flatten array and count the occurrence of all objects using Map
const map = new Map();
for (let o of arrs.flat()) {
map.has(o.id)
? (map.get(o.id).count += 1)
: map.set(o.id, { ...o, count: 1 });
}
4) Loop over the map and collect the result only if it is equal to arrs.length
if (count === arrs.length) result.push(rest);
let datasOne = [
{ id: 1, name: "Gustavo" },
{ id: 2, name: "Ana" },
{ id: 3, name: "Luiz" },
{ id: 8, name: "Alice" },
];
let datasTwo = [
{ id: 1, name: "Gustavo" },
{ id: 3, name: "Luiz" },
{ id: 8, name: "Alice" },
];
let datasThree = [
{ id: 1, name: "Gustavo" },
{ id: 3, name: "Luiz" },
{ id: 2, name: "Ana" },
{ id: 5, name: "Kelly" },
{ id: 4, name: "David" },
];
const arrs = [datasOne, datasTwo, datasThree].filter((a) => a.length);
const map = new Map();
for (let o of arrs.flat()) {
map.has(o.id)
? (map.get(o.id).count += 1)
: map.set(o.id, { ...o, count: 1 });
}
const result = [];
for (let [, obj] of map) {
const { count, ...rest } = obj;
if (count === arrs.length) result.push(rest);
}
console.log(result);
/* This is not a part of answer. It is just to give the output fill height. So IGNORE IT */
.as-console-wrapper { max-height: 100% !important; top: 0; }
Not 100% sure it cover all edge cases, but this might get you on the right track:
function filterArrays(...args) {
const arraysWithData = args.filter((array) => array.length > 0);
const [firstArray, ...otherArrays] = arraysWithData;
return firstArray.filter((item) => {
for (const array of otherArrays) {
if (!array.some((itemTwo) => itemTwo.id === item.id)) {
return false;
}
}
return true;
});
}
Usage:
const filtered = filterArrays(datasOne, datasTwo, datasThree);
console.log(filtered)
I believe the code is fairly readable, but if something is not clear I'm glad to clarify.
function merge(arr){
arr = arr.filter(item=>item.length>0)
const map = {};
arr.forEach(item=>{
item.forEach(obj=>{
if(!map[obj.id]){
map[obj.id]=[0,obj];
}
map[obj.id][0]++;
})
})
const len = arr.length;
const ret = [];
Object.keys(map).forEach(item=>{
if(map[item][0]===len){
ret.push(map[item][1])
}
})
return ret;
}
merge([datasOne,datasTwo,datasThree])

limit the array length by 2 data only

I am currently making a matchmaking system. My target is to limit the array length by 2 data only. In my current work, when I add another data with the same level, it becomes 3 data in 1 array (Please see pic 1). When there's 3 data in 1 array, send the extra 1 data to another array until it gets a player to match with. The matching works when players have the same level. I have provided my codes below and a screenshot. Any help will be appreciated. Thank you.
const source = [{
id: 1,
name: 'player1',
level: 1
},
{
id: 2,
name: 'player2',
level: 1
},
{
id: 3,
name: 'player3',
level: 2
},
{
id: 4,
name: 'player4',
level: 2
},
{
id: 5,
name: 'player5',
level: 1
},
{
id: 6,
name: 'player6',
level: 3
},
{
id: 7,
name: 'player7',
level: 3
},
]
const combine = (source) => {
return source.reduce((acc, curr) => {
if (acc[curr.level])
acc[curr.level].push(curr);
else
acc[curr.level] = [curr];
return acc;
}, {})
}
var result = combine(source)
var html = ""
var keys = Object.keys(result) //if there more then one keys i.e : 2..
for (var i = 0; i < keys.length; i++) {
console.log("Keys " + keys[i])
//loop through json array
result[keys[i]].forEach(function(val, index) {
//check if index value is `0`..change name.
var ids = index == 0 ? "id[]" : "idside[]"
var name = index == 0 ? "name[]" : "nameside[]"
var levels = index == 0 ? "level[]" : "levelside[]"
html += `<input type="text" name="${ids}" value="${val.id}">
<input type="text" name="${name}" value="${val.name}">
<input type="text" name="${levels}" value="${val.level}">`
})
}
document.getElementById("result").innerHTML = html //add html to div
console.log(result);
<div id="result">
</div>
You can accumulate into a 2d array by level and check if the currently accumulated array length is 2.
const
source = [{ id: 1, name: 'player1', level: 1 }, { id: 2, name: 'player2', level: 1 }, { id: 3, name: 'player3', level: 2 }, { id: 4, name: 'player4', level: 2 }, { id: 5, name: 'player5', level: 1 }, { id: 6, name: 'player6', level: 3 }, { id: 7, name: 'player7', level: 3 },];
const combine = (source) => {
return source.reduce((acc, curr) => {
if (acc[curr.level]) {
const levelArr = acc[curr.level];
const last = levelArr[levelArr.length - 1];
if (last.length === 2) {
levelArr.push([curr])
} else {
last.push(curr);
}
}
else {
acc[curr.level] = [[curr]];
}
return acc;
}, {})
};
const result = combine(source)
// HTML generation
const matchesUl = document.createElement('ul');
Object.entries(result).forEach(([key, level]) => {
const levelLi = document.createElement('li');
const title = document.createTextNode(`Level ${key}`);
levelLi.appendChild(title);
const levelUl = document.createElement('ul');
levelLi.appendChild(levelUl);
level.forEach(match => {
const [p1, p2] = match;
const matchLi = document.createElement('li');
matchLi.textContent = `${p1.name} vs. ${p2?.name ?? '-'}`;
levelUl.appendChild(matchLi);
})
matchesUl.appendChild(levelLi);
})
document.getElementById("result").appendChild(matchesUl); //add html to div
// Debug
const pre = document.createElement('pre')
pre.innerHTML = JSON.stringify(result, null, 2);
document.getElementById('debug').appendChild(pre);
#debug {
border: 1px solid darkgray;
background-color: lightgray;
padding: 1rem;
margin-top: 1rem;
}
<div id="result">
</div>
<div id="debug">
<p>Output</p>
</div>
If you only want complete combinations you can track individual players in an unmatched object and only add them to their applicable level array when a match is found. You will still end up with a 2D array for each level, but all sub-arrays will be matched pairs.
const
source = [{ id: 1, name: 'player1', level: 1 }, { id: 2, name: 'player2', level: 1 }, { id: 3, name: 'player3', level: 2 }, { id: 4, name: 'player4', level: 2 }, { id: 5, name: 'player5', level: 1 }, { id: 6, name: 'player6', level: 3 }, { id: 7, name: 'player7', level: 3 }, { id: 8, name: 'player8', level: 3 }];
const combine = (source) => {
return source.reduce((acc, curr) => {
if (acc[curr.level]) {
if (acc.unmatched[curr.level]) {
acc[curr.level].push([acc.unmatched[curr.level], curr])
acc.unmatched[curr.level] = null;
} else {
acc.unmatched[curr.level] = curr;
}
}
else {
acc[curr.level] = [];
acc.unmatched[curr.level] = curr;
}
return acc;
}, { unmatched: {} })
};
const result = combine(source)
// HTML Generation
const matchesUl = document.createElement('ul');
Object.entries(result).forEach(([key, level]) => {
if (key !== 'unmatched') {
const levelLi = document.createElement('li');
const title = document.createTextNode(`Level ${key}`);
levelLi.appendChild(title);
const levelUl = document.createElement('ul');
levelLi.appendChild(levelUl);
level.forEach(match => {
const [p1, p2] = match;
const matchLi = document.createElement('li');
matchLi.textContent = `${p1.name} vs. ${p2?.name ?? '-'}`;
levelUl.appendChild(matchLi);
});
matchesUl.appendChild(levelLi);
}
});
const unmatchedLi = document.createElement('li');
const title = document.createTextNode('Unmatched');
unmatchedLi.appendChild(title);
const unmatchedUl = document.createElement('ul');
unmatchedLi.appendChild(unmatchedUl);
Object.values(result.unmatched).forEach(player => {
if (player) {
const li = document.createElement('li');
li.textContent = `${player.name} (lvl ${player.level})`;
unmatchedUl.appendChild(li);
}
});
matchesUl.appendChild(unmatchedLi)
document.getElementById("result").appendChild(matchesUl); //add html to div
// Debug
const pre = document.createElement('pre')
pre.innerHTML = JSON.stringify(result, null, 2);
document.getElementById('debug').appendChild(pre);
#debug {
border: 1px solid darkgray;
background-color: lightgray;
padding: 1rem;
margin-top: 1rem;
}
<div id="result">
</div>
<div id="debug">
<p>Output</p>
</div>
You can check acc[curr.level].length > 1 in combine method as below to check if there are more than two player in one level, ignore that:
const source = [{
id: 1,
name: 'player1',
level: 1
},
{
id: 2,
name: 'player2',
level: 1
},
{
id: 3,
name: 'player3',
level: 2
},
{
id: 4,
name: 'player4',
level: 2
},
{
id: 5,
name: 'player5',
level: 1
},
{
id: 6,
name: 'player6',
level: 3
},
{
id: 7,
name: 'player7',
level: 3
},
]
const combine = (source) => {
return source.reduce((acc, curr) => {
if (acc[curr.level] && acc[curr.level].length > 1)
return acc;
if (acc[curr.level])
acc[curr.level].push(curr);
else
acc[curr.level] = [curr];
return acc;
}, {})
}
var result = combine(source)
var html = ""
var keys = Object.keys(result) //if there more then one keys i.e : 2..
for (var i = 0; i < keys.length; i++) {
console.log("Keys " + keys[i])
//loop through json array
result[keys[i]].forEach(function (val, index) {
//check if index value is `0`..change name.
var ids = index == 0 ? "id[]" : "idside[]"
var name = index == 0 ? "name[]" : "nameside[]"
var levels = index == 0 ? "level[]" : "levelside[]"
html += `<input type="text" name="${ids}" value="${val.id}">
<input type="text" name="${name}" value="${val.name}">
<input type="text" name="${levels}" value="${val.level}">`
})
}
document.getElementById("result").innerHTML = html //add html to div
console.log(result);
<div id="result">
</div>

Efficient way to get top count of an option in an array of objects

I have an array of objects like this,
[
{
user: 'A',
answers: [
{
id: 1,
score: 3,
},
{
id: 2,
score: 1,
},
...
]
},
{
user: 'B',
answers: [
...
where I have 200 users, each user answers a set of 40 questions, each question has an id and a score.
What I'm trying to do is add up each question's score. So that I can figure out which question has the highest score, which has the lowest. Aka, top question and bottom question.
What would be the best way to do this?
The current way I am doing feels a little long-winded.
const allAns = []
myList.forEach( user => allAns.push( ...user.answers ) )
const questionsScored = allAns.reduce( ( obj, cur ) => {
!obj[ cur.id ] ? obj[ cur.id ] = cur.score : obj[ cur.id ] += cur.score
return obj
}, {} )
const sortingList = []
for ( const qn in questionsScored ) {
sortingList.push( [ qn, questionsScored[ qn ] ] )
}
sortingList.sort( ( a, b ) => b[ 1 ] - a[ 1 ] )
console.log( sortingList[ 0 ], sortingList[ sortingList.length - 1 ] )
You're taking all the steps necessary so if it's working it's fine though you could replace some of your forEach() loops with available methods:
with .flatMap()
const allAns = myList.flatMap(({answers})=>answers);
and using Object.entries()
const sortingList = Object.entries(questionsScored);
const
input = [{ user: 'A', answers: [{ id: 1, score: 3, }, { id: 2, score: 1, }, { id: 3, score: 0, }] }, { user: 'B', answers: [{ id: 1, score: 2, }, { id: 2, score: 1, }, { id: 3, score: 0, }] },],
allAns = input.flatMap(({ answers }) => answers),
questionsScored = allAns.reduce((obj, cur) => {
!obj[cur.id] ? obj[cur.id] = cur.score : obj[cur.id] += cur.score
return obj
}, {}),
sortingList = Object.entries(questionsScored).sort((a, b) => b[1] - a[1]);
console.log({ max: sortingList[0], min: sortingList[sortingList.length - 1] })
Or combined into a single chained call, but it's not necessarily better.
const
input = [{ user: 'A', answers: [{ id: 1, score: 3, }, { id: 2, score: 1, }, { id: 3, score: 0, }] }, { user: 'B', answers: [{ id: 1, score: 2, }, { id: 2, score: 1, }, { id: 3, score: 0, }] },],
sortingList = Object
.entries(
input
.flatMap(({ answers }) => answers)
.reduce((obj, cur) => {
!obj[cur.id] ? obj[cur.id] = cur.score : obj[cur.id] += cur.score
return obj
}, {})
)
.sort((a, b) => b[1] - a[1]);
console.log({ max: sortingList[0], min: sortingList[sortingList.length - 1] })
If you would like to avoid the sort() call you can instead collect the low and high counts using a forEach() after the initial reduce()
const
input = [{ user: 'A', answers: [{ id: 1, score: 3, }, { id: 2, score: 1, }, { id: 3, score: 0, }] }, { user: 'B', answers: [{ id: 1, score: 2, }, { id: 2, score: 1, }, { id: 3, score: 0, }] },],
lowScore = { count: Infinity },
highScore = { count: -Infinity };
Object
.entries(
input
.flatMap(({ answers }) => answers)
.reduce((obj, cur) => {
!obj[cur.id] ? obj[cur.id] = cur.score : obj[cur.id] += cur.score
return obj
}, {})
)
.forEach(([id, count]) => {
// update low count
if (count < lowScore.count) {
lowScore.count = count;
lowScore.id = id;
}
// update high count
if (count > highScore.count) {
highScore.count = count;
highScore.id = id;
}
});
console.log({ lowScore, highScore })
// sample data
let data = [{
user: 'A',
answers: [{
id: 1,
score: 1,
},
{
id: 2,
score: 2,
},
{
id: 3,
score: 3,
},
{
id: 4,
score: 4,
},
]
},
{
user: 'B',
answers: [{
id: 1,
score: 1,
},
{
id: 2,
score: 2,
},
{
id: 3,
score: 3,
},
{
id: 4,
score: 4,
},
]
},
]
let scoreSum = []; //scoreSum to store total score of each question
let initialValue = 0;
for (let i = 0; i < 4; i++) {
let sum = data.reduce(function (accumulator, currentValue) {
return accumulator + currentValue.answers[i].score;
}, initialValue)
scoreSum.push(sum);
}
let highestScore = Math.max(...scoreSum);
let lowestScore = Math.min(...scoreSum);
// increasing index by 1 to match with question numbers
let highestScoreIndex = scoreSum.indexOf(highestScore) + 1;
let lowestScoreIndex = scoreSum.indexOf(lowestScore) + 1;
// Array.prototype.getDuplicates returns an object where the keys are the duplicate entries
// and the values are an array with their indices.
Array.prototype.getDuplicates = function () {
var duplicates = {};
for (var i = 0; i < this.length; i++) {
if (duplicates.hasOwnProperty(this[i])) {
duplicates[this[i]].push(i);
} else if (this.lastIndexOf(this[i]) !== i) {
duplicates[this[i]] = [i];
}
}
return duplicates;
};
let sameScore = scoreSum.getDuplicates();
// checking if highest score has duplicates
// and if so then updaing highest score index
//with highest score indices
if (sameScore.hasOwnProperty(highestScore)) {
highestScoreIndex = sameScore[highestScore].map((a) => a + 1);
}
// checking if lowest score has duplicates
// and if so then updaing lowest score index
//with lowest score indices
if (sameScore.hasOwnProperty(lowestScore)) {
lowestScoreIndex = sameScore[lowestScore].map((a) => a + 1);
}
console.log(`Top question no(s): ${highestScoreIndex} highest score:${highestScore}`);
console.log(`bottom question no(s): ${lowestScoreIndex} lowest score:${lowestScore}`);
I only loop once through each answer in .answers for each user using nested reduce.
The input array got three users with each three answers.
let input = [{ user: 'A', answers: [{ id: 1, score: 2, }, { id: 2, score: 1, }, { id: 3, score: 0, }] }, { user: 'B', answers: [{ id: 1, score: 2, }, { id: 2, score: 4, }, { id: 3, score: 0, }] }, { user: 'c', answers: [{ id: 1, score: 0, }, { id: 2, score: 3, }, { id: 3, score:5, }] }]
function showBestAndWorstFrom(input) {
let highestScore = {'id': 0, 'score': -Infinity};
let lowestScore = {'id': 0, 'score': Infinity};
let currentScore = 0;
let id = 0;
const LAST_USER = input.length - 1;
let answers = input.reduce((combinedObj, user, userIndex) => {
return user.answers.reduce((_answerObj, _answer) => {
id = _answer.id
currentScore = (_answerObj[id] || 0) + _answer.score;
_answerObj[id] = currentScore;
if (userIndex == LAST_USER) {
highestScore = (highestScore.score < currentScore) ? {'id': id, 'score': currentScore } : highestScore;
lowestScore = (lowestScore.score > currentScore) ? {'id': id, 'score': currentScore } : lowestScore;
}
return _answerObj;
}, combinedObj);
}, {});
// console.log(answers); // { "1": 4, "2": 8, "3": 5 }
return {highestScore, lowestScore};
}
console.log(showBestAndWorstFrom(input))

How to sum value in javascript array object form specific search id?

I have javascript array object as below. My need is to sum value base on seach id in the array object.
var array = [
{ id: 1, val: 10 },
{ id: 2, val: 25 },
{ id: 3, val: 20 },
{ id: 1, val: 30 },
{ id: 1, val: 25 },
{ id: 2, val: 10 },
{ id: 1, val: 20 }
],
For example sum of value for id 1 is 10 + 30 + 25 + 20 = 85 , It may be something link linq but I'm not sure in javascript. Thanks for all answers.
You can use a combination of filter and reduce to get the result you want:
sumOfId = (id) => array.filter(i => i.id === id).reduce((a, b) => a + b.val, 0);
Usage:
const sumOf1 = sumOfId(1); //85
Reading material:
Array.prototype.filter
Array.prototype.reduce
A way to do it with a traditional for loop
var array = [
{ id: 1, val: 10 },
{ id: 2, val: 25 },
{ id: 3, val: 20 },
{ id: 1, val: 30 },
{ id: 1, val: 25 },
{ id: 2, val: 10 },
{ id: 1, val: 20 }
];
var sums = {};
for (var i = 0; i < array.length; i++) {
var obj = array[i];
sums[obj.id] = sums[obj.id] === undefined ? 0 : sums[obj.id];
sums[obj.id] += parseInt(obj.val);
}
console.log(sums);
running example
You can use reduce() and findIndex()
var array = [
{ id: 1, val: 10 },
{ id: 2, val: 25 },
{ id: 3, val: 20 },
{ id: 1, val: 30 },
{ id: 1, val: 25 },
{ id: 2, val: 10 },
{ id: 1, val: 20 }
];
let res = array.reduce((ac,a) => {
let ind = ac.findIndex(x => x.id === a.id);
ind === -1 ? ac.push(a) : ac[ind].val += a.val;
return ac;
},[])
console.log(res);
JS noob here ... I guess something like this should be here too :-)
let newArray = {}
array.forEach((e) => {
!newArray[e.id] && (newArray[e.id] = 0);
newArray[e.id] += e.val;
});
You can loop on the array and check the ids.
var array = [
{ id: 1, val: 10 },
{ id: 2, val: 25 },
{ id: 3, val: 20 },
{ id: 1, val: 30 },
{ id: 1, val: 25 },
{ id: 2, val: 10 },
{ id: 1, val: 20 }
];
var sum = 0;
var id = 1;
$.each(array, function(index, object){
if (object.id == id) {
sum += object.val;
}
});
console.log(sum);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Using Array#reduce and Map you can get the sum for each id like so. This also uses destructuring to have quicker access to properties.
const data=[{id:1,val:10},{id:2,val:25},{id:3,val:20},{id:1,val:30},{id:1,val:25},{id:2,val:10},{id:1,val:20}];
const res = data.reduce((a,{id,val})=>{
return a.set(id, (a.get(id)||0) + val);
}, new Map())
console.log(res.get(1));
console.log(res.get(2));
If you wanted to output all the sums, then you need to use Array#from
const data=[{id:1,val:10},{id:2,val:25},{id:3,val:20},{id:1,val:30},{id:1,val:25},{id:2,val:10},{id:1,val:20}];
const res = Array.from(
data.reduce((a,{id,val})=>{
return a.set(id, (a.get(id)||0) + val);
}, new Map())
);
console.log(res);
If the format should be similar as to your original structure, you need to add a Array#map afterwards to transform it.
const data=[{id:1,val:10},{id:2,val:25},{id:3,val:20},{id:1,val:30},{id:1,val:25},{id:2,val:10},{id:1,val:20}];
const res = Array.from(
data.reduce((a,{id,val})=>{
return a.set(id, (a.get(id)||0) + val);
}, new Map())
).map(([id,sum])=>({id,sum}));
console.log(res);
You could take GroupBy from linq.js with a summing function.
var array = [{ id: 1, val: 10 }, { id: 2, val: 25 }, { id: 3, val: 20 }, { id: 1, val: 30 }, { id: 1, val: 25 }, { id: 2, val: 10 }, { id: 1, val: 20 }],
result = Enumerable
.From(array)
.GroupBy(null, null, "{ id: $.id, sum: $$.Sum('$.val') }", "$.id")
.ToArray();
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/linq.js/2.2.0.2/linq.js"></script>
Here is another option, introducing an Array.prototype.sum helper:
Array.prototype.sum = function (init = 0, fn = obj => obj) {
if (typeof init === 'function') {
fn = init;
init = 0;
}
return this.reduce(
(acc, ...fnArgs) => acc + fn(...fnArgs),
init
);
};
// .sum usage examples
console.log(
// sum simple values
[1, 2, 3].sum(),
// sum simple values with initial value
[1, 2, 3].sum(10),
// sum objects
[{ a: 1 }, { a: 2 }, { a: 3 }].sum(obj => obj.a),
// sum objects with initial value
[{ a: 1 }, { a: 2 }, { a: 3 }].sum(10, obj => obj.a),
// sum custom combinations
[{ amount: 1, price: 2 }, { amount: 3, price: 4 }]
.sum(product => product.amount * product.price)
);
var array = [{ id: 1, val: 10 }, { id: 2, val: 25 }, { id: 3, val: 20 }, { id: 1, val: 30 }, { id: 1, val: 25 }, { id: 2, val: 10 }, { id: 1, val: 20 }];
// solutions
console.log(
array.filter(obj => obj.id === 1).sum(obj => obj.val),
array.filter(({id}) => id === 1).sum(({val}) => val),
array.sum(({id, val}) => id === 1 ? val : 0)
);
references:
Array.prototype.reduce
Array.prototype.filter
Arrow functions used in sum(obj => obj.val)
Object destructing assignment used in ({id}) => id === 1
Rest parameters used in (acc, ...fnArgs) => acc + fn(...fnArgs)
Conditional (ternary) operator used in id === 1 ? val : 0

Categories