Related
I have a large array of objects, similar to this:
[
{
id: "some_id",
timestamp: 12345
},
{
id: "some_other_id",
timestamp: 12347
},
{
id: "some_id",
timestamp: 12346
},
{
id: "some_other_id",
timestamp: 12348
},
...
]
I want to sort the array in a way, that there are "sections" of objects in the array, depending which id the object has. Inside each section, the objects should be sorted ascending depending on the timestamp. The sections themselves should also be sorted depending on the first timestamp of the section. So the array should look like this:
[
// section: some_id
{
id: "some_id",
timestamp: 12345
},
{
id: "some_id",
timestamp: 12348
},
// section: some_other_id, comes ofter some_id section because 12346 > 12345
{
id: "some_other_id",
timestamp: 12346
},
{
id: "some_other_id",
timestamp: 12347
},
...
]
It should also be possible to chose between ascending/descending in the function.
Right now i have this:
elements.sort((a, b) => {
if (a.id === b.id) {
if (sortAscending) {
return a.timestamp > b.timestamp ? -1 : 1;
} else {
return a.timestamp > b.timestamp ? 1 : -1;
}
} else {
return a.id.localeCompare(b.id);
}
})
This doesn't sort the sections correctly however. Any ideas?
This will require two passes through the array because you can't sort by sections until you know what the order of the sections should be and you don't know that until you've seen all the sections and thus know what the lowest or highest timestamp is for each section (depending upon whether you're doing ascending or descending sort).
So, probably what makes sense is to make a first pass that collects the extreme value for each section and stores it into a Map object that you can use as a section sort index. Then, you can run .sort(). If the sections are the same, you sort by timestamp. If the sections are not the same, you sort by the value in the section index.
function sortByIdAndTimestamp(data, sortAscending = true) {
// create a Map object where keys are id values and values are the extreme
// timestamp for that id
const extremeTimestamp = new Map();
for (let item of data) {
if (testExtreme(item.timestamp, extremeTimestamp.get(item.id), sortAscending)) {
extremeTimestamp.set(item.id, item.timestamp);
}
}
// now just do a dual key sort
data.sort((a, b) => {
let result;
if (a.id === b.id) {
// if id is the same, just sort by timestamp
result = b.timestamp - a.timestamp;
} else {
// if id is not the same, sort by the extreme timestamp of the id
result = extremeTimestamp.get(b.id) - extremeTimestamp.get(a.id);
}
if (sortAscending) {
result = -result;
}
return result;
});
return data;
}
function testExtreme(val, extremeSoFar, sortAscending) {
// determine if this id's timestamp is more extreme
// than what we already have for that section
return (extremeSoFar === undefined) || (sortAscending ?
val < extremeSoFar :
val > extremeSoFar);
}
const sampleData = [{
id: "some_id",
timestamp: 12345
},
{
id: "some_other_id",
timestamp: 123
},
{
id: "some_id",
timestamp: 12346
},
{
id: "some_other_id",
timestamp: 99999
},
{
id: "yet_another_id",
timestamp: 1
},
{
id: "yet_another_id",
timestamp: 90000
},
];
sortByIdAndTimestamp(sampleData, true);
console.log(sampleData);
Note: When you said you wanted to sort in either ascending or descending order, I assumed you meant that for both sections and for timestamps. So, an ascending sort would have the lowest timestamp section first and then the items within each section would be sorted by timestamp from low to high. And, a descending sort would have the highest timestamp section first and then items within each section would be sorted by timestamp from high to low.
You are not going to be able to do it in one sort. It is going to have to be multiple steps since you do not know what the minimum is.
One way is to combine, sort and than sort based on the lowest.
const data = [
{
id: "a",
timestamp: 4
},
{
id: "b",
timestamp: 3
},
{
id: "a",
timestamp: 2
},
{
id: "b",
timestamp: 1
},
];
const x = data.reduce((a,o) => {
a[o.id] = a[o.id] || [];
a[o.id].push(o);
return a;
}, {});
const v = Object.values(x);
v.forEach(x => x.sort((a,b) => a.timestamp > b.timestamp ? 1 : -1))
v.sort((a,b)=>a[0].timestamp > b[0].timestamp ? 1 : -1);
const sorted = v.flat();
console.log(sorted);
The other way is find the lowest and then sort with that.
const data = [{
id: "a",
timestamp: 4
},
{
id: "b",
timestamp: 3
},
{
id: "a",
timestamp: 2
},
{
id: "b",
timestamp: 1
},
];
const smallest = data.reduce((a ,o) => {
a[o.id] = Math.min(a[o.id] === undefined? Number.POSITIVE_INFINITY : a[o.id], o.timestamp);
return a;
}, {});
data.sort((a,b) => {
return a.id === b.id ?
(a.timestamp > b.timestamp ? 1 : -1)
: smallest[a.id] > smallest[b.id] ? 1 : -1;
})
console.log(data);
EDITED: Here's one way to do it if I understood correctly what you need
const data = [{
id: "some_id",
timestamp: 12348
},
{
id: "some_other_id",
timestamp: 12346
},
{
id: "some_id",
timestamp: 12345
},
{
id: "some_other_id",
timestamp: 12347
},
{
id: "X_other_id",
timestamp: 12343
},
{
id: "other_id",
timestamp: 12349
}
]
const groupById = (acc, item) => {
acc[item.id] ? acc[item.id].push(item) : acc[item.id] = [item];
return acc;
};
function sort(arr, bool) {
const groups = Object.values(arr.reduce(groupById, {}))
.map(group => group.sort((a, b) => bool ? a.timestamp - b.timestamp : b.timestamp - a.timestamp))
.sort((a, b) => bool ? a[0].timestamp - b[0].timestamp : b[0].timestamp - a[0].timestamp);
return groups.flat()
}
console.log(sort(data, ascending = true))
The desired output should be as follows. I tried object restructuring way but i could not push the out as an object. If you can just guide me what are the other array methods i can use to get the desired array
const sample = [
{
name: 'Bike',
series: [
{ date: '01-01-2020', value: '4$' },
{ date: '02-01-2020', value: '3$' },
{ date: '03-01-2020', value: '3.5$' }
]
},
{
name: 'Watch',
series: [
{ date: '01-01-2020', value: '1$' },
{ date: '02-01-2020', value: '2$' },
{ date: '03-01-2020', value: '5$' }
]
}
]
const output = [
{ date: '01-01-2020', 'bike-value': '4$', 'watch-value': '1$' },
{ date: '02-01-2020', 'bike-value': '3$', 'watch-value': '2$' },
{ date: '03-01-2020', 'bike-value': '3.5$', 'watch-value': '5$'}
]
What i tried is as follows. But i cannot make this into a object to push into an empty array.
for (const {name: n, series: [{date: d , value: v}]} of sample) {
console.log('name: ' + n + ', date: ' + d + ', value: ' + v);
}
You could loop through the sample array and then loop through the each series array. Create a group object which has each date as key and the object needed in the final output it's value. Use Object.values() to get the values of the group object as an array
const sample=[{name:"Bike",series:[{date:"01-01-2020",value:"4$"},{date:"02-01-2020",value:"3$"},{date:"03-01-2020",value:"3.5$"}]},{name:"Watch",series:[{date:"01-01-2020",value:"1$"},{date:"02-01-2020",value:"2$"},{date:"03-01-2020",value:"5$"}]}];
const group = {}
for (const { name, series } of sample) {
for (const { date, value } of series) {
group[date] = group[date] || { date };
group[date][`${name.toLowerCase()}-value`] = value
}
}
const output = Object.values(group)
console.log(output)
The group object looks like this:
{
"01-01-2020": {
"date": "01-01-2020",
"bike-value": "4$",
"watch-value": "1$"
},
"02-01-2020": {
"date": "02-01-2020",
"bike-value": "3$",
...
},
"03-01-2020": {
....
}
A simple nested constructor should work here:
const sample = [
{name : 'Bike', series :
[{date : '01-01-2020', value : '4$'},
{date : '02-01-2020', value : '3$'},
{date : '03-01-2020', value : '3.5$'}]
},
{name : 'Watch', series :
[{date : '01-01-2020', value : '1$'},
{date : '02-01-2020', value : '2$'},
{date : '03-01-2020', value : '5$'}]
}
];
let results = [];
for (let i = 0; i< sample[0].series.length; i++){
//get date and 'Bike' value from first value
let newEntry = {date : sample[0].series[i].date, bikeValue : sample[0].series[i].value};
//find the corresponding 'Watch' value with another loop
let watchValue = 0;
for (let i2 = 0; i2<sample[1].series.length; i2++){
if(sample[1].series[i2].date == newEntry.date){
watchValue = sample[1].series[i2].value;
}
}
newEntry.watchValue = watchValue;
//push new object into results array
results.push(newEntry);
}
console.log(results);
I have a model that looks like this :
Model :
createdAt: {
type: String,
default: Moment(new Date()).format('YYYY-MM-DD')
},
loginTrack: [
{
user_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Users',
}
}
With some data :
[
{
_id: ...,
createdAt : '2018-03-22',
loginTrack: [
{user_id : 1,...}
{user_id : 1, ...},
{user_id : 2, ...}
]
},
{
_id: ...,
createdAt : '2018-03-23',
loginTrack : [
{user_id : 4, ...},
{user_id : 1, ...}
]
},
{
_id : ...,
createdAt: '2018-03-24',
loginTrack : [
{user_id : 2, ...}
]
]
I'd like to have the percentage of total unique new sessions per day, that mean count the number of sessions for each previous day, is it possible with mongodb ?
With an output like this
[{
date : '2018-03-22',
newSessionsAvg : 2 (unique sessions only : maybe it's 100 % ?)
},
{
date : '2018-03-23',
newSessionAvg: 100
},
{
date : '2018-03-24',
newSessionAvg : 25 (1/ (2+2) * 100)
}]
Is it possible using an aggregation/project/group ?
This is what I tried :
AnalyticsModel.aggregate([
{
"$project" : {
users: {$size: "$loginTrack"},
"createdAt" : 1,
"_id": 0
}},
{
"$group": {
"_id": "$createdAt",
"count": { "$sum": 1 }
}
}
The output looks like this :
[{"_id":"2018-03-22","count":3},{"_id":"2018-03-21","count":2}]
Thanks
Maybe just create a occurence map at first:
User.find({}, function(err, users) {
const occurences = {};
for(const {createdAt} of users){
occurences[createdAt] = (occurences[createdAt] || 0) +1;
}
Then you can sort that data after the date and build up the results:
const timeline = Object.entries(occurences);
timeline.sort((a,b) => a[0].localeCompare(b[0]));
const result = [];
let previous = 0;
for(const [date, total] of timeline){
result.push({ date, avg: (total / (total + previous) || 0) * 100 });
previous = total;
}
I have a json file with multiple transactions with a date and a price attribute. Now I want to compare the dates and if they are in the same month and year I want to sum up the prices.
JSON:
transactions: [
{
date: "2017-11-17",
price: "28",
},
{
...
}
JavaScript:
request.onload = function() {
for(const transaction of request.response.transactions) {
let year = new Date(transaction.date).getFullYear();
let month = new Date(transaction.date).getMonth();
console.log(year + ' ' + month); // output: 2017-11 ...
}
};
I tried to loop over the json object but I struggle to find a solution to compare the dates.
Edit: Edited example with Object.assign instead of Object spread.
You'll need to use reduce to sum the prices. See comments for details.
const transactions = [{
date: "2017-11-17",
price: "28",
},
{
date: "2017-12-17",
price: "23",
},
{
date: "2017-11-17",
price: "12",
},
{
date: "2017-10-17",
price: "55",
},
{
date: "2017-11-17",
price: "09",
},
];
const sumTransactions = (transactions) => {
const summed = transactions.reduce((acc, current) => {
// Get the current date object
const date = new Date(current.date);
// Create your key/identifier
const key = `${date.getFullYear()}-${date.getMonth() + 1}`;
// Retreive the previous price from the accumulator
const previousPrice = acc[key]; // Might also return undefined
// Create your temp current price value, and be sure to deal with numbers.
let currentPrice = Number(current.price);
// If you had a previous value (and not undefined)
if (previousPrice) {
// Add it to our value
currentPrice += Number(previousPrice);
}
// Return the future accumulator value
return Object.assign(acc, {
[key]: currentPrice, // new values will overwrite same old values
})
}, {})
// Once we have all values, get the dates, and sort them (default: earlier first)
// Return an array of each value from the summed object to our sortedArray
const sortedArray = Object.keys(summed).sort().map((val) => {
return summed[val];
});
console.log("sortedArray", sortedArray);
};
sumTransactions(transactions);
I experimented a bit and came up with this solution:
var transactions = [
{
date: "2017-11-17",
price: "28",
},
{
date: "2017-12-17",
price: "22",
},
{
date: "2017-12-17",
price: "20",
}
]
var sumedUpDates = [];
var prices = [];
function isDateSumedUp(date) {
return sumedUpDates.indexOf(date.substring(0, 7)) !== -1;
}
function sumUpDate(date) {
var sum = 0;
transactions.forEach(t => {
if(t.date.substring(0, 7) === date.substring(0, 7)) {
sum += parseInt(t.price);
}
});
sumedUpDates.push(date.substring(0, 7));
prices.push(sum);
}
transactions.forEach(t => {
if(!isDateSumedUp(t.date)) {
sumUpDate(t.date);
}
});
var obj = {};
sumedUpDates.forEach((d, i) => obj[d] = prices[i]);
console.log(obj);
This solutions uses map to format your dates into year/month format for each object entry and then reduce to sum them by those separated dates.
const transactions = [
{date:"2017-11-17", price: "28",},
{date:"2017-12-17", price: "28",},
{date:"2017-11-17", price: "20",},
{date:"2017-12-17", price: "2",},
{date:"2017-11-17", price: "58",},
{date:"2017-11-17", price: "8",},
{date:"2017-10-17", price: "30",},
{date:"2018-11-17", price: "1",},
];
const mapper = single => {
let d = single.date.split('-');
let p = Number(single.price);
return { year: d[0], month: d[1], price: p };
}
const reducer = (group, current) => {
let i = group.findIndex(single => (single.year == current.year && single.month == current.month));
if (i == -1) {
return [ ...group, current ];
}
group[i].price += current.price;
return group;
};
const sumPrices = transactions.map(mapper).reduce(reducer, []);
console.log(sumPrices);
var array = [];
for (var i = 0; i < transactions.length; i++) {
var date = new Date(transactions[i].date);
var ym = date.getFullYear() + "-" + date.getMonth();
if (array[ym] == null) {
array[ym] = 0;
}
array[ym] += parseInt(transactions[i].price);
}
With this data
var transactions = [{
date: "2017-11-17",
price: "28",
},
{
date: "2017-12-17",
price: "5",
},
{
date: "2016-02-17",
price: "28",
},
{
date: "2015-11-17",
price: "25",
},
{
date: "2016-02-17",
price: "12",
},
{
date: "2017-11-17",
price: "50",
}
];
This will give you the sum of all of the year-months duplicates like this :
[
2017-10: 78,
2017-11: 5,
2016-1: 40,
2015-10: 25
]
Another solution is reduce:
var transactions = [
{date: "2017-11-17",price: "28"},
{date: "2017-12-17",price: "22"},
{date: "2017-12-17",price: "20"}
];
var result = transactions.reduce(function(acc, obj) {
var key = obj.date.substr(0,7);
acc[key] = (acc[key] || 0) + +obj.price;
return acc;
}, Object.create(null));
console.log(result);
I have an array arr of objects, each object is of the form:
obj={id: /*some string*/, //id is unique
msgDetails: { content: /*some string*/,time : /*number*/ }
}
In order to get an index of a specific element by its id value ,I use the following:
var idIndex=Babble.messages.findIndex(function(element){
return element.id===num;
});
Is there a way to get all the indexes of the elements in arr that has an id>=num where num is a given number ,without for loop?
You can use filter instead of for:
data.filter(d => Number(d.id) > id);
var data = [{
id: "1",
msgDetails: {
content: "abc1",
time: 1
}
},{
id: "2",
msgDetails: {
content: "abc2",
time: 1
}
},{
id: "3",
msgDetails: {
content: "abc3",
time: 1
}
},{
id: "4",
msgDetails: {
content: "abc4",
time: 1
}
}];
var filterData = function(id) {
return data.filter(d => Number(d.id) > id);
};
console.log(filterData(2));
// Another way
var filterId = function(cond) {
return data.filter(d => cond(Number(d.id)));
};
console.log(filterId(id => id > 2));
You can .map() and .filter() the collection to get the indexes want.
var ids = Babble.messages.map((e, i) => [+e.id, i])
.filter(a => a[0] >= num)
.map(a => a[1]);
You would first use map to get the indexes and then chain filter to that:
var Babble = {
messages: [{ id: "1", msgDetails: { content: "abc", time: 10 }},
{ id: "3", msgDetails: { content: "word", time: 15 }},
{ id: "5", msgDetails: { content: "phrase", time: 12 }},
{ id: "7", msgDetails: { content: "test", time: 21 }}]
};
var num = 4;
var idIndexes = Babble.messages.map( (el, i) => el.id >= num ? i : null )
.filter(i => i !== null);
console.log('indexes with id-values greater or equal than ' + num + ':');
console.log(idIndexes);
This will log the indexes of the items with an ID equal to or larger than the specified ID.
var messages = [
{ id: 10 },
{ id: 12 },
{ id: 2 },
{ id: 20 },
{ id: 30 }
];
function getIndexesForId(id) {
// Create a result array
var indexes = [];
// Loop over all messages.
messages.forEach((item, index) => {
// Check if message ID is equal to or larger than requested ID.
if (item.id >= id) {
// Push the index of the current item into the result array.
indexes.push(index);
}
});
// Return the array.
return indexes;
}
console.log(getIndexesForId(10));
console.log(getIndexesForId(20));