Efficiently grouping data based on date and day-of-week - javascript

Given a list of items sorted in descending order by date (most recent date at index 0). Pls note that we may not have data for all dates.
[
{price: 10, dt: "2017-02-13"},
{price: 25, dt: "2017-02-05"},
{price: 15, dt: "2017-01-15"},
...
..
]
I am attempting to group this data into weeks which I was able to do successfully using a "while" loop.
See Plnkr here
I am not currently able to use constructs like _.groupBy() cause the grouping clause is constantly moving .. (we we have to group based on weeks and a week could be Mn-Sun or Tues-Mn etc)
is there a more elegant solution to this ?

This code will create array of arrays representing each week:
const data = [
{ price: 10, dt: "2017-02-13" },
{ price: 10, dt: "2017-02-12" },
{ price: 10, dt: "2017-02-11" },
{ price: 25, dt: "2017-02-05" },
{ price: 15, dt: "2017-01-15" }
]
const group = (data) => {
let date = new Date(data[0].dt)
date.setTime -= date.getDay() * 24 * 60 * 60 * 1000
let grup = []
let newData = []
for (let i in data) {
const dif = Math.floor(((date - new Date(data[i].dt)) / 24 / 1000 / 60 / 60) / 7)
if (newData[dif]) {
newData[dif].push(data[i])
} else {
newData[dif] = [data[i]]
}
}
data.push(grup)
return newData
}
console.log(group(data))
note if data of week not found value would be undefined

Related

Biweekly View with DHTMLX Gantt Chart

In my dhtmlx gantt chart, I've configured my scales to show the year and Quarter (3 PIs per quarter)
gantt.config.scales = [
{ name: 'year', format: '%Y' },
{ name: 'quarter', template: date => `PI-${(Math.floor((new Date(date).getMonth() / 3)) + 1 )}` },
]
This gives me the result
| 2022 |
| PI-1 | PI-2 | PI-3 |
I now want to add 2-week increments to my scale to represent sprints per Quarter (6 sprints per quarter, 24 sprints per year)
| 2022 |
| PI-1 | PI-2 | PI-3 |
| S1 | S2 | S3 | S4 | S5 | S6 |
I can't figure out how to structure my template to achieve this. Any thoughts?
It should be noted that if the increment is 2 weeks, then there will be more than 24 sprints in a year. The same can be said about months, one quarter is equal to 3 months, which contain a non-integer number of weeks, so the boundaries of the quarter and sprint scales cannot coincide.
To set sprints in the scale, you need to use Custom time units.
And so I can offer the following solution:
If the increment is to be strictly 2 weeks, first determine the start date of each sprint. It is also necessary to set the start for the first sprint, for example, I set it to start on Monday of the first week of the year:
// specify the start date of the first sprint
gantt.config.project_start = new Date(2022, 0, 3);
// find a sprint for a given date
function findSprint(date) {
const firstSprint = gantt.date.week_start(new Date(gantt.config.project_start));
let currentDate = firstSprint;
let direction = 1;
if (date < firstSprint) {
direction = -1;
}
const increment = 2 * direction;
let nextDate = gantt.date.add(currentDate, increment, "week");
let num = 0;
while (!(currentDate.valueOf() <= date.valueOf() && nextDate.valueOf() > date.valueOf())) {
if (increment > 0) {
currentDate = nextDate;
nextDate = gantt.date.add(currentDate, increment, "week");
} else {
nextDate = currentDate;
currentDate = gantt.date.add(currentDate, increment, "week");
}
num += 1 * direction;
}
return {
sprintStart: currentDate,
sprintEnd: nextDate,
sprintNumber: num
}
}
// custom scale unit definition
gantt.date.sprint_start = function (date) {
return findSprint(date).sprintStart;
};
The next step is to specify that the increment will be two weeks:
gantt.date.add_sprint = function (date, inc) {
return gantt.date.add(gantt.date.sprint_start(date), inc * 2, "week");
};
And finally, add a new unit to the scale:
gantt.config.scales = [
{ unit: "year", step: 1, format: "%Y" },
{
unit: 'quarter',
format: date => {
return `PI-${(Math.floor((new Date(date).getMonth() / 3)) + 1)}`
}
},
{ unit: 'sprint', step: 1, template: function (date) {
const sprintInfo = findSprint(date);
return `Sprint ${sprintInfo.sprintNumber + 1}, (${gantt.templates.date_grid(sprintInfo.sprintStart)} - ${gantt.templates.date_grid(new Date(sprintInfo.sprintEnd - 1))})`
}
}
];
Please see an example: https://snippet.dhtmlx.com/15u2bd85.
Sometimes for some reason the borders of the quarter and year scales do not match, and this is most likely a bug, if this happens to you, please contact dhtmlx technical support.
If you want to have 24 sprints in a year, you can set specific dates for each sprint, it might look something like this:
const sprints = [
{ name: 'S1', start_date: new Date(2022,00,01), end_date: new Date(2022,00,15) },
{ name: 'S2', start_date: new Date(2022,00,15), end_date: new Date(2022,01,01) },
{ name: 'S3', start_date: new Date(2022,01,01), end_date: new Date(2022,01,15) },
{ name: 'S4', start_date: new Date(2022,01,15), end_date: new Date(2022,02,01) },
{ name: 'S5', start_date: new Date(2022,02,01), end_date: new Date(2022,02,15) },
{ name: 'S6', start_date: new Date(2022,02,15), end_date: new Date(2022,03,01) },
...
];
You need to add the given sprints to the unit:
gantt.date.sprints_start = function(date) {
return date;
};
function getSprint(date,type) {
const tempDate = new Date(date);
for (let i = 0; i < sprints.length; i++) {
if (+tempDate >= +sprints[i].start_date && +tempDate < +sprints[i].end_date) {
if (type == 'scaleUnit') {
return sprints[i].end_date;
}
if (type == 'template') {
return "<div class='sprint'>"+sprints[i].name+"</div>";
}
}
}
if (type == 'scaleUnit') {
const newDate = gantt.date.add(date, 1,'day');
return newDate;
}
if (type == 'template') {
return gantt.date.date_to_str("%m-%d")(date);
}
}
gantt.date.add_sprints = function(date, inc) {
return getSprint(date, 'scaleUnit');
};
const sprintsTemplate = function(date) {
return getSprint(date,'template');
}
and also add a new unit to the scale:
gantt.config.scales = [
{ unit: "year", step: 1, format: "%Y" },
{
unit: 'quarter',
format: date => {
return `PI-${(Math.floor((new Date(date).getMonth() / 3)) + 1 )}`
}
},
{ unit: 'month', step: 1, format: "%M" },
{ unit: 'sprints', step: 1, template: sprintsTemplate },
];
Here is an example: https://snippet.dhtmlx.com/0xznw5m9

