Process an array of sequential boolean values - javascript

Hard to set a title and a description for this one, but you'll get it when u read the code and comments, hopefully. If you guys got better idea please edit.
How can i get, in javascript, the timestamp sum difference between each true and false for each day for this particular example?
As a general rule for the array: is it always loops true - false.
My real data is a bit more complex, but i just can't seem to get the thinking right, even for this simplified example.
Can be reduce, can be a for loop, anything. Thank you!
const data = [
{ day: 'today', timestamp: 11, value: true },
{ day: 'today', timestamp: 13, value: false }, //here should be 13-11
{ day: 'today', timestamp: 14, value: true },
{ day: 'today', timestamp: 17, value: false }, //here should be 17-14
//the sum for today should be 5 (13-11 + 17-14)
{ day: 'tomorrow', timestamp: 9, value: true },
{ day: 'tomorrow', timestamp: 11, value: false }, //here should be 11-9
{ day: 'tomorrow', timestamp: 11, value: true },
{ day: 'tomorrow', timestamp: 16, value: false } //here should be 16-11
//the sum for today should be 7 (11-9 + 16-11)
]

A simple reduce would make the job: just add the 'false' values which are bigger, and remove from the sum the 'true' values and you got it ;)
const data = [
{ day: 'today', timestamp: 11, value: true },
{ day: 'today', timestamp: 13, value: false }, //here should be 13-11
{ day: 'today', timestamp: 14, value: true },
{ day: 'today', timestamp: 17, value: false }, //here should be 17-14
//the sum for today should be 5 (13-11 + 17-14)
{ day: 'tomorrow', timestamp: 9, value: true },
{ day: 'tomorrow', timestamp: 11, value: false }, //here should be 11-9
{ day: 'tomorrow', timestamp: 11, value: true },
{ day: 'tomorrow', timestamp: 16, value: false } //here should be 16-11
//the sum for today should be 7 (11-9 + 16-11)
];
const result = data.reduce((acc, elt) => {
if(!acc[elt.day]) acc[elt.day] = 0;
if(!elt.value) {
acc[elt.day] += elt.timestamp;
} else {
acc[elt.day] -= elt.timestamp;
}
return acc;
},{});
console.log(result);
Do not forget to init each day to 0 in the accumulator!
Hoping this will help.

You can do it with reduce easily
const data = [
{ day: 'today', timestamp: 11, value: true },
{ day: 'today', timestamp: 13, value: false },
{ day: 'today', timestamp: 14, value: true },
{ day: 'today', timestamp: 17, value: false },
{ day: 'tomorrow', timestamp: 9, value: true },
{ day: 'tomorrow', timestamp: 11, value: false },
{ day: 'tomorrow', timestamp: 11, value: true },
{ day: 'tomorrow', timestamp: 16, value: false }
]
const op = data.reduce((o,c)=>{
if(o[c['day']]){
o[c['day']]['timestamp'] += c.value ? -c.timestamp : c.timestamp;
} else {
o[c['day']] = {
'timestamp' : c.value ? -c.timestamp : c.timestamp
}
}
return o;
},{})
console.log(op)

You could take a check with the last inserted object in the result set and update timestamp.
const
data = [{ day: 'today', timestamp: 11, value: true }, { day: 'today', timestamp: 13, value: false }, { day: 'today', timestamp: 14, value: true }, { day: 'today', timestamp: 17, value: false }, { day: 'tomorrow', timestamp: 9, value: true }, { day: 'tomorrow', timestamp: 11, value: false }, { day: 'tomorrow', timestamp: 11, value: true }, { day: 'tomorrow', timestamp: 16, value: false }],
result = data.reduce((r, { day, timestamp, value }) => {
var last = r[r.length - 1];
if (!last || last.day !== day) {
r.push(last = { day, timestamp: 0 });
}
last.timestamp += value ? -timestamp : +timestamp;
return r;
}, []);
console.log(result);

