How can I add two values together inside a JavaScript object? [duplicate] - javascript

This question already has answers here:
Self-references in object literals / initializers
(30 answers)
Closed last month.
In the following example, I used square brackets to recall the values for "hours" and "minutes" in an attempt to add them together. This did not work. Is there a way to add two values together like this?
let movies = [
{
title: "Rosaline",
hours: 1,
mintues: 36,
totalLength: [hours] * 60 + [minutes],
},
{
title: "The Good Nurse"
hours: 2,
mintues: 3,
totalLength: [hours] * 60 + [minutes],
}
]

Since totalLength is a computed value, you can return totalLength with a getter. The advantage of this approach, should hours or minutes change, then the next time totalLength is retrieved, it will reflect the change.
let movies = [
{
title: "Rosaline",
hours: 1,
minutes: 36,
get totalLength() { return this.hours * 60 + this.minutes; }
},
{
title: "The Good Nurse",
hours: 2,
minutes: 3,
get totalLength() { return this.hours * 60 + this.minutes; }
},
];
console.log(JSON.stringify( movies, undefined, 2 ) );

Those properties do not yet exist, simply because the object that you think you are referencing does not yet exist. It is still in the process of being created.
You can do it like this instead, with a helper lambda function:
const movie = (title, hours, minutes) => ({title, hours, minutes, totalLength: hours * 60 + minutes});
let movies = [
movie("Rosaline", 1, 36),
movie("The Good Nurse", 2, 3)
];
console.log(movies);

No such syntax exists in JavaScript. You can do what you are attempting in 2 ways:
Option 1
Create the objects using a function or a class constructor:
function createMovie(title, hours, minutes) {
return {
title,
hours,
minutes,
totalLength: hours * 60 + minutes
};
}
let movies = [
createMovie("Rosaline", 1, 36)
];
Option 2
Do a pass over the array to correct the information post construction
const calculateTotalLength = (movie) => movie.totalLength = movie.hours * 60 + movie.minutes;
let movies = [{
title: "Rosaline",
hours: 1,
minutes: 36,
totalLength: 0 // recalculated below
}];
movies.forEach(calculateTotalLength);

Related

Typescript reduce I want to sum numbers but the previous value becomes string

I have a typescript function which traverse an array of objects, and summary them.
It was working if I implicit gives the value a number, however if it comes from input it converts the value to string.
const mmrChanges = matches.reduce<IHistory[]>(
(previous: IHistory[], match: IMatch, index: number) => {
const mmrChange = (match.party_size > 1 ? 20 : 30) * isWon(match); // party game 20 mmr, solo game 30
const prevMmr = previous[previous.length - 1].value;
return [
...previous,
{
index: index,
value: prevMmr + mmrChange,
time: new Date(match.start_time * 1000).toISOString().split("T")[0],
},
];
},
[{ index: 0, value: currentMmr, time: "" }]
);
console.log(mmrChanges);
value type is : (property) IHistory.value: number
and the current mmr is: const currentMmr: number
However when I start to accumulate the value numbers it starts to concatenate values as strings.
currentValue = 400
the next iterate it becomes:
{
"index": 0,
"value": "40030",
"time": "2022-01-25"
}
I tried to force it to number explicitly inside the reduce function with "as number"
Also I tried the parseInt() function, but then typescript throws error I cant transform number to int.
What am I missing here?
If I change the currentMmr to 0 it is working as intended.
This solution below seem to have achieved the desired results:
const mmrChanges = matches.reduce<IHistory[]>(
(previous: IHistory[], match: IMatch, index: number) => {
const mmrChange = (match.party_size > 1 ? 20 : 30) * isWon(match); // party game 20 mmr, solo game 30
const prevMmr = previous[previous.length - 1].value;
return [
...previous,
{
index: index,
value: prevMmr + mmrChange,
time: new Date(match.start_time * 1000).toISOString().split("T")[0],
},
];
},
[{ index: 0, value: parseInt(currentMmr.toString()) || 0, time: "" }]
);
console.log(mmrChanges);
What changed?
The .reduce aggregator was set to [{ index: 0, value: parseInt(currentMmr.toString()) || 0, time: "" }]
Why?
In the question, currentMmr could be any value. In order to ensure that it is always an int, the value prop is set as parseInt(currentMmr.toString()). The .toString() ensures that in case currentMmr is an integer, it is still treated as a string. And, the parseInt transforms the string into an int.
Note: This solution caters to this particular question. It may not work AS IS in other contexts and may need to be customized.

Sort a nested array of object when index is a key in JavaScript