How to add the values ​in an array of objects depending on the date value of each object

I have this array:
[{start_date: "2022-12-05T04:00:00Z" ,distance: 1000, time: 3600}
,{start_date: "2022-02-07T04:00:00Z" ,distance: 1500, time: 6400},
{start_date: "2022-12-08T04:00:00Z" ,distance: 1000, time: 1300}]
I want to add the distance and time values ​​grouping them by the month indicated by the start_date value. For example, if two start_dates have the same month 2022-12-01 and 2022-12-08, how can I add the distance and time values ​​of those two months?
so i get a new array like this:
[{month: 12 ,total distance: 2000, total time: 4900},
{month: 02 , total distance: 1500, total time: 6400} ]
you can use reduce to group them by month which will give an object like
{
12: {
distance: 2000,
month: 12,
time: 4900
},
2: {
distance: 1500,
month: 2,
time: 6400
}
}
and using Object.values get the values array of it
let x = [{start_date: "2022-12-05T04:00:00Z" ,distance: 1000, time: 3600},{start_date: "2022-02-07T04:00:00Z" ,distance: 1500, time: 6400},{start_date: "2022-12-08T04:00:00Z" ,distance: 1000, time: 1300}]
let res = Object.values(x.reduce((acc,{start_date,distance,time})=> {
let month = new Date(start_date).getMonth()+1
if(!acc[month])acc[month] = {totalDistance:0,totalTime:0,month:month}
acc[month].totalDistance+=distance
acc[month].totalTime+=time
return acc;
},{}))
console.log(res)
You can use a object as a dictionary and save a accumulated value of time and distance per month key. Then, reduce all keys to an array with the requested format.
const groupPerMonth = (list) => {
const extractMonth = (stringDate) => {
const month = new Date(stringDate).getMonth() + 1;
return month < 10 ? `0${month}` : `${month}`;
}
const months = {};
for (const item of list) {
const month = extractMonth(item.start_date);
if (!(month in months)) {
months[month] = {
distance: 0,
total_time: 0,
};
}
months[month].distance += item.distance;
months[month].total_time += item.time;
}
const result = [];
for (const month in months) {
result.push({
month,
...months[month]
});
}
return result;
};
And test it:
console.log(
groupPerMonth([
{ start_date: "2022-12-05T04:00:00Z", distance: 1000, time: 3600 },
{ start_date: "2022-02-07T04:00:00Z", distance: 1500, time: 6400 },
{ start_date: "2022-12-08T04:00:00Z", distance: 1000, time: 1300 },
])
);
Output:
[
{ month: '12', distance: 2000, total_time: 4900 },
{ month: '02', distance: 1500, total_time: 6400 }
]
There might be different solutions to this, but one way to solve this is by using the lodash library to solve this. We can first group by month, followed by mapping each grouped item and adding the distance and time values in each group using reduce:
const list = [
{start_date: "2022-12-05T04:00:00Z" ,distance: 1000, time: 3600},
{start_date: "2022-02-07T04:00:00Z" ,distance: 1500, time: 6400},
{start_date: "2022-12-08T04:00:00Z" ,distance: 1000, time: 1300}
]
const grouped = _.groupBy(list, item => {
const date = new Date(item.start_date)
return date.getMonth() + 1
})
const groupedAndMapped = _.map(grouped, function(groupedList, date){
return {
month: date,
total_distance: _.reduce(groupedList, (total, current) => {
return total + current.distance
}, 0),
total_time:_.reduce(groupedList, (total, current) => {
return total + current.time
}, 0)
}
})
One improvement you could do is to format the month to an "MM-YYYY" format or something similar as its possible your data set can include items with different years.