Related

I want to turn a javascript array to create a new array

I want to display a graph of the total count for groupA and groupB for each month.
The graph library uses chart.js
I want to put the sum of the counts for each month in data:[].
I want to turn the array of values to be retrieved from the data to determine groupA and groupB, and put the count for each month into data
script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"
javascript:
var users = #{raw #user_counts.to_json}
console.log(users)
var ct = document.getElementById('ex_chart');
var ex_chart = new Chart(ct, {
type: 'horizontalBar',
data: {
labels: ["Jan", "Feb", "Mar", "Apr", "May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
datasets: [
{
label: 'groupA',
data: [],
backgroundColor: '#C7CED7'
},
{
label: 'groupB',
data: [],
backgroundColor: '#0068B4'
}
]
},
options: options
})
Contents of users
[
{
activity_type: "groupA"
count: 10
created_at: "2021-01-14T13:46:18.000Z"
id: 1
year: 2020
month: "Jan"
updated_at: "2021-01-14T13:46:18.000Z"
},
{
activity_type: "groupA"
count: 8
created_at: "2021-01-14T13:46:18.000Z"
id: 2
year: 2020
month: "Feb"
updated_at: "2021-01-14T13:46:18.000Z"
},
{
activity_type: "groupB"
count: 8
created_at: "2021-01-14T13:46:18.000Z"
id: 3
year: 2020
month: "Feb"
updated_at: "2021-01-14T13:46:18.000Z"
}
]
In technical terms, you want to group your user counts by two parameters: 'activityType' and 'month'.
Below is a solution using functional programming. You may modify keys, for example to add 'year' parameter, which actually makes sense.
const users = [
{
activity_type: "groupA",
count: 10,
year: 2020,
month: 1
},
{
activity_type: "groupA",
count: 17,
year: 2019,
month: 2,
},
{
activity_type: "groupA",
count: 8,
year: 2020,
month: 2,
},
{
activity_type: "groupB",
count: 8,
year: 2020,
month: 1,
}
];
const keys = ['activity_type', 'month'];
function matches(table, entry, keys) { // finds item with same values
return table.find(e => keys.every(k => e[k] == entry[k]));
}
const usersGroupedByKeys = users.reduce((cur, val) => {
let alreadyIn = matches(cur, val, keys);
if (alreadyIn) {
alreadyIn['count'] = alreadyIn['count'] + val['count'];
} else {
cur.push(val);
}
return cur;
}, []);
console.log(usersGroupedByKeys);
Check the Docs.

Group an array of time object

I have a requirement to group an array of objects based on time interval. The input looks like:
[
{
_id: {
hour: 0,
interval: '0'
},
time: '0:0',
count: 10
},
{
_id: {
hour: 0,
interval: '15'
},
time: '0:15',
count: 5
},
{
_id: {
hour: 0,
interval: '30'
},
time: '0:30',
count: 1
},
{
_id: {
hour: 0,
interval: '45'
},
time: '0:45',
count: 2
},
{
_id: {
hour: 1,
interval: '0'
},
time: '1:0',
count: 4
},
{
_id: {
hour: 1,
interval: '15'
},
time: '1:15',
count: 3
},
{
_id: {
hour: 1,
interval: '30'
},
time: '1:30',
count: 5
},
{
_id: {
hour: 1,
interval: '45'
},
time: '1:45',
count: 1
}
]
My desired output:
[
{
"time": "0",
"0": 10,
"15": 5
"30": 1,
"45": 2
},
{
"time": "1",
"0": 4,
"15": 3
"30": 5,
"45": 1
}
]
I tried to use the following code to group the objects, which works to an extent, but I'm stuck on what to do next:
const a = [ { _id: { hour: 0, interval: '0' }, time: '0:0', count: 10 }, { _id: { hour: 0, interval: '15' }, time: '0:15', count: 5 }, { _id: { hour: 0, interval: '30' }, time: '0:30', count: 1 }, { _id: { hour: 0, interval: '45' }, time: '0:45', count: 2 }, { _id: { hour: 1, interval: '0' }, time: '1:0', count: 4 }, { _id: { hour: 1, interval: '15' }, time: '1:15', count: 3 }, { _id: { hour: 1, interval: '30' }, time: '1:30', count: 5 }, { _id: { hour: 1, interval: '45' }, time: '1:45', count: 1 }]
var group = a.reduce((r, a) => {
console.log("a", a);
console.log('r', r);
r[a._id.hour] = [...r[a._id.hour] || [], a];
return r;
}, {});
console.log("group", group);
Check if the object with that hour exists in the accumulator object first - if it doesn't, create one, then assign count to that object's [interval] property, and get the Object.values at the end to turn it back into an array:
const input=[{_id:{hour:0,interval:"0"},time:"0:0",count:10},{_id:{hour:0,interval:"15"},time:"0:15",count:5},{_id:{hour:0,interval:"30"},time:"0:30",count:1},{_id:{hour:0,interval:"45"},time:"0:45",count:2},{_id:{hour:1,interval:"0"},time:"1:0",count:4},{_id:{hour:1,interval:"15"},time:"1:15",count:3},{_id:{hour:1,interval:"30"},time:"1:30",count:5},{_id:{hour:1,interval:"45"},time:"1:45",count:1}];
const groupedObj = {};
for (const { _id: { hour, interval }, count } of input) {
if (!groupedObj[hour]) {
groupedObj[hour] = { time: hour };
}
groupedObj[hour][interval] = count;
}
const output = Object.values(groupedObj);
console.log(output);
Reduce the array, and create an object for each _id.time. Assign the current [interval] = count to the object. Get the entries, and use Array.from() to convert the entries to an array of the required form:
const arr = [{"_id":{"hour":0,"interval":"0"},"time":"0:0","count":10},{"_id":{"hour":0,"interval":"15"},"time":"0:15","count":5},{"_id":{"hour":0,"interval":"30"},"time":"0:30","count":1},{"_id":{"hour":0,"interval":"45"},"time":"0:45","count":2},{"_id":{"hour":1,"interval":"0"},"time":"1:0","count":4},{"_id":{"hour":1,"interval":"15"},"time":"1:15","count":3},{"_id":{"hour":1,"interval":"30"},"time":"1:30","count":5},{"_id":{"hour":1,"interval":"45"},"time":"1:45","count":1}];
// convert the entries to an array
const result = Array.from(Object.entries(
arr.reduce((r, o) => {
const { hour, interval } = o._id; // get the hour and interval
if(!r[hour]) r[hour] = {}; // create a the hour object
r[hour][interval] = o.count; // add the interval and count
return r;
}, {})
), ([time, values]) => ({ time, ...values })); // generate the result objects
console.log(result)
You can group object by reduce method. So at first you need to group by hour and then just add interval properties from each iteration of reduce method to the hour property:
const result = arr.reduce((a, c) => {
a[c._id.hour] = a[c._id.hour] || {};
a[c._id.hour].time = c._id.hour;
a[c._id.hour][c._id.interval] = c.count;
return a;
}, {})
console.log(result);
An example:
let arr = [
{
_id: {
hour: 0,
interval: '0'
},
time: '0:0',
count: 10
},
{
_id: {
hour: 0,
interval: '15'
},
time: '0:15',
count: 5
},
{
_id: {
hour: 0,
interval: '30'
},
time: '0:30',
count: 1
},
{
_id: {
hour: 0,
interval: '45'
},
time: '0:45',
count: 2
},
{
_id: {
hour: 1,
interval: '0'
},
time: '1:0',
count: 4
},
{
_id: {
hour: 1,
interval: '15'
},
time: '1:15',
count: 3
},
{
_id: {
hour: 1,
interval: '30'
},
time: '1:30',
count: 5
},
{
_id: {
hour: 1,
interval: '45'
},
time: '1:45',
count: 1
}
]
const result = arr.reduce((a, c) => {
a[c._id.hour] = a[c._id.hour] || {};
a[c._id.hour].time = c._id.hour;
a[c._id.hour][c._id.interval] = c.count;
return a;
}, {})
console.log(result);

How can I format the response coming from the server as given below using js array methods such as map(), reduce(), filter() and etc?

This is the response object from an API that I need to reformat:
const data = {
id: 12,
currency: "USD",
transactions: [
{
from_wallet_id: 14,
transaction_amount: '30.00',
type: 'income',
"date": "2019-12-04T06:45:49.394000+06:00"
},
{
from_wallet_id: 11,
transaction_amount: '50.00',
type: 'expenses',
date: "2019-12-04T06:45:49.394000+06:00"
},
{
from_wallet_id: 11,
transaction_amount: '70.00',
type: 'transfer',
date: "2019-12-06T06:45:49.394000+06:00"
},
{
from_wallet_id: 14,
transaction_amount: '40.00',
type: 'transfer',
date: "2019-12-08T06:45:49.394000+06:00"
},
]
}
I need to change it to the example given below (using array methods such as reduce, map, filter, etc)
A little bit information:
income is sum of transactions at that date.
expenses is sum of transactions at that date.
type "transfer" can be income or expense
If type is "transfer" and from_wallet_id is not equals to data.id
then it will be added to income otherwise it will be added to expenses
formattedObject = {
"2019-12-04": {
date: "2019-12-04",
income: 30,
expenses: 50
},
"2019-12-06": {
date: "2019-12-06",
income: 0,
expenses: 70
},
"2019-12-08": {
date: "2019-12-08",
income: 40,
expenses: 0
}
}
You could reduce the array nad take an adjustment for type: 'transfer'.
const
data = { id: 12, currency: "USD", transactions: [{ from_wallet_id: 14, transaction_amount: '30.00', type: 'income', date: "2019-12-04T06:45:49.394000+06:00" }, { from_wallet_id: 11, transaction_amount: '50.00', type: 'expenses', date: "2019-12-04T06:45:49.394000+06:00" }, { from_wallet_id: 11, transaction_amount: '70.00', type: 'transfer', date: "2019-12-06T06:45:49.394000+06:00" }, { from_wallet_id: 14, transaction_amount: '40.00', type: 'transfer', date: "2019-12-08T06:45:49.394000+06:00" }] },
result = data.transactions.reduce((r, { from_wallet_id, transaction_amount, type, date }) => {
date = date.slice(0, 10);
if (type === 'transfer') type = data.id === from_wallet_id ? 'expenses' : 'income';
r[date] = r[date] || { date, income: 0, expenses: 0 };
r[date][type] += +transaction_amount;
return r;
}, {});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Using functional programming to parse opening hours from Google Places api

Is there someone who is familiar with functional programming in javascript that can help me parse this data from google place API:
`periods: [
{ close: { day: 1, time: '1400' }, open: { day: 1, time: '1100' } },
{ close: { day: 1, time: '2200' }, open: { day: 1, time: '1900' } },
{ close: { day: 2, time: '1400' }, open: { day: 2, time: '1100' } },
{ close: { day: 2, time: '2200' }, open: { day: 2, time: '1900' } },
{ close: { day: 3, time: '1400' }, open: { day: 3, time: '1100' } },
{ close: { day: 3, time: '2200' }, open: { day: 3, time: '1900' } },
{ close: { day: 4, time: '1400' }, open: { day: 4, time: '1100' } },
{ close: { day: 4, time: '2200' }, open: { day: 4, time: '1900' } },
{ close: { day: 5, time: '1400' }, open: { day: 5, time: '1100' } },
{ close: { day: 5, time: '2200' }, open: { day: 5, time: '1900' } },
{ close: { day: 6, time: '2200' }, open: { day: 6, time: '1100' } },
];`
so each entry in the array is an object. The object contains open and close object that represent the day of the week and opening/closing hour.
This place is open from
11:00 - 14:00 and 19:00 - 22:00 during weekdays.
11:00 - 22:00 on Saturdays.
Closed on Sundays because no entry has got the day : 0.
How can I use functional programming to parse this array into an array like this:
`openingHours = [
"Sundays: Closed"
"Mondays: 11:00 - 14:00 and 19:00 - 22:00",
"Tuesdays: 11:00 - 14:00 and 19:00 - 22:00",
"Wednesdays: 11:00 - 14:00 and 19:00 - 22:00",
"Thursdays: 11:00 - 14:00 and 19:00 - 22:00",
"Fridays: 11:00 - 14:00 and 19:00 - 22:00",
"Saturdays: 11:00 - 22:00"
]`
My attempt: https://jsfiddle.net/4u53tb1p/1/
const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
const openingHours =
periods.map(p => ({
day: p.open.day,
time: `${p.open.time} - ${p.close.time}`
}))
.reduce((acc, current) => {
let time = acc[current.day];
time.push(current.time);
return Object.assign([], acc, { [current.day]: time });
}, days.map(d => []))
.map((p, index) => {
const status = p.length == 0 ? "Closed" : p.join(" and ");
return `${days[index]}: ${status}`;
});
Breakdown:
The first map() convert from initial structure to:
[
{day: 1, time: "1100 - 1400"},
{day: 1, time: "1900 - 2200"},
{day: 2, time: "1100 - 1400"},
{day: 2, time: "1900 - 2200"},
{day: 3, time: "1100 - 1400"},
{day: 3, time: "1900 - 2200"},
{day: 4, time: "1100 - 1400"},
{day: 4, time: "1900 - 2200"},
{day: 5, time: "1100 - 1400"},
{day: 5, time: "1900 - 2200"},
{day: 6, time: "1100 - 2200"}
]
The reduce takes objects that have the same day number, group them together (.i.e. putting their time in the same array):
[
[],
["1100 - 1400", "1900 - 2200"],
["1100 - 1400", "1900 - 2200"],
["1100 - 1400", "1900 - 2200"],
["1100 - 1400", "1900 - 2200"],
["1100 - 1400", "1900 - 2200"],
["1100 - 2200"]
]
Notice that I provide a nested array with the same length as days array to the reducer() function as initial accumulator. This is to pad the previous map() output with any day that doesn't have entry (.i.e. Sunday in this case). Sunday doesn't have any entry, so at the end of reduce(), the value is empty array
And final step is to transform the previous nested array into desired output, which is quite straightforward: map the index to days array to get the label of the day
This solution is not using any 3rd party library
Here is a solution.
First we have to add a MyGroupBy function
var groupBy = function(xs, key1, key2) {
return xs.reduce(function(rv, x) {
(rv[x[key1][key2]] = rv[x[key1][key2]] || []).push(x);
return rv;
}, {});
};
let periods = [
{ close: { day: 1, time: '1400' }, open: { day: 1, time: '1100' } },
{ close: { day: 1, time: '2200' }, open: { day: 1, time: '1900' } },
{ close: { day: 2, time: '1400' }, open: { day: 2, time: '1100' } },
{ close: { day: 2, time: '2200' }, open: { day: 2, time: '1900' } },
{ close: { day: 3, time: '1400' }, open: { day: 3, time: '1100' } },
{ close: { day: 3, time: '2200' }, open: { day: 3, time: '1900' } },
{ close: { day: 4, time: '1400' }, open: { day: 4, time: '1100' } },
{ close: { day: 4, time: '2200' }, open: { day: 4, time: '1900' } },
{ close: { day: 5, time: '1400' }, open: { day: 5, time: '1100' } },
{ close: { day: 5, time: '2200' }, open: { day: 5, time: '1900' } },
{ close: { day: 6, time: '2200' }, open: { day: 6, time: '1100' } },
];
invok our function "MyGroupBy"
openingHoursArr = myGroupBy(periods, 'open', 'day');
openingHours must be an object
let openingHours = {
"Sundays": openingHoursArr[0] ? openingHoursArr[0].map( i => i.open.time+ ' - ' +i.close.time ).join(' and ') : "Closed",
"Mondays": openingHoursArr[1] ? openingHoursArr[1].map( i => i.open.time+ ' - ' +i.close.time ).join(' and ') : "Closed",
"Tuesdays": openingHoursArr[2] ? openingHoursArr[2].map( i => i.open.time+ ' - ' +i.close.time ).join(' and ') : "Closed",
"Wednesdays": openingHoursArr[3] ? openingHoursArr[3].map( i => i.open.time+ ' - ' +i.close.time ).join(' and ') : "Closed",
"Thursdays": openingHoursArr[4] ? openingHoursArr[4].map( i => i.open.time+ ' - ' +i.close.time ).join(' and ') : "Closed",
"Fridays": openingHoursArr[5] ? openingHoursArr[5].map( i => i.open.time+ ' - ' +i.close.time ).join(' and ') : "Closed",
"Saturdays": openingHoursArr[6] ? openingHoursArr[6].map( i => i.open.time+ ' - ' +i.close.time ).join(' and ') : "Closed"
};
enjoy.
Here's another options...
var data = {periods: [
{ close: { day: 1, time: '1400' }, open: { day: 1, time: '1100' } },
{ close: { day: 1, time: '2200' }, open: { day: 1, time: '1900' } },
{ close: { day: 2, time: '1400' }, open: { day: 2, time: '1100' } },
{ close: { day: 2, time: '2200' }, open: { day: 2, time: '1900' } },
{ close: { day: 3, time: '1400' }, open: { day: 3, time: '1100' } },
{ close: { day: 3, time: '2200' }, open: { day: 3, time: '1900' } },
{ close: { day: 4, time: '1400' }, open: { day: 4, time: '1100' } },
{ close: { day: 4, time: '2200' }, open: { day: 4, time: '1900' } },
{ close: { day: 5, time: '1400' }, open: { day: 5, time: '1100' } },
{ close: { day: 5, time: '2200' }, open: { day: 5, time: '1900' } },
{ close: { day: 6, time: '2200' }, open: { day: 6, time: '1100' } },
]};
var days = "Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday".split(", ");
var hours = {};
days.map(itm => hours[itm] = `${itm}s: Closed`);
data = data.periods.map(itm => ({
day: days[itm.close.day],
open: itm.open.time.match(/\d{2}/g).join(":"),
close: itm.close.time.match(/\d{2}/g).join(":")
})).reduce((acc, itm) => {
let str = `${itm.day}s: ${itm.open} - ${itm.open}`;
acc[itm.day] = ~acc[itm.day].indexOf('Closed') ? str : acc[itm.day] + ` and ${str}`;
return acc;
}, hours);
data = days.map(itm => data[itm]);
console.log(data);
Here's yet another options...
const DAYS =
[ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ]
const format = (open, close) =>
`${open.time} - ${close.time}`
const main = (periods) =>
{
const hours =
periods.reduce ((m, { open, close }) =>
m.has (open.day)
? m.set (open.day, [ ...m.get (open.day), format (open, close) ])
: m.set (open.day, [ format (open, close) ])
, new Map ())
return DAYS.map ((name, day) =>
hours.has (day)
? name + ': ' + hours.get (day) .join (' and ')
: name + ': ' + 'Closed')
}
const data =
[ { close : { day : 1, time : "1400" }, open : { day : 1, time : "1100" } }
, { close : { day : 1, time : "2200" }, open : { day : 1, time : "1900" } }
, { close : { day : 2, time : "1400" }, open : { day : 2, time : "1100" } }
, { close : { day : 2, time : "2200" }, open : { day : 2, time : "1900" } }
, { close : { day : 3, time : "1400" }, open : { day : 3, time : "1100" } }
, { close : { day : 3, time : "2200" }, open : { day : 3, time : "1900" } }
, { close : { day : 4, time : "1400" }, open : { day : 4, time : "1100" } }
, { close : { day : 4, time : "2200" }, open : { day : 4, time : "1900" } }
, { close : { day : 5, time : "1400" }, open : { day : 5, time : "1100" } }
, { close : { day : 5, time : "2200" }, open : { day : 5, time : "1900" } }
, { close : { day : 6, time : "2200" }, open : { day : 6, time : "1100" } }
]
console.log (main (data))
This solution is not using any 3rd party library 📚
So here is how I solved it. I used the solution from #Phuong Nguyen.
I also found out that if a place is open 24/7 then the data is represented as:
`periods = [
{open: {day: 0, time: "0000"}}
]`
So I had to add that into the getOpeningHours function.
Because the application is in Icelandic the days are in Icelandic (you can learn a little bit of Icelandic if you like)
`const days = [
'Sunnudagar',
'Mánudagar',
'Þriðjudagar',
'Miðvikudagar',
'Fimmtudagar',
'Föstudagar',
'Laugardagar',
];`
`const createTimeStrings = item => {
return {
day: item.open.day,
time: ``${item.open.time
.split('')
.reduce(addColon, [])
.join('')} - ${item.close.time
.split('')
.reduce(addColon, [])
.join('')}``,
};
};`
`const addColon = (acc, curr, index) =>
index === 2 ? [...acc, ':', curr] : [...acc, curr];`
This function changes the data structure to:
`[
{day: 1, time: "11:00 - 14:00"},
{day: 1, time: "19:00 - 22:00"},
{day: 2, time: "11:00 - 14:00"},
]`
`const getHoursbyDay = (acc, curr) => {
let time = acc[curr.day];
let newTime = [...time, curr.time];
return [...acc.slice(0, curr.day), newTime, ...acc.slice(curr.day + 1)];
};`
This function changes the data to :
`[
[],
["1100 - 1400", "1900 - 2200"],
["1100 - 1400", "1900 - 2200"],
["1100 - 1400", "1900 - 2200"],
["1100 - 1400", "1900 - 2200"],
["1100 - 1400", "1900 - 2200"],
["1100 - 2200"]
]`
The main function that uses the other helper functions and deals with when places are open 24/7
`export const getOpeningHours = periods => {
/* Clients can rely on always-open being represented
as an open period containing day with value 0 and
time with value 0000, and no close.
*/
if (
periods.length === 1 &&
periods[0].open.day === 0 &&
periods[0].open.time === '0000'
) {
return ['Opið allan sólarhringinn alla daga'];
}
// otherwise, parse openinghours
return periods
.map(createTimeStrings)
.reduce(getHoursbyDay, days.map(d => []))
.map((arr, index) => {
const hours = arr.length === 0 ? 'Lokað' : arr.join(' og ');
return `${days[index]}: ${hours}`;
});
};`

time series and aggregation framework (mongo)

I'm trying to synchronise two functions I run in my app.
First one checks the count of the documents I save to MongoDB every time block (e.g. every 10 seconds) in the real time:
var getVolume = function(timeBlock, cb) {
var triggerTime = Date.now();
var blockPeriod = triggerTime - timeBlock;
Document.find({
time: { $gt: blockPeriod }
}).count(function(err, count) {
log('getting volume since ', new Date(blockPeriod), 'result is', count)
cb(triggerTime, count);
});
};
and then I have the second function which I use whenever I want to get a data for my graph (front end):
var getHistory = function(timeBlock, end, cb) {
Document.aggregate(
{
$match: {
time: {
$gte: new Date(end - 10 * timeBlock),
$lt: new Date(end)
}
}
},
// count number of documents based on time block
// timeBlock is divided by 1000 as we use it as seconds here
// and the timeBlock parameter is in miliseconds
{
$group: {
_id: {
year: { $year: "$time" },
month: { $month: "$time" },
day: { $dayOfMonth: "$time" },
hour: { $hour: "$time" },
minute: { $minute: "$time" },
second: { $subtract: [
{ $second: "$time" },
{ $mod: [
{ $second: "$time" },
timeBlock / 1000
]}
]}
},
count: { $sum: 1 }
}
},
// changing the name _id to timeParts
{
$project: {
timeParts: "$_id",
count: 1,
_id: 0
}
},
// sorting by date, from earliest to latest
{
$sort: {
"time": 1
}
}, function(err, result) {
if (err) {
cb(err)
} else {
log("start", new Date(end - 10 * timeBlock))
log("end", new Date(end))
log("timeBlock", timeBlock)
log(">****", result)
cb(result)
}
})
}
and the problem is that I can't get the same values on my graph and on the back-end code (getVolume function)
I realised that the log from getHistory is not how I would expect it to be (log below):
start Fri Jul 18 2014 11:56:56 GMT+0100 (BST)
end Fri Jul 18 2014 11:58:36 GMT+0100 (BST)
timeBlock 10000
>**** [ { count: 4,
timeParts: { year: 2014, month: 7, day: 18, hour: 10, minute: 58, second: 30 } },
{ count: 6,
timeParts: { year: 2014, month: 7, day: 18, hour: 10, minute: 58, second: 20 } },
{ count: 3,
timeParts: { year: 2014, month: 7, day: 18, hour: 10, minute: 58, second: 10 } },
{ count: 3,
timeParts: { year: 2014, month: 7, day: 18, hour: 10, minute: 58, second: 0 } },
{ count: 2,
timeParts: { year: 2014, month: 7, day: 18, hour: 10, minute: 57, second: 50 } } ]
So I would expect that the getHistory should look up data in mongo every 10 seconds starting from start Fri Jul 18 2014 11:56:56 GMT+0100 (BST) so it will look roughly like:
11:56:56 count: 3
11:57:06 count: 0
11:57:16 count: 14
... etc.
TODO:
1. I know I should cover in my aggregate function the case when the count is 0 at the moment I guess this time is skipped`
Your error is how you're calculating _id for $group operator, specifically its second part:
second: { $subtract: [
{ $second: "$time" },
{ $mod: [
{ $second: "$time" },
timeBlock / 1000
]}
]}
So, instead of splitting all your data into 10 timeBlock milliseconds long chunks starting from new Date(end - 10 * timeBlock), you're splitting it into 11 chunks starting from from the nearest divisor of timeBlock.
To fix it you should first calculate delta = end - $time and then use it instead of the original $time to build your _id.
Here is an example of what I mean:
Document.aggregate({
$match: {
time: {
$gte: new Date(end - 10 * timeBlock),
$lt: new Date(end)
}
}
}, {
$project: {
time: 1,
delta: { $subtract: [
new Date(end),
"$time"
]}
}
}, {
$project: {
time: 1,
delta: { $subtract: [
"$delta",
{ $mod: [
"$delta",
timeBlock
]}
]}
}
}, {
$group: {
_id: { $subtract: [
new Date(end),
"$delta"
]},
count: { $sum: 1 }
}
}, {
$project: {
time: "$_id",
count: 1,
_id: 0
}
}, {
$sort: {
time: 1
}
}, function(err, result) {
// ...
})
I also recommend you to use raw time values (in milliseconds), because it's much easier and because it'll keep you from making a mistake. You could cast time into timeParts after $group using $project operator.

Categories

Resources