Trying to create a trending formula based on certain conditions - javascript

I have five articles with these initial properties:
const articles = [
{ id: 1, views: 92, likes: 0, shares: 2, trendingValue: ? },
{ id: 2, views: 14, likes: 2, shares: 1, trendingValue: ? },
{ id: 3, views: 39, likes: 3, shares: 1, trendingValue: ? },
{ id: 4, views: 87, likes: 0, shares: 1, trendingValue: ? },
{ id: 5, views: 8, likes: 1, shares: 0, trendingValue: ? }
];
I also have a global stats object that should be automatically updated once an article gets new views, likes or shares (or once a week):
const stats = {
totalArticles: 5,
totalViews: 240,
totalLikes: 6,
totalShares: 5,
trendingCriteria: 0
};
So far, I believe there is some sort of formula that can be done with articles' trendingValue and stats' trendingCriteria. Basically the articles that are "trending" need to have an equal or higher number than the criteria. Meaning that whenever an article gets a new view, share or like, trendingValue has to be updated in regards to it's percentage of views, likes and shares, by the global stats' counterparts.
An example (which doesn't exactly work) is:
A user views article 1.
This formula runs for the article to create its trendingValue:
const article = articles[0]; // Article with id 1
article.views++; // Increment the views count
stats.totalViews++ // Increment the total views count
let percentSum = (
(article.views / stats.totalViews) + // = 0.3833
(article.likes / stats.totalLikes) + // = 0
(article.shares / stats.totalShares) // = 0.4
); // = 0.7833
// The trendingValue needs to be a higher value of trendingCriteria
// before refreshing trendingCriteria.
article.trendingValue = (stats.trendingCriteria +
(percentSum / stats.trendingCriteria)
);
Next, trendingCriteria should be refreshed in regards to the updated article. The underlying logic is; if the new trendingCriteria is higher than the article's trendingValue, the article should no longer be "trending".
The third step is where I'm stuck. How do I create this value? Can this value be update for every single new view, like and share? Or do I have to update the value once a week or so?
Update
Thanks for all responses. Unfortunately I could not make any use of them since I'm yet confused what to do with the proposed solutions.
Anyhow, I tried another solution that makes use of an epoch timestamp and the average views, likes and shares. Not sure if it works in practice, so if anyone can confirm I'd be grateful.
function refreshArticleAtIndex(index, props) {
const data = articles[index];
// Increment props
if(props.view) { data.views++; stats.views++; }
else if(props.like) { data.likes++; stats.likes++; }
else if(props.share) { data.shares++; stats.shares++; }
// Refresh trendingRate
data.trendingRate = (() => {
const calcViews = data.views / stats.views;
const calcLikes = data.likes / stats.likes;
const calcShares = data.shares / stats.shares;
let value = Date.now() * (
(isFinite(calcViews) ? calcViews : 0) +
(isFinite(calcLikes) ? calcLikes : 0) +
(isFinite(calcShares) ? calcShares : 0)
);
return Math.round(value);
})();
}
function logArticles() {
const arr = articles.map(article => article);
arr.sort((a, b) => a.trendingRate > b.trendingRate ? -1 : 1);
arr.forEach(a => console.log(a.id +" |", a.trendingRate));
console.log("----------");
}
const stats = { views: 239, likes: 6, shares: 5 };
const articles = [
{ id: 1, views: 91, likes: 0, shares: 2, trendingRate: 0 },
{ id: 2, views: 14, likes: 2, shares: 1, trendingRate: 0 },
{ id: 3, views: 39, likes: 3, shares: 1, trendingRate: 0 },
{ id: 4, views: 87, likes: 0, shares: 1, trendingRate: 0 },
{ id: 5, views: 8, likes: 1, shares: 0, trendingRate: 0 }
];
console.log("ID | trendingRate");
// ================================================
// Add 1 view to article 1
refreshArticleAtIndex(0, { view: true });
// Add nothing to below articles, but refresh their trendingRate
refreshArticleAtIndex(1, {});
refreshArticleAtIndex(2, {});
refreshArticleAtIndex(3, {});
refreshArticleAtIndex(4, {});
logArticles();
// Add 1 like to article 1
refreshArticleAtIndex(0, { like: true });
logArticles();
The first log will show the correct order based on a combination of views, likes and shares for each article. Next, article 1 gets a like, which bumps it to the most trending article.
Question is if this is a working solution?

Trending probably should mean something like "top n% of activity" or "top n articles by activity".
Towards this end, you could simply save the trendingValue to an array like: { article: articleId, trendingValue: trendingValue}
Then when you go to find the most trending articles you would:
let cnt = trendingValues.length * myLimit
let i = 0
let trending = trendingValues.sort((a,b) => a.trendingValue > b.trendingValue).filter(() => i++ < cnt)
To use your language, trendingCriteria could be set to some minimum based on the set of articles. From there it could be fine tuned to ensure a certain minimum activity (as the average article age starts to grow).

Related

Javascript array reduce return zero instead of undefined

I've got an example array that I'm trying to reduce by the counts of the occurrence of a key (sentiment in this example):
const sentimentLog = [
{
id: 1,
createdOn: new Date('2020-02-13'),
sentiment: 1
},
{
id: 2,
createdOn: new Date('2020-02-12'),
sentiment: 1
},
{
id: 3,
createdOn: new Date('2020-02-12'),
sentiment: 2
},
{
id: 4,
createdOn: new Date('2020-02-11'),
sentiment: 3
},
{
id: 5,
createdOn: new Date('2020-02-11'),
sentiment: 2
},
{
id: 6,
createdOn: new Date('2020-02-10'),
sentiment: 1
},
{
id: 7,
createdOn: new Date('2020-02-10'),
sentiment: 2
},
{
id: 8,
createdOn: new Date('2020-02-09'),
sentiment: 1
}
]
I'm using:
const sentimentGrouped = (sentiments) => {
return sentiments.reduce((hash, { sentiment }) => {
hash[sentiment] = (hash[sentiment] || 0) + 1
return hash
}, [])
}
And it's nearly there. What I can't figure out is how to replace undefined when there's no sentiment scores of 0 (which is a possibility).
console.log('sentimentGrouped', sentimentGrouped(sentimentLog))
The above produces:
"sentimentGrouped" [undefined, 4, 3, 1]
Whereas I'd like:
"sentimentGrouped" [0, 4, 3, 1]
What am I missing?
Thanks in advance.
Edit: I'll elaborate a bit further, there's 4 scores that will be returned (0 to 3). The data returned will be based on a date range. So there may be instances where there'll be no 1s returned, similarly no 3s returned by a different date range.
The issue is that if you never touch an element of the array, then it stays as a hole in the array, which means it's treated as undefined. Since you know the length of the array i would just prefill the array with zeros. Any sentiment score that does occur will be incremented. Any one that doesn't will stay with its initial value.
return sentiments.reduce((hash, { sentiment }) => {
hash[sentiment] = hash[sentiment] + 1
return hash
}, [0, 0, 0, 0])

How to reformat a JSON array into another format "grouping" based on different keys

Question: How can I reformat this JSON array by "grouping" via different keys, using ReactJS?
I have a JSON array as :
[
{Product: "Shoes", Sold: 5, Bought : 0, Reversed : 2} ,
{Product: "Table", Sold: 2, Bought : 0, Reserved : 4}
]
The reason for this is the data type I'm working with, and on realizing I need to visualize this data in a different way (due to one of the graph packages I am using) I need to structure this data as:
[
{
Status: "Sold",
Shoes : 5,
Table : 2
} ,
{
Status: "Bought",
Shoes : 0,
Table : 0
} ,
{
Status: "Reserved",
Shoes : 2,
Table : 4
}
]
So I'm grouping the data into the keys other than Product, and then the keys after this are Product with the Value being the Product and it's "status".
Frankly, I am at a complete loss as to what to do, as I'm thinking the code required to generate this would be quite convoluted, so I'm very open to know if this just is too much work.
const data = [
{
Product: "Shoes",
Sold: 5,
Bought : 0,
Reserved : 2
} , {
Product: "Table",
Sold: 2,
Bought : 0,
Reserved : 4
}
];
let resultData = [];
Object.keys(data[0]).forEach((key, idx) => {
if (idx !== 0) {
let resultUnit = {
Status: key,
};
data.forEach(item => {
return resultUnit = {
...resultUnit,
[item.Product]: item[key],
}
})
resultData.push(resultUnit);
}
})
console.log(resultData);
// 0: {Status: "Sold", Shoes: 5, Table: 2}
// 1: {Status: "Bought", Shoes: 0, Table: 0}
// 2: {Status: "Reserved", Shoes: 2, Table: 4}
You can do this using the Array.reduce function. (Actually, two reduce functions).
Here's an extensible solution that allows for other statuses.
Note that I changed everything to lowercase, as is standard convention.
const items = [
{product: "Shoes", sold: 5, bought : 0, reserved : 2} ,
{product: "Table", sold: 2, bought : 0, reserved : 4}
]
//We declare the status types here.
const keys = ["sold", "bought", "reserved"];
// Just create the initial 'statuses' array.
function initAcc(keys) {
return keys.map((key) => {
return {
status: key
}
});
}
//Here we are iterating over each item, getting it to return a single accumulator array each time.
const newItems = items.reduce((acc, cur) => {
return addItemToAccumulator(acc, cur);
}, initAcc(keys));
console.log(newItems);
// This function maps of the accumulator array (ie. over each status).
function addItemToAccumulator(acc, item) {
return acc.reduce((acc, statusLine) => {
//Find the count from the existing status if it exists,
//Add the current items count for that status to it.
const itemCount = item[statusLine.status] + (statusLine[item.product] || 0);
//Return a modified status, with the new count for that product
return [
...acc,
{
...statusLine,
[item.product]: itemCount
}
];
}, []);
}
Lets just do a simple loop function and create a couple objects to clearly solve the problem here:
const data = [YOUR_INITIAL_ARRAY];
let Sold, Bought, Reserved = {};
data.forEach(({Product, Sold, Bought, Reserved})=> {
Sold[Product] = Sold;
Bought[Product] = Bought;
Reservered[Product] = Reserved;
});
let newArray = [Sold, Bought, Reserved];
I think you can see where this is going ^ I see a few others have given complete answers, but try and go for the clear understandable route so it makes sense.
All you have to do after this is set the status which i'd do off an enum and you are good

Calculate the greater value between objects in different arrays

I have a score array containing two objects: Liga and Premier. These 2 objects are an array of a list of teams.
I was able to define the greater string when score was previously a single array of objects.
This is the demo i have reproduced where the comparison works fine.
This is the code calculating the higher value comparing the 2 objects.
const maxAverage = teams => {
return teams.map(team => {
return {
team:team,
avg: getAverage(team)
}
}).reduce((a,b)=>a.avg>b.avg?a:b).team
}
<p>Stronger Team:{maxAverage([this.state.homeCity,this.state.awayCity])</p>
The problem now is that now score is an array of the 2 object as i said and i am trying to change my function in something like
const maxAverage = (league, teams) => {
return teams.map(team => {
return {
team:team,
avg: getAverage(league,team)
}
}).reduce((a,b)=>a.avg>b.avg?a:b).team
}
I am not able to pass to my function maxAverage the parameter of one of the two leagues selected and then the 2 objects ( teams ) i want to compare.
i want to do something like this:
<p>Stronger Team:{maxAverage([this.state.selectedLeague], this.state.selectedHomeTeam,this.state.selectedAwayTeam])}
This is the other demo i have reproduced with the current situation.
Given the signature const maxAverage = (league, teams) => ..., following code would match the expected arguments (not sure about the business logic though):
maxAverage(
this.state.selectedLeague,
[this.state.selectedHomeTeam, this.state.selectedAwayTeam]
)
I looked at your second demo and I think you have two choices to get the correct team selected and you can reuse your previous getAverage method for both. Either
const maxAverage = (league, teams) => {
const currentLeague = [scores][0][league]
return teams
.map(team => {
return {
team: team,
avg: getAverage(currentLeague, team)
};
})
.reduce((a, b) => (a.avg > b.avg ? a : b)).team;
};
alternatively you could keep the original maxAverage code and change how you implement the league value eg.
<p>
Stronger Team:
{maxAverage(scores[this.state.selectedLeague], [
this.state.selectedHomeTeam,
this.state.selectedAwayTeam
])}
</p>
Why not simply extract team when selected, save in the state and use the same method used before?
What is a problem?
const scores = {'liga':[
{ day: "1", Barcelona: 1, Real: 3, Valencia: 0 },
{ day: "2", Barcelona: 4, Real: 6, Valencia: 3 },
{ day: "3", Barcelona: 7, Real: 7, Valencia: 3 },
{ day: "4", Barcelona: 7, Real: 8, Valencia: 6 }
], 'primier':[
{ day: "1", Barcelona: 1, Real: 3, Valencia: 0 },
{ day: "2", Barcelona: 4, Real: 6, Valencia: 3 },
{ day: "3", Barcelona: 7, Real: 7, Valencia: 3 },
{ day: "4", Barcelona: 7, Real: 8, Valencia: 6 }]};
const getAverage = (type, team) => {
if (isNaN(scores[type][0][team])) return null;
return scores[type].map(x => x[team]).reduce((a, c) => a + c) / scores[type].length;
};
getAverage('liga',this.state.homeCity);
src:
https://codesandbox.io/s/recharts-examples-d9qy0

To group of array of objects by its id value

I have a group of array of objects in which that object property has value with ID number. I am successfully group it with that ID, however I am doing it by hardcode, not dynamically.
var objBuyUser = response.newIdUserPul[i].arrayUserHistoryBuy;
for (var y = 0; y < objBuyUser.length; y++) {
if(objBuyUser[y].product_id == 1) {
var doesIdExist = vm.pulBms.filter(function(v) {
return v.userid === response.newIdUserPul[i].id;
});
if(doesIdExist.length > 0) {
//do nothhing
} else {
vm.pulBms.push({
userid:response.newIdUserPul[i].id,
buyAct: objBuyUser
});
}
}
objBuyUser[y].product_id == 1 is what will different them each other. As long as this time we only have three ID number 1, 2, 3, I just copied the same code by changing the id manually. So, anybody can help to make it dynamically?
(**)SO: actually we have the list of product: 1, 2, 3
In the backend, I have process a unique user with its history buy, assign by response.newIdUserPul[i]. And the arrayUserHistoryBuy is the array of object detailing their product history buying from the range of the date, which in the object can consiste the date, quantity, and the product_id which need to be group here (reffer to -> **). In this case, knowing that each user can buy different product in the range of date, we still group it by product since the product is exist in the history of buying.
So the output could be similar like this:
ProductID1 :
{
User: 123
ProductBuyHistory :
{
Date: 3-10-10
ProductID : 2,
Quantity: 10
},
{
Date: 4-10-10
ProductID : 1,
Quantity: 10
},
},
{
User: 124
ProductBuyHistory :
{
Date: 3-10-10
ProductID : 3,
Quantity: 10
},
{
Date: 4-10-10
ProductID : 1,
Quantity: 10
},
},
SO on for productId 2 and 3

Gridster serialize data to 1-5 order (recursive loop)?

I'm using the Gridster plugin, and I need to capture the newly dragged order in an 1-5 manner. I know that we choose row first, then order (column). So, row:1, order:1 would be id: grid_74. The next closest order number greater than 1 in row 1 is 5, so id: grid_78. How can I accomplish this?
1 - grid_74
2 - grid_78
(etc...)
var gridinfo = gridster.serialize()
I think you might just need a sort.
// Just setting up object array to match example:
var gridinfo = [
{ id: "grid_75", order: 5, row: 4 },
{ id: "grid_74", order: 1, row: 1 },
{ id: "grid_91", order: 9, row: 1 },
{ id: "grid_85", order: 5, row: 7 },
{ id: "grid_78", order: 5, row: 1 }
]
// This sort is what does all the magic.. it first sorts by row, then order.
// I lifted this bit of javascript ninjutsu from:
// http://www.sitepoint.com/sophisticated-sorting-in-javascript/
gridinfo.sort(function(a, b)
{
if(a.row === b.row)
{
return a.order < b.order ? -1 : a.order > b.order ? 1 : 0;
}
return a.row - b.row;
});
// Display sorted array:
for(var k=0; k<gridinfo.length; k++)
{
document.write('id: '+gridinfo[k].id+'<br>');
document.write('row: '+gridinfo[k].row+'<br>');
document.write('order: '+gridinfo[k].order+'<br>');
document.write('-------<br>');
}

Categories