Multiply two values from api response and sum all the results

I'm getting info from an api, and what i wanna do is to multiply two different values from that response, and then sum the totals. I know how to sum all the values with reduce:
function getHistoricSales(){
$http.get('api/SomeApi')
.then(function(data){
$scope.salesResult = data.data.Response;
var hResults = $scope.salesResult.reduce((a, b) => a + b.Cost, 0);
$scope.historic = hResult.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,');
});
}
But, if per example, on that response not only get the Cost (b.Cost), but the Quantity too. So, how can i in first place multiply every Costby it's own Quantity and then sum the results?
I'm using Javascript and AngularJs.
Hope you can help me. Thanx in advance...
I think what you're looking for is:
var hResults = $scope.salesResult
.map(sr => sr.Cost * sr.Quantity)
.reduce((a, b) => a + b);
So you want to transform each sales result (with map) into its own result (i.e. cost * quantity) and then sum those (with reduce).
For example:
let data = [
{Cost: 10, Quantity: 15},
{Cost: 5, Quantity: 11},
{Cost: 2, Quantity: 110},
{Cost: 5, Quantity: 90},
]
let result = data
.map(sr => sr.Cost * sr.Quantity)
.reduce((a, b) => a + b);
console.log(result);
If each item in $scope.salesResult optionally has a quantity property, you'll need to make sure you're not multiplying by an undefined value. Thus, your reduce() should take this condition into account.
var salesResult = [
// Some items only have the cost
{ cost: 10 },
{ cost: 10 },
// Some items might also have a quantity
{ cost: 10, quantity: 10 },
{ cost: 10, quantity: 10 },
];
var hResults = salesResult.reduce((total, result) =>
total + (result.quantity
? result.cost * result.quantity
: result.cost), 0);
console.log(hResults); // 220

javascript - merge objects return arrays with the keys and sum of values

I have an object like this, but I need to manipulate to return 2 arrays with the keys and the sum of the data. Considering that this data can grow indefinitely.
const data = [
{
year: 2017,
month: 1,
sources: {
source1: 50,
source2: 30,
source3: 10
}
},
{
year: 2017,
month: 2,
sources: {
source1: 50,
source2: 10
}
},
{
year: 2017,
month: 3,
sources: {
source1: 10,
source2: 10,
source3: 1
}
}
]
I need the result to be and Array of strings and in order and with the sum of the sources data
const sources = ["source1", "source2", "source3"]
const data = [160, 50, 1]
You can reduce the objects into a Map by iterating the sources of each object using forEach. You spread the Map's keys iterator to get the sources array, and spread the values iterator to get the values array:
const data = [{"year":2017,"month":1,"sources":{"source1":50,"source2":30,"source3":10}},{"year":2017,"month":2,"sources":{"source1":50,"source2":10}},{"year":2017,"month":3,"sources":{"source1":10,"source2":10,"source3":1}}]
const sourcesMap = data.reduce((m, { sources }) => {
Object.entries(sources).forEach(([key, value]) => m.set(key, (m.get(key) || 0) + value))
return m;
}, new Map())
const sources = [...sourcesMap.keys()]
const values = [...sourcesMap.values()]
console.log(sources)
console.log(values)

how to split the array list in separate array by day wise using javascript

