limit the array length by 2 data only - javascript

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>

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);

Compare two arrays of objects, and remove if object value is equal

I've tried modifying some of the similar solutions on here but I keep getting stuck, I believe I have part of this figured out however, the main caveat is that:
Some of the objects have extra keys, which renders my object comparison logic useless.
I am trying to compare two arrays of objects. One array is the original array, and the other array contains the items I want deleted from the original array. However there's one extra issue in that the second array contains extra keys, so my comparison logic doesn't work.
An example would make this easier, let's say I have the following two arrays:
const originalArray = [{id: 1, name: "darnell"}, {id: 2, name: "funboi"},
{id: 3, name: "jackson5"}, {id: 4, name: "zelensky"}];
const itemsToBeRemoved = [{id: 2, name: "funboi", extraProperty: "something"},
{id: 4, name: "zelensky", extraProperty: "somethingelse"}];
after running the logic, my final output should be this array:
[{id: 1, name: "darnell"}, {id: 3, name: "jackson5"}]
And here's the current code / logic that I have, which compares but doesn't handle the extra keys. How should I handle this? Thank you in advance.
const prepareArray = (arr) => {
return arr.map((el) => {
if (typeof el === "object" && el !== null) {
return JSON.stringify(el);
} else {
return el;
}
});
};
const convertJSON = (arr) => {
return arr.map((el) => {
return JSON.parse(el);
});
};
const compareArrays = (arr1, arr2) => {
const currentArray = [...prepareArray(arr1)];
const deletedItems = [...prepareArray(arr2)];
const compared = currentArray.filter((el) => deletedItems.indexOf(el) === -1);
return convertJSON(compared);
};
How about using filter and some? You can extend the filter condition on select properties using &&.
const originalArray = [
{ id: 1, name: 'darnell' },
{ id: 2, name: 'funboi' },
{ id: 3, name: 'jackson5' },
{ id: 4, name: 'zelensky' },
];
const itemsToBeRemoved = [
{ id: 2, name: 'funboi', extraProperty: 'something' },
{ id: 4, name: 'zelensky', extraProperty: 'somethingelse' },
];
console.log(
originalArray.filter(item => !itemsToBeRemoved.some(itemToBeRemoved => itemToBeRemoved.id === item.id))
)
Or you can generalise it as well.
const originalArray = [
{ id: 1, name: 'darnell' },
{ id: 2, name: 'funboi' },
{ id: 3, name: 'jackson5' },
{ id: 4, name: 'zelensky' },
];
const itemsToBeRemoved = [
{ id: 2, name: 'funboi', extraProperty: 'something' },
{ id: 4, name: 'zelensky', extraProperty: 'somethingelse' },
];
function filterIfSubset(originalArray, itemsToBeRemoved) {
const filteredArray = [];
for (let i = 0; i < originalArray.length; i++) {
let isSubset = false;
for (let j = 0; j < itemsToBeRemoved.length; j++) {
// check if whole object is a subset of the object in itemsToBeRemoved
if (Object.keys(originalArray[i]).every(key => originalArray[i][key] === itemsToBeRemoved[j][key])) {
isSubset = true;
}
}
if (!isSubset) {
filteredArray.push(originalArray[i]);
}
}
return filteredArray;
}
console.log(filterIfSubset(originalArray, itemsToBeRemoved));
Another simpler variation of the second approach:
const originalArray = [
{ id: 1, name: 'darnell' },
{ id: 2, name: 'funboi' },
{ id: 3, name: 'jackson5' },
{ id: 4, name: 'zelensky' },
];
const itemsToBeRemoved = [
{ id: 2, name: 'funboi', extraProperty: 'something' },
{ id: 4, name: 'zelensky', extraProperty: 'somethingelse' },
];
const removeSubsetObjectsIfExists = (originalArray, itemsToBeRemoved) => {
return originalArray.filter(item => {
const isSubset = itemsToBeRemoved.some(itemToBeRemoved => {
return Object.keys(item).every(key => {
return item[key] === itemToBeRemoved[key];
});
});
return !isSubset;
});
}
console.log(removeSubsetObjectsIfExists(originalArray, itemsToBeRemoved));
The example below is a reusable function, the third parameter is the key to which you compare values from both arrays.
Details are commented in example
const arr=[{id:1,name:"darnell"},{id:2,name:"funboi"},{id:3,name:"jackson5"},{id:4,name:"zelensky"}],del=[{id:2,name:"funboi",extraProperty:"something"},{id:4,name:"zelensky",extraProperty:"somethingelse"}];
/** Compare arrayA vs. delArray by a given key's value.
--- ex. key = 'id'
**/
function deleteByKey(arrayA, delArray, key) {
/* Get an array of only the values of the given key from delArray
--- ex. delList = [1, 2, 3, 4]
*/
const delList = delArray.map(obj => obj[key]);
/* On every object of arrayA compare delList values vs
current object's key's value
--- ex. current obj[id] = 2
--- [1, 2, 3, 4].includes(obj[id])
Any match returns an empty array and non-matches are returned
in it's own array.
--- ex. ? [] : [obj]
The final return is a flattened array of the non-matching objects
*/
return arrayA.flatMap(obj => delList.includes(obj[key]) ? [] : [obj]);
};
console.log(deleteByKey(arr, del, 'id'));
let ff = [{ id: 1, name: 'darnell' }, { id: 2, name: 'funboi' },
{ id: 3, name: 'jackson5' },
{ id: 4, name: 'zelensky' }]
let cc = [{ id: 2, name: 'funboi', extraProperty: 'something' },
{ id: 4, name: 'zelensky', extraProperty: 'somethingelse' }]
let ar = []
let out = []
const result = ff.filter(function(i){
ar.push(i.id)
cc.forEach(function(k){
out.push(k.id)
})
if(!out.includes(i.id)){
// console.log(i.id, i)
return i
}
})
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])

Missing data should be on the next array

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>

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))

Categories