here is my code for now:
router.get('/questions/best', function(req,res,next){
Question.find({}).exec(function(err,results){
if (err){return next(err)}
let top15 = [];
for(let i = 0; i< results.length; i ){
let dislikestouse = results[i].dislikes[0]
let ratio = 0
if(dislikestouse > 0){
ratio = results[i].likes[0]/dislikestouse
}
else{
ratio = results[i].likes[0]/1
}
}
res.render('content', { whichOne: 5, user: req.user, questions: top15 });
})
});
results is every question in the database, and top15 is supposed to be only the ones displayed since they have the highest ratio and rendered into the template. I tried using if statements to prevent it dividing by 0 since it results in an error.
how do I sort every result by finding if it is bigger then any of the items in top15 and then placing it in the right place if it is? I use pure JavaScript.
to access the numbers of a result's dislikes and likes search for the first item since it where I store the length, like a typical likes array is: [2,'username1', 'username2']
.
here is the scheme for a question:
const Question = mongoose.model(
"Question",
new Schema({
title: { type: String, required: true },
text: { type: String },
authorUsername: { type: String, required: true },
dateCreated: {},
answers: { type: Array, required: true },
likes: { type: Array, required: true },
dislikes: { type: Array, required: true },
tag: { type: String, required: true },
views: {type: Array, required:true},
})
);
here is an example of a question:
_id:620935985f6865b4e85c333d
title:"How do i make a lemon?"
text:"yeet"
authorUsername:"SweetWhite"
dateCreated:2022-02-13T16:45:12.598 00:00
answers:
Array
likes:
Array
0:1
1:"SweetWhite"
dislikes:
Array
0:0
tag:
"Social"
views:
Array
0:1
1:"SweetWhite"
__v:0
Thanks!
CodePudding user response:
You could use Array methods:
sort: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
slice: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
map: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Here is snippet i tried for your problem:
function sortQuestions(results) {
const questionsWithRatio = results.map((result) => ({
...result,
ratio: result.likes[0] / Math.max(result.dislikes[0], 1),
}));
// console.log(questionsWithRatio);
const top15 = questionsWithRatio
.sort((a, b) => b.ratio - a.ratio)
.slice(0, 15);
console.log(top15);
}
CodePudding user response:
The answer above is a superb pure Javascript solution. However, it might be the case that we do not want to drag 1000s or possibly more of items out of the database just to sort and pick 15 on the client side. Given a set of docs like this:
var r = [
{likes: ['A','A'], dislikes: ['A','A']},
{likes: ['A','A']},
{dislikes: ['A','A']},
{likes: ['A','A','A','A','A'], dislikes: ['A','A']},
{likes: ['A','A','A','A','A','A','A','A','A'], dislikes: ['A','A']},
{likes: ['A','A','A','A','A','A','A','A'], dislikes: ['A','A']},
{likes: ['A','A','A','A','A','A','A'], dislikes: ['A','A']},
{likes: ['A'], dislikes: ['A','A']}
];
Then here is an aggregation that will drive the sort and "slice" logic into the DB:
db.foo.aggregate([
// Protect against missing fields with $ifNull
// Protect against division by zero dislikes using max[1,size]
{$addFields: {ratio: {$divide:[{$size:{$ifNull:["$likes",[]]}},
{$max:[1,{$size:{$ifNull:["$dislikes",[]]}}]}
]}
}},
{$sort: {ratio:-1}},
{$limit: 3} // change to 15 for the real dataset
]);
If we must accomodate the "run length encoding" nature of the arrays e.g. [2, 'user1', 'user2']
then the agg is very similar only using $arrayElemAt
to grab array[0] instead of using $size
:
var r = [
{likes: [2, 'A','A'], dislikes: [2, 'A','A']},
{likes: [2, 'A','A']},
{dislikes: [2, 'A','A']},
{likes: [5, 'A','A','A','A','A'], dislikes: [2, 'A','A']},
{likes: [9, 'A','A','A','A','A','A','A','A','A'], dislikes: [2, 'A','A']},
{likes: [8, 'A','A','A','A','A','A','A','A'], dislikes: [2, 'A','A']},
{likes: [7, 'A','A','A','A','A','A','A'], dislikes: [2, 'A','A']},
{likes: [1, 'A'], dislikes: [2, 'A','A']}
];
db.foo.aggregate([
// Protect against missing fields with $ifNull but this time it is not
// an empty array [] but rather an array of one elem, the length, which
// is 0:
{$project: {ratio: {$divide:[
{$arrayElemAt:[{$ifNull:["$likes",[0]]}, 0]},
{$max:[1, {$arrayElemAt:[{$ifNull:["$dislikes",[0]]}, 0]}]}
]}
}},
{$sort: {ratio:-1}},
{$limit: 3}
]);
{ "_id" : 4, "ratio" : 4.5 }
{ "_id" : 5, "ratio" : 4 }
{ "_id" : 6, "ratio" : 3.5 }
CodePudding user response:
Before I try to help, please try to improve your code's readability. You can look up async/await
and ES6 syntax to help you in this.
Also, if you're using an IDE like Visual Studio Code then you can install prettier which will autoformat your codes.
Here's a rough solution for you:
router.get('/questions/best', function(req,res,next){
Question.find({}).exec(function(err,results){
if (err) return next(err);
let top15 = [];
// note that the code is slightly more readable already
results.forEach(result => {
const dislikesToUse = result.dislikes[0]
let ratio = 0;
if(dislikesToUse > 0) {
ratio = result.likes[0]/dislikesToUse
} else {
ratio = result.likes[0]/1
}
// push each result with ratio to top15
top15.push({...result, ratio})
});
// sort top15 by ratio
top15.sort((a,b) => b.ratio - a.ratio) // do a.ratio - b.ratio to change order
// get only the top 15
top15 = top15.slice(0, 15)
res.render('content', { whichOne: 5, user: req.user, questions: top15 });
})
});
NOTE
This is only one way to do it and might not be the most efficient way. You should also consider Mongo's aggregate functions.
You should also definitely reconsider your schema design.