I am trying to sort nested array of objects, based off the key hours. The reason I am setting the index as userId, is that I want to be able to do highScoreList[userId] at a later time to grab the selected user information.
[
'587665723162626': { userId: '587665723162626', hours: 0, lastHours: 0 },
'120156769943556': { userId: '120156769943556', hours: 0, lastHours: 0 },
'773193626386432': { userId: '773193626386432', hours: 10, lastHours: 2 }
]
let highScoreList = [];
//data is inserted via another function from mongoDB and below is how I inserted into the array.
const updateHighScoreCache = (userId, hours, lastHours) =>{
highScoreList[userId] = { userId, hours, lastHours };
}
function compare(a, b) {
if(a.hours > b.hours) return 1;
if(b.hours > a.hours) return -1;
return 0;
}
highScoreList.sort(compare)
I have tried changing the if statement in the compare function to the following and the array did not change at all still:
if(highScoreList[a].hours > highScoreList[b].hours) return 1;
if(highScoreList[b].hours > highScoreList[a].hours) return -1;
The expected result after sort is:
[
'773193626386432': { userId: '773193626386432', hours: 10, lastHours: 2 },
'587665723162626': { userId: '587665723162626', hours: 0, lastHours: 0 },
'120156769943556': { userId: '120156769943556', hours: 0, lastHours: 0 }
]
But I keep getting the original array.
Your array literal at the top is not valid JS syntax. The code shows where you go wrong.
First you define highScoreList as an array, but then you don't populate the array, but create object properties for it, with:
highScoreList[userId] = { userId, hours, lastHours };
These (non-index) properties are ignored by the typical array methods such as sort. Those methods work on the values stored at the array indexes.
So change this:
highScoreList[userId] = { userId, hours, lastHours };
to this:
highScoreList.push({ userId, hours, lastHours });
If however, the structure is returned to you as a plain object (not an array), like so:
{
'587665723162626': { userId: '587665723162626', hours: 0, lastHours: 0 },
'120156769943556': { userId: '120156769943556', hours: 0, lastHours: 0 },
'773193626386432': { userId: '773193626386432', hours: 10, lastHours: 2 }
}
..then note that plain objects are not really the right tool for sorting. They better serve use cases where you don't care about order. So if you really get an object like this (not an array) and need order, then covert that object to an array first, with:
highScoreList = Object.values(highScoreList);
...and then call .sort
Another remark: your compare function will do the job, but the way it really is done, is as follows:
const compare = (a, b) => a.hours - b.hours;
If you array is:
const highScoreList = [
{ userId: '587665723162626', hours: 0, lastHours: 0 },
{ userId: '120156769943556', hours: 0, lastHours: 0 },
{ userId: '773193626386432', hours: 10, lastHours: 2 }
];
Then this code sorts it DESC:
highScoreList.sort((x,y) => y.hours - x.hours)
Then this code sorts it ASC:
highScoreList.sort((x,y) => x.hours - y.hours)

How does this code work in context with reduce function?

It might be a very basic question for people here but I have to ask away.
So I was going through reducce recently and I came through this example where I could find the maximum of some value in an array of object. Please, have a look at this code.
var pilots = [
{
id: 10,
name: "Poe Dameron",
years: 14
}, {
id: 2,
name: "Temmin 'Snap' Wexley",
years: 30
}, {
id: 41,
name: "Tallissan Lintra",
years: 16
}, {
id: 99,
name: "Ello Asty",
years: 22
}
];
If I write soemthing like this to find the maximum years,
var oldest_of_them_all = pilots.reduce(function (old, current) {
var old = (old.years > current.years) ? old.years : current.years;
return old
})
I get 22 as my value, and if I dont involve the property years, i.e-
var oldest_of_them_all = pilots.reduce(function (old, current) {
var old = (old.years > current.years) ? old : current;
return old
})
I get the object Object {id: 2, name: "Temmin 'Snap' Wexley", years: 30} as my value. Can someone explain why the first example is wrong and what is happening in there? Also, if I Just want to fetch the years value, how can I do that? Thanks in advance.
In the first example, as you are not returning the object there is no object property (years) of the accumulator (old) after the first iteration. Hence there is no year property to compare with.
var pilots = [
{
id: 10,
name: "Poe Dameron",
years: 14
}, {
id: 2,
name: "Temmin 'Snap' Wexley",
years: 30
}, {
id: 41,
name: "Tallissan Lintra",
years: 16
}, {
id: 99,
name: "Ello Asty",
years: 22
}
];
var oldest_of_them_all = pilots.reduce(function (old, current) {
console.log(old);// the value is not the object having the property years after the first iteration
var old = (old.years > current.years) ? old.years : current.years;
return old;
})
console.log(oldest_of_them_all);

Trending sort by views and date

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?

Mongoose query for "hot" posts

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.

Categories