how to split the array list by days
const days = [
{'name':'Mon','value':1},
{'name':'Tue','value':5},
{'name':'Wed','value':10},
{'name':'Wed','value':30},
{'name':'Fri','value':18},
{'name':'Sat','value':80},
{'name':'Sat','value':90},
{'name':'Sun','value':20},
]
I having the above array list by days i wed and Sat i am having two values for thu I am not having values. i need to split the repeated key values into separate array if day are not in the list i need to add zero value for that for example my out put will be
const result = [
[1,5,10,0,18,80,20],
[0,0,30,0,0,90,0]
]
I need like this result is it possible to do in javascript.
You can do this using native javascript.
The algorithm is very simple:
For each day in daysArray you should search it in your given array and just remove first occurence from days.
Do step 1 until days is empty. With the other words, execute step 1 until days.length == 0 condition is satisfied.
let days = [ {'name':'Mon','value':1}, {'name':'Tue','value':5}, {'name':'Wed','value':10}, {'name':'Wed','value':30}, {'name':'Fri','value':18}, {'name':'Sat','value':80}, {'name':'Sat','value':90}, {'name':'Sun','value':20} ], daysArray = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
let result = [];
while(days.length){
sub_array = [];
daysArray.forEach(function(item){
let index = days.findIndex(a => a.name == item);
if(index == -1)
sub_array.push(0);
else{
sub_array.push(days[index].value);
days.splice(index, 1);
}
});
result.push(sub_array);
}
console.log(result);
Add an array of days in the order that you want - daysList.
Group the day objects into a Map using the name as key - daysMap.
Reduce the daysList, and get the days objects from the map by the current day. Iterate the array of days with Array.forEach(), and for each add a week array filled with 0s if missing, and assign the day's value to the day index di.
const daysList = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
const days = [{"name":"Mon","value":1},{"name":"Tue","value":5},{"name":"Wed","value":10},{"name":"Wed","value":30},{"name":"Fri","value":18},{"name":"Sat","value":80},{"name":"Sat","value":90},{"name":"Sun","value":20}]
// group the days by name into a map
const daysMap = days.reduce((m, o) => {
m.has(o.name) || m.set(o.name, [])
m.get(o.name).push(o)
return m
}, new Map)
// iterate the daysList
const result = daysList.reduce((r, d, di) => {
//get the array of the days with the same name from the group
const daysObjs = daysMap.get(d) || []
//iterate the daysObjs array
daysObjs.forEach(({ name, value }, wi) => {
// add a new week array filled with 0 if the row is missing
r[wi] || r.push(new Array(daysList.length).fill(0))
// assign the day value to the week array
r[wi][di] = value
})
return r
}, [])
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
You could take an object for the day indices and for the ouiter indixec which is incremented by every inset of data.
var days = [{ name: 'Mon', value: 1 }, { name: 'Tue', value: 5 }, { name: 'Wed', value: 10 }, { name: 'Wed', value: 30 }, { name: 'Fri', value: 18 }, { name: 'Sat', value: 80 }, { name: 'Sat', value: 90 }, { name: 'Sun', value: 20 }],
indices = { Mon: { day: 0, index: 0 }, Tue: { day: 1, index: 0 }, Wed: { day: 2, index: 0 }, Thu: { day: 3, index: 0 }, Fri: { day: 4, index: 0 }, Sat: { day: 5, index: 0 }, Sun: { day: 6, index: 0 } },
result = days.reduce((r, { name, value }) => {
r[indices[name].index] = r[indices[name].index] || Array.from({ length: 7 }).fill(0);
r[indices[name].index++][indices[name].day] = value;
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use array#reduce and a lookup object of day and create an array of value based on a given day.
const days = [{'name':'Mon','value':1}, {'name':'Tue','value':5}, {'name':'Wed','value':10}, {'name':'Wed','value':30}, {'name':'Fri','value':18}, {'name':'Sat','value':80}, {'name':'Sat','value':90}, {'name':'Sun','value':20}, ],
day = {'Mon':0, 'Tue':1, 'Wed':2, 'Thu': 3, 'Fri': 4, 'Sat': 5, 'Sun': 6},
result = days.reduce((r,o) => {
var index = 0;
if(r[index][day[o.name]]) {
while(r[index] && r[index][day[o.name]]) {
index++;
}
if(!r[index]) {
r[index] = Array.from({length: 7}, _=> 0);
}
}
r[index][day[o.name]] = o.value;
return r;
},[Array.from({length: 7}, _=> 0)]);
console.log(result);
another solution:
const res = _.chain(days)
.map('name') // get days name
.uniq() // uniq days name
.map(day => _.filter(days, { name: day })) // iterate days name and get all day items
.map(sameDays => _.map(sameDays, 'value')) // get value for each day items
.map(vs => [vs[0], vs[1] || 0]) // set 0 value if only one item for day
.thru(_.spread(_.zip)) // transpose the array
.value()

Categories

Resources