I have a JavaScript array of dates (as strings) like the following:
["2020-07-24T04:00:00.000Z", "2020-07-25T04:00:00.000Z", "2020-07-26T04:00:00.000Z", "2020-07-27T04:00:00.000Z", "2020-07-28T04:00:00.000Z", "2020-07-29T04:00:00.000Z", "2020-07-30T04:00:00.000Z", "2020-07-31T04:00:00.000Z", "2020-08-01T04:00:00.000Z", "2020-11-29T05:00:00.000Z", "2020-12-30T05:00:00.000Z", "2020-12-31T05:00:00.000Z", "2021-01-01T05:00:00.000Z", "2021-01-02T05:00:00.000Z", "2021-02-18T05:00:00.000Z"]
I want to convert this into an array of arrays of [first, last] contiguous date ranges, e.g., as below:
[["2020-07-24T04:00:00.000Z", "2020-08-01T04:00:00.000Z"], ["2020-11-29T05:00:00.000Z"], ["2020-12-30T05:00:00.000Z", "2021-01-02T05:00:00.000Z"], []]
How do I do this? Code attempt below:
var ranges = [];
for (var i = 0; i < popNull.length; i++) {
let currentRange = [];
let current = new Date(popNull[i]);
let tomorrow = new Date(current.getTime() + (24 * 60 * 60 * 1000));
let next = new Date(popNull[i+1]);
if (next === tomorrow) {
}
else {
}
}
I've made a couple of assumptions in the code below
That the dates are pre-sorted in ascending date order
That "contiguous" means less than or equal to 24 hours.
All dates are formatted in a way that can be passed directly to the Date constructor on the platform of choice.
const input = ["2020-07-24T04:00:00.000Z", "2020-07-25T04:00:00.000Z", "2020-07-26T04:00:00.000Z", "2020-07-27T04:00:00.000Z", "2020-07-28T04:00:00.000Z", "2020-07-29T04:00:00.000Z", "2020-07-30T04:00:00.000Z", "2020-07-31T04:00:00.000Z", "2020-08-01T04:00:00.000Z", "2020-11-29T05:00:00.000Z", "2020-12-30T05:00:00.000Z", "2020-12-31T05:00:00.000Z", "2021-01-01T05:00:00.000Z", "2021-01-02T05:00:00.000Z", "2021-02-18T05:00:00.000Z"].map(x => new Date(x));
let aggregation = input.reduce( (acc,i) => {
if(acc.prev){
const diffInHrs = (i - acc.prev)/1000/60/60;
if(diffInHrs <= 24){
acc.result[acc.result.length-1][1] = i;
}
else{
acc.result.push([i])
}
acc.prev = i;
return acc;
}
else{
return {prev:i, result:[[i]]}
}
},{});
console.log(aggregation.result)
You can reduce the dates by keeoing track of the latest and checking the current with the previous. You can diff their epoch valyes and check if they are within a day.
const dates = ["2020-07-24T04:00:00.000Z", "2020-07-25T04:00:00.000Z", "2020-07-26T04:00:00.000Z", "2020-07-27T04:00:00.000Z", "2020-07-28T04:00:00.000Z", "2020-07-29T04:00:00.000Z", "2020-07-30T04:00:00.000Z", "2020-07-31T04:00:00.000Z", "2020-08-01T04:00:00.000Z", "2020-11-29T05:00:00.000Z", "2020-12-30T05:00:00.000Z", "2020-12-31T05:00:00.000Z", "2021-01-01T05:00:00.000Z", "2021-01-02T05:00:00.000Z", "2021-02-18T05:00:00.000Z"];
const DAY_MILLIS = 8.64e7;
const ranges = dates
.reduce((acc, dateStr, index, all) => {
const dateObj = new Date(dateStr);
if (acc.length === 0) {
acc.push({ start: dateObj, prev: dateObj });
} else {
let last = acc[acc.length - 1];
const { start, prev } = last;
if (dateObj.getTime() - prev.getTime() <= DAY_MILLIS) {
last.prev = dateObj;
} else {
last.end = prev;
acc.push({ start: dateObj, prev: dateObj });
}
if (index === all.length - 1) {
last = acc[acc.length - 1];
if (last.end == null) {
last.end = last.prev;
}
}
}
return acc;
}, [])
.map(({ start, prev, end }) =>
((startStr, endStr) =>
startStr !== endStr ? [startStr, endStr] : [startStr])
(start.toISOString(), end.toISOString()));
console.log(ranges);
.as-console-wrapper { top: 0; max-height: 100% !important; }
Output
[
[ "2020-07-24T04:00:00.000Z", "2020-08-01T04:00:00.000Z" ],
[ "2020-11-29T05:00:00.000Z" ],
[ "2020-12-30T05:00:00.000Z", "2021-01-02T05:00:00.000Z" ],
[ "2021-02-18T05:00:00.000Z" ]
]
You can do the following using Array#reduce():
Go through each date.
Check if the current date will extend last range.
if yes, then overwrite the end in the range pair (second element)
if no, start a new range
If it happens that a range only has a single date, then use the start to compare with. The logic still holds - extending the range will add a second date. If the new date is not within the desired time frame, then a new range is created and the previous range is left with a single element in it.
const areDatesWithin = ms => (str1, str2) => {
if (!str1 || !str2)
return false;
const date1 = new Date(str1);
const date2 = new Date(str2);
return (date2 - date1) <= ms;
}
const areDatesWithin1Day = areDatesWithin(1000 * 60 * 60 * 24);
function combineInRanges(dates) {
return dates.reduce((acc, nextDate) => {
const lastDateRange = acc[acc.length-1] ?? [];
//compare with range end (if there) or range start
const lastDate = lastDateRange[1] ?? lastDateRange[0];
//check if the range needs to be extended
const mergeWithRange = areDatesWithin1Day(lastDate, nextDate);
if (mergeWithRange) {
//change the end of the range
lastDateRange[1] = nextDate;
} else {
//start a new range
acc.push([nextDate]);
}
return acc;
}, []);
}
const arr = ["2020-07-24T04:00:00.000Z", "2020-07-25T04:00:00.000Z", "2020-07-26T04:00:00.000Z", "2020-07-27T04:00:00.000Z", "2020-07-28T04:00:00.000Z", "2020-07-29T04:00:00.000Z", "2020-07-30T04:00:00.000Z", "2020-07-31T04:00:00.000Z", "2020-08-01T04:00:00.000Z", "2020-11-29T05:00:00.000Z", "2020-12-30T05:00:00.000Z", "2020-12-31T05:00:00.000Z", "2021-01-01T05:00:00.000Z", "2021-01-02T05:00:00.000Z", "2021-02-18T05:00:00.000Z"];
console.log(combineInRanges(arr));
https://stackoverflow.com/a/67182108/20667780
Jamiec answer is working. If you have a date array with UTC dates correctly offsetted to local timezone, then the daylight save start/end date will have more than 24 hours. You have to change the diffInHrs to 25 instead of 24.
Otherwise, its a perfect answer.
It's a sort of reduction based on the even-ness of the index...
let array = ['a', 'b', 'c', 'd', 'e', 'f'];
let pairs = array.reduce((acc, el, idx) => {
idx % 2 ? acc[acc.length-1].push(el) : acc.push([el]);
return acc;
}, []);
console.log(pairs)
Related
I have a array which updates every minute. When i want to show it over a day, I want to have the average of every hour that day.
The most recent minute is add the end of the array.
//get the last elements from the array
var hours= (this.today.getHours() + 1) * 60
var data = Array.from(this.temps.data)
let lastData = data.slice(Math.max(data.length - hours))
let newData: any
// calculate the average of every hour
for (let i = 0; i < minutes; i++) {
var cut = i * 60
for (let i = cut; i < (cut + 60); i++) {
newData = newData + lastData[i];
let test = newData/60
console.log(test);
}
}
I can't figure out how I make an array from every last 60 elements.
My goal is to get an array like
avgHour[20,22,30,27,]
The array I have is updated every minute. So I need the average of every 60 elements to get a hour.
array looks like this
data[25,33,22,33]
It is every minute from a week so really long.
This Worked For me
var arrays = [], size = 60;
while (arr.length > 0){
arrays.push(arr.splice(0, size));
}
for (let i = 0; i < (arrays.length - 1); i++) {
var sum = 0
for (let b = 0; b < 60; b++) {
sum += arrays[i][b]
}
let avg = sum/60
arr2.push(avg)
}
this just splits the array every 60 elements. Now I can calculate the average for every 60.
duplicate of How to split a long array into smaller arrays, with JavaScript
Thanks for the help!
I am a big fan of the functional programming library Ramda. (Disclaimer: I'm one of its authors.) I tend to think in terms of simple, reusable functions.
When I think of how to solve this problem, I think of it through a Ramda viewpoint. And I would probably solve this problem like this:
const avgHour = pipe(
splitEvery(60),
map(mean),
)
// some random data
const data = range(0, 7 * 24 * 60).map(_ => Math.floor(Math.random() * 20 + 10))
console.log(avgHour(data))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {pipe, splitEvery, map, mean, range} = R</script>
I think that is fairly readable, at least once you understand that pipe creates a pipeline of functions, each handing its result to the next one.
Now, there is often not a reason to include a large library like Ramda to solve a fairly simple problem. But all the functions used in that version are easily reusable. So it might make sense to try to create your own versions of these functions and keep them available to the rest of your application. In fact, that's how libraries like Ramda actually get built.
So here is a version that has simple implementations of those functions, ones you might place in a utility library:
const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x)
const splitEvery = (n) => (xs) => {
let i = 0, a = []
while (i < xs.length) {a.push(xs.slice(i, i + n)); i += n}
return a
}
const map = (fn) => (xs) => xs.map(x => fn(x))
const sum = (xs) => xs.reduce((a, b) => a + b, 0)
const mean = (xs) => sum(xs) / (xs.length || 1)
const avgHour = pipe(
splitEvery(60),
map(mean)
)
const range = (lo, hi) => [...Array(hi - lo)].map((_, i) => lo + i)
// some random data
const data = range(0, 7 * 24 * 60).map(_ => Math.floor(Math.random() * 20 + 10))
console.log(avgHour(data))
You can reduce the data and group by hour, then simply map to get each hour's average. I'm using moment to parse the dates below, you can do that with whatever lib/js you prefer...
const arr = Array.from({length: 100}, () => ({time: moment().subtract(Math.floor(Math.random() * 10), 'hours'), value: Math.floor(Math.random() * 100)}));
const grouped = [...arr.reduce((a, b) => {
let o = a.get(b.time.get('hour')) || {value: 0, qty: 0};
a.set(b.time.get('hour'), {value: o.value + b.value, qty: o.qty + 1});
return a;
}, new Map)].map(([k, v]) => ({
[k]: v.value / v.qty
}));
console.log(grouped)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment-with-locales.min.js"></script>
By grouping and then reducing you can do this like following.
function groupBy(list, keyGetter) {
const map = {};
list.forEach((item) => {
const key = keyGetter(item);
if (!map[key]) {
map[key] = [item];
} else {
map[key].push(item);
}
});
return map;
}
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
const now = (new Date()).getTime();
const stepSize = 60000*1;
const withTime = data.reverse().map((x, i) => { return { time: new Date(now - stepSize * i), temp: x } });
const grouped = groupBy(withTime, x => new Date(x.time.getFullYear(), x.time.getMonth(), x.time.getDate(), x.time.getHours()).valueOf());
const avg = Object.entries(grouped).map((x) => {
return {
time: new Date(Number(x[0])),
temp: x[1].map(y => y.temp).reduce((acc, val) => acc + val) * (1.0 / x[1].length)
}
});
console.log(avg);
To measure the average I needed to split the array every 60 elements.
This is the solution I found
//Calculate the average of every 60 elements to get the average of an hour
var arr2: number[] = []
var arr: number[] = []
arr = Array.from(this.temps.data)
var arrays = [], size = 60;
while (arr.length > 0){
arrays.push(arr.splice(0, size));
}
for (let i = 0; i < (arrays.length - 1); i++) {
var sum = 0
for (let b = 0; b < 60; b++) {
sum += arrays[i][b]
}
let avg = sum/60
arr2.push(avg)
}
After all I think its stupid to get the last elements of the array, Because this is a better solution. But thanks for the help!
I have the following array of objects:
var transactions = [
[
{"id":1,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:33:00.000Z"},
{"id":2,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:33:50.000Z"},
{"id":3,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:34:30.000Z"},
{"id":4,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:36:00.000Z"}
],
[
{"id":5,"sourceAccount":"A","targetAccount":"C","amount":250,"category":"other","time":"2018-03-02T10:33:00.000Z"},
{"id":6,"sourceAccount":"A","targetAccount":"C","amount":250,"category":"other","time":"2018-03-02T10:33:05.000Z"}
]
]
I need to compare time property of each object consecutively, and keep only those properties which time difference between each consecutive transaction is less than 1 minute.
The array format should be stay same, this is what I did try, but no luck, didn't work. What's the problem?
var newArray = transactions.map(g => g.reduce((r, o, i, a) => {
if (!i || new Date(o.time).getTime() - new Date(a[i - 1].time).getTime() >= 60000) {
r.push([o]);
} else {
r[r.length - 1].push(o);
}
return r;
}, []));
The expected output is something like this :
var output = [
[
{"id":1,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:33:00.000Z"},
{"id":2,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:33:50.000Z"},
{"id":3,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:34:30.000Z"}
],
[
{"id":5,"sourceAccount":"A","targetAccount":"C","amount":250,"category":"other","time":"2018-03-02T10:33:00.000Z"},
{"id":6,"sourceAccount":"A","targetAccount":"C","amount":250,"category":"other","time":"2018-03-02T10:33:05.000Z"}
]
]
You can Array#map your source array, and in each iteration, Array#filter the desired elements by comparing the time of current element with the time of previous element.
var transactions = [[{"id":1,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:33:00.000Z"},{"id":2,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:33:50.000Z"},{"id":3,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:34:30.000Z"},{"id":4,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:36:00.000Z"}],[{"id":5,"sourceAccount":"A","targetAccount":"C","amount":250,"category":"other","time":"2018-03-02T10:33:00.000Z"},{"id":6,"sourceAccount":"A","targetAccount":"C","amount":250,"category":"other","time":"2018-03-02T10:33:05.000Z"}]];
var result = transactions.map((tr, i) => {
return tr.filter((t, j) => {
if (transactions[i][j - 1]) {
var d1 = new Date(t.time);
var d2 = new Date(transactions[i][j - 1].time);
return (d1.getTime() - d2.getTime()) <= 60000;
}
return true;
});
});
console.log(result);
I have an array that looks like
[
"2017-05-08T13:42:00.318Z",
"2017-05-08T13:42:05.590Z",
"2017-05-08T13:42:12.377Z",
"2017-05-08T13:42:20.830Z",
"2017-05-08T13:42:22.634Z",
"2017-05-08T13:42:25.249Z"
]
And I want to end up with an array of counts of how many timestamps fall into 10 second time ranges.
So for the above example "2017-05-08T13:42:00.318Z" and "2017-05-08T13:42:05.590Z" would fall into the range 2017-05-08T13:42:00.000Z to 2017-05-08T13:42:10.000Z.
"2017-05-08T13:42:12.377Z" would fall into the range of 2017-05-08T13:42:10.000Z to 2017-05-08T13:42:20.000Z.
"2017-05-08T13:42:20.830Z", "2017-05-08T13:42:22.634Z", and "2017-05-08T13:42:25.249Z" would fall into the range of 2017-05-08T13:42:20.000Z to 2017-05-08T13:42:30.000Z.
My goal is to get an array of counts of the number of timestamps in each of these ranges. For the above example, the result would be: [2, 1, 3].
Note: I am using Typescript so answers using ES6 features will work.
You could slice the date and check it with the previous date.
value slot result
------------------------ ------------------ ---------
2017-05-08T13:42:00.318Z 2017-05-08T13:42:0 [1]
2017-05-08T13:42:05.590Z 2017-05-08T13:42:0 [2]
2017-05-08T13:42:12.377Z 2017-05-08T13:42:1 [2, 1]
2017-05-08T13:42:20.830Z 2017-05-08T13:42:2 [2, 1, 1]
2017-05-08T13:42:22.634Z 2017-05-08T13:42:2 [2, 1, 2]
2017-05-08T13:42:25.249Z 2017-05-08T13:42:2 [2, 1, 3]
var data = ["2017-05-08T13:42:00.318Z", "2017-05-08T13:42:05.590Z", "2017-05-08T13:42:12.377Z", "2017-05-08T13:42:20.830Z", "2017-05-08T13:42:22.634Z", "2017-05-08T13:42:25.249Z"],
count = data.reduce(function (r, a, i, aa) {
if (i && aa[i - 1].slice(0, 18) === a.slice(0, 18)) {
r[r.length - 1]++;
} else {
r.push(1);
}
return r;
}, []);
console.log(count)
This could work using some loops which I am not particularly proud of, but you can change the interval and it can account for more data later if you just use the function called by the forEach on its own.
const timestampsToCheck = [
"2017-05-08T13:42:00.318Z",
"2017-05-08T13:42:05.590Z",
"2017-05-08T13:42:12.377Z",
"2017-05-08T13:42:20.830Z",
"2017-05-08T13:42:22.634Z",
"2017-05-08T13:42:25.249Z"
];
let nextTime = null;
let outputTime = [0];
function getNextTime() {
nextTime.setSeconds(nextTime.getSeconds() + 10);
}
timestampsToCheck.forEach((element) => {
let thisTime = new Date(element);
let outputLast = outputTime.length - 1;
if (nextTime === null) {
nextTime = thisTime;
getNextTime();
outputTime[outputLast]++;
} else if (thisTime > nextTime) {
getNextTime();
while (thisTime > nextTime) {
getNextTime();
outputTime.push(0);
}
outputTime.push(1);
} else {
outputTime[outputLast]++;
}
});
console.log(outputTime);
const timestampsToCheck = [
'2017-05-08T13:42:00.318Z',
'2017-05-08T13:42:05.590Z',
'2017-05-08T13:42:12.377Z',
'2017-05-08T13:42:20.830Z',
'2017-05-08T13:42:22.634Z',
'2017-05-08T13:42:25.249Z',
];
const ranges = [
['2017-05-08T13:42:00.000Z', '2017-05-08T13:42:10.000Z'],
['2017-05-08T13:42:10.000Z', '2017-05-08T13:42:20.000Z'],
['2017-05-08T13:42:20.000Z', '2017-05-08T13:42:30.000Z'],
];
const timestampsInRange = ranges.reduce((acc, range) => {
const count = timestampsToCheck.reduce((innerAcc, time) => {
return innerAcc + ((time > range[0] && time < range[1]) ? 1 : 0);
}, 0);
return acc.concat(count);
}, []);
console.log(timestampsInRange);
Working fiddle: https://jsfiddle.net/9tjerwxa/
I have an array of Date() objects in javascript and I want to count the number of events on each day.
Here is an example:
What I have is:
Array [ Date 2014-12-04T10:30:20.000Z, Date 2014-12-05T11:04:58.056Z, Date 2014-12-05T11:04:58.056Z, Date 2014-12-05T11:04:58.056Z ]
What I want is:
Array [{date: '2014-12-04', counts: 1}, {date: '2014-12-05', counts: 3}]
Thanks a lot!
Max
Basic answer:
var arr = [], // fill it with array with your data
results = {}, rarr = [], i, date;
for (i=0; i<arr.length; i++) {
// get the date
date = [arr[i].getFullYear(),arr[i].getMonth(),arr[i].getDate()].join("-");
results[date] = results[date] || 0;
results[date]++;
}
// you can always convert it into an array of objects, if you must
for (i in results) {
if (results.hasOwnProperty(i)) {
rarr.push({date:i,counts:results[i]});
}
}
These can be made much easier with lodash functions, and Array.forEach() in ES5
You much better off having a simple object with the keys as the date and the value as the count. I've added a simple pad function that prefixes a zero where the number is a single digit as per your output requirements.
function pad(n) {
return n.toString().length == 1 ? '0' + n : n;
}
function getCount(arr) {
var obj = {};
for (var i = 0, l = arr.length; i < l; i++) {
var thisDate = arr[i];
var day = pad(thisDate.getDate());
var month = pad(thisDate.getMonth() + 1);
var year = thisDate.getFullYear();
var key = [year, day, month].join('-');
obj[key] = obj[key] || 0;
obj[key]++;
}
return obj;
}
getCount(arr); // Object { 2014-04-12: 1, 2014-05-12: 3 }
DEMO
I came across the same issue and found this solution which uses Map()
`
calc = (obj) => {
const orders = []
const dates_map = new Map()
//iterate through all the objects inside the orders array
orders.forEach(order => {
// format and get the date
const date = new Date(order.created_at).toLocaleDateString('en-GB')
//check if the date key exists in the Map() and save it in a temp
const temp = dates_map.get(date) || false
// if it does not exist
if (temp) {
// clone the object
const previous = {...temp}
// increase counter
previous.count += 1
dates_map.set(date, previous)
}else{
//create new object to avoid overwriting
const result = {}
result.count = 1
dates_map.set(date, result)
}
})
console.log(dates_map)
}
And this is the output
Output: Map(3) {
'08/05/2021' => { count: 2 },
'09/05/2021' => { count: 1 },
'11/05/2021' => { count: 2,}
}
`
I have an array with the following values (example):
[
1367848800000: true,
1367935200000: true,
1368021600000: true,
1368108000000: true,
1368194400000: true,
1368367200000: true,
1368540000000: true,
1368626400000: true,
1368712800000: true
]
Where the index is a date time. The date time will always be at 12:00:00 on a date.
In this example, the first five dates are consecutive, then one day by itself, and then another group of 3 dates. An example of what I mean is below.
Now, what I am trying to do is find sequential dates and put them into an array as follows:
[
1367848800000,
1367935200000,
1368021600000,
1368108000000,
1368194400000
],
[
1368367200000,
1368540000000,
1368626400000,
],
[
1368712800000Ω
]
So in the end, I have an array, with 3 arrays of all the times.
I have tried numerous pieces of code, but everything bugs out and nothing is worth posting on here. Any help would be much appreciated!
The following approach uses array .reduce() method:
var arr = [1367848800000, 1367935200000, 1368021600000,
1368108000000, 1368194400000, 1368367200000,
1368540000000, 1368626400000, 1368712800000],
i = 0,
result = arr.reduce(function(stack, b) {
var cur = stack[i],
a = cur ? cur[cur.length-1] : 0;
if (b - a > 86400000) {
i++;
}
if (!stack[i])
stack[i] = [];
stack[i].push(b);
return stack;
}, []);
console.log(result);
DEMO: http://jsfiddle.net/gbC8B/1/
Sth like this could do:
function sequentialize(dArr) {
dArr = Object.keys(dArr).slice().sort();
var last;
var arrs = [[]];
for (var i = 0, l = dArr.length; i < l; i++) {
var cur = new Date();
cur.setTime(dArr[i]);
last = last || cur;
if (isNewSequence(cur, last)) {
arrs.push([]);
}
arrs[arrs.length - 1].push(cur.getTime()); //always push to the last index
last = cur;
}
return arrs;
function isNewSequence(a, b) {
if (a.getTime() - b.getTime() > (24 * 60 * 60 * 1000))
return true;
return false;
}
}
Now if you pass your example Array/Object to the sequentialize function
var dates = {
1367848800000: true,
1367935200000: true,
1368021600000: true,
1368108000000: true,
1368194400000: true,
1368367200000: true,
1368540000000: true,
1368626400000: true,
1368712800000: true
};
console.log(sequentialize(dates));
This gives the following output
[
[
1367848800000,
1367935200000,
1368021600000,
1368108000000,
1368194400000
],
[
1368367200000
],
[
1368540000000,
1368626400000,
1368712800000
]
]
This simply
creates an array out of the Date keys,
Sorts them
Iterates over them
If the difference of the Current and Last Date is greate than a day
Push a new Array to the Sequence Array
Push the Current Date to the last Array in the Sequence Array
Demo on JSBin
Note: You may have to change the isNewSequence function to actually fit your needs
Gotta love these puzzles. Nice answers everyone, here's mine more jQueryish approach.
var datearray = {
1367848800000: true,
1367935200000: true,
1368021600000: true,
1368108000000: true,
1368194400000: true,
1368367200000: true,
1368540000000: true,
1368626400000: true,
1368712800000: true
};
$(function() {
var result = dateSequences(datearray);
}
function dateSequences(array) {
// parse json object to array of keys
var keys = Object.keys(array);
// sort it up
keys = keys.sort();
// convert them to dates
var dates = new Array();
$.each(keys, function(i) {
dates.push(new Date(parseInt(keys[i])));
});
// now we have array of dates, search for sequential dates
var final = new Array();
var prevdate = undefined;
var currentseq = 0;
$.each(dates, function(i, d) {
// undefined?
// first sequence
if (prevdate == undefined) {
final.push(new Array());
final[currentseq].push(d);
}
else {
// compare if difference to current date in loop is greater than a day
var comp=new Date();
comp.setDate(prevdate.getDate()+2);
// Advance sequence if it is
if (comp < d) {
currentseq++;
final[currentseq] = new Array();
}
// Push the date to current sequence
final[currentseq].push(d);
}
// store previous
prevdate = d;
});
return final;
}
Fiddle:
http://jsfiddle.net/f57Ah/1/
tried array sort with forEach
var dates = [1367848800000, 1367935200000, 1368021600000,
1368108000000, 1368194400000, 1368367200000,
1368540000000, 1368626400000, 1368712800000];
var k = 0 , sorted = [[]];
dates.sort( function ( a, b ){
return +a > +b ? 1 : +a == +b ? 0: -1;
})
.forEach( function( v , i ){
var a = v,b = dates[i+1]||0;
sorted[k].push( +a );
if ( (+b - +a) > 86400000) {
sorted[++k] = []
}
});
Later you can sort them per counts
sorted.sort( function ( a,b ){
return a.length > b.length ? -1: 1;
});
The sorted array contains desired result jsfiddle
// Preconditions: singleArray contains the input array with each element corresponding to a time index. singleArray is sorted.
var outputArray = new Array();
var stack = new Array();
var stackSize = 0;
var i;
for( i = 0; i < singleArray.length; i++ )
{
// Get the last element on the stack
var lastElement = (stackSize == 0) ? 0 : stack.pop();
// Compare to see if difference is one day
if( singleArray[i] - lastElement == 86400000 ) // 24 * 60 * 60 * 1000
{
// Dates are 1 day apart
if( lastElement != 0 ) stack.push(lastElement);
stack.push(singleArray[i]);
stackSize++;
}
else
{
if( lastElement != 0 ) stack.push(lastElement);
var tempQueue = new Array();
while(stackSize > 0)
{
// Build up a new array containing consecutive days
// using a queue
tempQueue.push(stack.pop());
stackSize--;
}
// Push the consecutive days onto the next place in the output array.
outputArray.push(tempQueue);
// Start a new group of consecutive dates
stack.push(singleArray[i]);
stackSize++;
}
}