I am trying to implement Hacker News ranking algorithm with some custom changes. I need to sort my posts (array of objects) by popularity/trending. Simply, I would like to compare views x publish date and get trending posts.
I have tried this code:
const array = [
{ title: "1", views: 100, publishDate: 1 }, // publish date = 1 hour ago
{ title: "2", views: 400, publishDate: 2 }, // publish date = 2 hour ago
{ title: "3", views: 300, publishDate: 3} // publish date = 3 hour ago
];
const sortByTrending = array.sort((a, b) => b.views / Math.pow(b.publishDate, 1.8) - a.views /
Math.pow(a.publishDate, 1.8));
console.log(sortByTrending);
// Output:
[
{
title:"1",
views:300,
publishDate:1
},{
title:"3",
views:1000,
publishDate:3
},{
title:"2",
views:400,
publishDate:2
}
];
It's working somehow. But is there any better way to achieve more accurate result?
Related
I have an array with over 50 entries in the form of objects, which I would like to save depending on the Item ID so that I can then apply certain calculations to them. For example, I would like to add up the time of all entries with the Item Id "Las5241Wz".
Since the array can change dynamically, I can't analyze it manually. How can I separate the data beforehand according to their Item ID and push them into new arrays? The real Array contains up to 16 objects with the same ID.
var data= []
data = [
//...objects
{
itemId: "Las5241Wz",
time: 10
},
{
itemId:"Bos1239Zf",
time: 11
},
{
itemId:"Las5241Wz",
time: 15
},
{
itemId:"Bos1239Zf",
time: 21
}
//...more objets
]
The solution for this should look like this:
var Item1 = [
{
itemId: "Las5241Wz",
time: 10
},
{
itemId:"Las5241Wz",
time: 15
},
]
var Item2 = [
{
itemId:"Bos1239Zf",
time: 11
},
{
itemId:"Bos1239Zf",
time: 21
}
]
Here is another solution that builds an object with the properties "item1", "item2" and so on from the given object:
const data = [
//...objects
{
itemId: "Las5241Wz",
time: 10
},
{
itemId:"Bos1239Zf",
time: 11
},
{
itemId:"Las5241Wz",
time: 15
},
{
itemId:"Bos1239Zf",
time: 21
}
//...more objets
]
console.log(
Object.values(
data.reduce((o,e)=>((o[e.itemId]=o[e.itemId]||[]).push(e),o),{}))
.reduce((o,c,i)=>(o["item"+(i+1)]=c,o),{})
);
This is a "one-liner" and for that reason not that easy to read. So, probably not the version you would put into your production code.
Unless you have a performance reason to keep the lists separately, the answer is that you can just store the list of ids as a Set and use array.filter when you want to get the list that is just for that id
Set s = new Set();
for (let i = 0; i < data.length; i++) {
s.add(data[i].itemId);
}
var createFilterFunc(id) {
return function(elem) {
return elem.itemId == id;
}
}
var items = data.filter (createFilterFunc("Las5241Wz"));
I'm trying to render a chart from a big amount of data (about 1200 entries). The chart takes in an array of objects with text and value properties like the one shown in FIG1. The data that I have coming in though is an object with key value pairs of string and number like the one shown if FIG2.
How could I transform the data from FIG2 format to FIG1 format so that I can use it in the Chart? Any help is much appreciated.
//FIG1
let words = [
{
text: "told",
value: 64,
},
{
text: "great",
value: 11,
},
{
text: "thought",
value: 16,
},
{
text: "clean",
value: 17,
},
];
//FIG2
const data = {
"give it a try!": 97,
"go for 6 months and get 1 month free": 8,
"go for 12 months and get 2 month free": 2,
"go for 12 months and get 2 months free": 6,
"go to url": 1,
};
...
return (
<div>
<h1>Chart</h1>
<ReactWordcloud words={words} />
</div>
);
Easy-Peasy
const transformed = Object.entries(data).map(( [key, value] )=>{
return { text:key , value: value }
})
I have a problem sorting data and getting an array with the right data. Here is the situation :
I get a table of data representing invoices (creation date and price) in this format :
invoices: [
0: {
amount: 200,
created_at: 1590572830425
},
1: {
amount: 799,
created_at: 1590572847553
}
...
]
I would like to loop on this table of invoices, and extract the total sum of the amount of invoices according to the month of the year (thanks to the fields created_at representing a timestamp.
At the end, I would like to obtain a table with 12 values maximum, representing the total amount of invoices classified by month
Expected result : [300, 450, 799, 650, 288, 400, 643, 809, 1073, 499, 640, 600]
Thank you in advance.
As you would not want amounts to get mingled up across different years, it would be better to have a result that lists the relevant years, and then for each year, have the 12 sums:
function monthTotals(invoices) {
let totals = {};
for (let {created_at, amount} of invoices) {
let date = new Date(created_at);
let year = date.getFullYear();
(totals[year] = (totals[year] || Array(12).fill(0)))[date.getMonth()] += amount;
}
return totals;
}
let invoices = [{
amount: 200,
created_at: 1590572830425
},{
amount: 799,
created_at: 1590572847553
}];
console.log(monthTotals(invoices));
If you can, try to always give a real input and output combination - i.e. ones that match (input maps to expected output), so that we can run it and test our output against your expected output. Also, often known as a minimal reproducible example (https://stackoverflow.com/help/minimal-reproducible-example):
const invoices = [
{
amount: 200,
created_at: 1590572830425
},
{
amount: 799,
created_at: 1590572847553
}
]
const outputArr = [0,0,0,0,0,0,0,0,0,0,0,0];
invoices.forEach(item => {
const myDate = new Date(item.created_at);
console.log(myDate.toDateString())
const month = parseInt(myDate.getMonth());
outputArr[month] += item.amount
});
console.log(outputArr);
Output:
[0,0,0,0,999,0,0,0,0,0,0,0]
Like this. I first did a reduce but that was overkill
This solution will not work across years
const invoices = [{
amount: 200,
created_at: 1590572830425
},
{
amount: 799,
created_at: 1590572847553
}
];
const monthVal = new Array(12).fill(0);
invoices.forEach(item => {
const month = new Date(item.created_at).getMonth();
monthVal[month] += item.amount;
});
console.log(monthVal)
Also you can sum up the entire process in reduce method.
var invoices = [ {amount: 200,created_at: 1590572830425},{amount: 799,created_at:1590572847553}];
var result = invoices.reduce((acc, elem)=>{
month = new Date(elem.created_at).getMonth();
acc[month] += elem.amount;
return acc;
},Array.from({length:12}, ()=>0));
console.log(result);
I want to show a list of posts from the database based on likes and date, think of the basic "trending" items page.
I want to use a formula like score = likes / daysSinceCreation and then get the first 10 posts based on this score.
How can I add that sort function with mongoDB/Mongoose?
Posts.find().sort(???).limit(10).then(posts => console.log(posts));
Currently I can get top posts in last week (find if creation date larger than last week and order by score), but how can I implement a more complex sorting function without getting all the items from the DB?
eg:
Today is Friday
ID CREATION_DAY LIKES
1 Monday 4 // score is 5/5 = 0
2 Tuesday 10 // score is 10/4 = 2
3 Wednesday 3 // score is 3/3 = 1
4 Thursday 20 // score is 20/2 = 10
5 Friday 5 // score is 5/1 = 5
Sorted list of IDs is: [4 (Th), 5 (Fr), 2 (Tu), 3 (We), 1(Mo)]
This will create a new document in a "trendingposts" table:
const fiveDaysAgo = new Date(Date.now() - (5 * 24 * 60 * 60 * 1000));
const oid = new ObjectId();
const now = new Date();
Posts.aggregate([
{
$match: {
createdAt: {
$gte: fiveDaysAgo
},
score: {
$gt: 0
}
}
},
{
$project: {
_id: true,
createdAt: true,
updatedAt: true,
title: true,
description: true,
score: true,
trendScore: {
$divide: [ "$score", {$subtract: [new Date(), "$createdAt"]} ]
}
}
},
{
$sort: {
trendScore: -1
}
},
{
$limit: 10
},
{
$group: {
_id: { $min: oid },
evaluatedAt: { $min: now },
posts: { $push: "$$ROOT"}
}
},
{
$out: "trendingposts"
}
])
.then(...)
A few things to note:
If using Mongo 3.4+ the $project stage can also be written as:
{
$addFields: {
trendScore: {
$divide: [ "$score", {$subtract: [new Date(), "$createdAt"]} ]
}
}
},
{ $min: now } is just a hack to grab the minimum value of now on each document, even though it's the same value for all of them.
"$$ROOT" is the entire current document. This means your end result will be a single object with the form:
{
"_id" : ObjectId("5a0a2fe912a325eb331f2759"),
"evaluatedAt" : ISODate("2017-11-13T23:51:56.051Z"),
"posts" : [/*10 `post` documents, sorted by trendScore */]
}
You can then query with:
TrendingPosts.findOne({})
.sort({_id: -1})
.then(trendingPost => console.log(trendingPost));
If your description/title are changing frequently, instead of $pushing the entire document in, you could just push the ids and use them for an $in query on your posts in order to guarantee the latest data.
Using Fuse.js, I need to weight individual item for a better ranking in search results. For instance, how do I make sure "Paris France" has the biggest score for a "Paris" query with the data below?
places = [{
name: 'Paris, France'
weigth: 10
},{
name: 'Paris, Ontario'
weigth: 2
},
{
name: 'Paris, Texas'
weigth: 1
}]
As far as I am aware, there are no methods built into Fuse.js to do this. The weight property is meant to be applied to properties which are being searched (in the options object), rather than to the object that is being searched (as seen in the example here.
What I might suggest is writing a function to sort this yourself. So once you get your results array back, after the search, perform an Array.sort() on it yourself (documentation here).
For example...
//Your places object
var places = [
{
name: 'Paris, Texas',
weight: 2
},
{
name: 'Paris, France',
weight: 10
},
{
name: 'Paris, Texas',
weight: 1
}
];
//Your search options
var options = {
keys: [
"name"
]
};
var fuse = new Fuse(places, options); // "list" is the item array
var result = fuse.search("Paris");
//Once you have got this result, perform your own sort on it:
result.sort(function(a, b) {
return b.weight - a.weight;
});
console.log('Your sorted results:');
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.1.0/fuse.min.js"></script>