patternswiftMinor
Finding consecutive days in an array of dates
Viewed 0 times
arraydatesfindingdaysconsecutive
Problem
The problem:
How to get the largest number of consecutive days from an array of unknown number of Date objects, by taking into account days only like 4th of April, 5th of April and so on.
Example input data:
Expected result from the sample data: 6 consecutive days
How this solution could be improved?
Solution:
How to get the largest number of consecutive days from an array of unknown number of Date objects, by taking into account days only like 4th of April, 5th of April and so on.
Example input data:
let calendar = Calendar.current
let x1 = Date()
let x2 = calendar.date(byAdding: .day, value: 1, to: x1)!
let x3 = calendar.date(byAdding: .day, value: 1, to: x2)!
let x4 = calendar.date(byAdding: .day, value: 1, to: x3)!
let x5 = calendar.date(byAdding: .day, value: 1, to: x4)!
let x6 = calendar.date(byAdding: .day, value: 1, to: x5)!
let x7 = calendar.date(byAdding: .hour, value: 1, to: x4)!
let x8 = calendar.date(byAdding: .hour, value: 2, to: x4)!
let x9 = calendar.date(byAdding: .hour, value: 3, to: x2)!
let x10 = calendar.date(byAdding: .hour, value: 4, to: x2)!
var dates = [Date]()
dates.append(x1)
dates.append(x5)
dates.append(x2)
dates.append(x3)
dates.append(x4)
dates.append(x6)
dates.append(x7)
dates.append(x8)
dates.append(x9)
dates.append(x10)Expected result from the sample data: 6 consecutive days
How this solution could be improved?
Solution:
dates.sort(by: = aDayInSeconds && nextDate.timeIntervalSince(date) < twoDaysInSeconds {
dateIndex += 1
nextDateIndex += 1
consecutiveDaysCount += 1
} else {
break
}
} while (i < newDates.count - 1)
print(consecutiveDaysCount)Solution
Never use 86,400 seconds as the duration of a day. "Most" days have 24 hours,
but in regions with daylight saving time, a day can have 23 or 25 hours (when the
clock is adjusted one hour forward or back).
Another problem is that you compare the difference in seconds between two days
in order to decide if they are on the same day. Then for example
"April 3, 10:00" and "April 4, 09:00" will be considered the same day.
There is also a bug in your final loop: The loop is exited if two dates are more
than one day apart, instead of continuing to search for a longer sequence of
consecutive days.
I would start by computing the difference in days to some reference date first:
Now we have an array of (non-decreasing) integers, in your example
that would be
It remains to find the longest
sequence of consecutive numbers (ignoring duplicates) in that array, i.e. no date/calendar calculations
are needed from this point on.
This should be done in a separate function, which could look like this:
The approach is actually similar as in your last loop, only that
to remove duplicates in advance,
You should add unit tests to verify that the function works correctly.
This is easier for an array of integers than for an array of dates.
With these preparations, the number of consecutive days is simply obtained as
but in regions with daylight saving time, a day can have 23 or 25 hours (when the
clock is adjusted one hour forward or back).
Another problem is that you compare the difference in seconds between two days
in order to decide if they are on the same day. Then for example
"April 3, 10:00" and "April 4, 09:00" will be considered the same day.
There is also a bug in your final loop: The loop is exited if two dates are more
than one day apart, instead of continuing to search for a longer sequence of
consecutive days.
I would start by computing the difference in days to some reference date first:
dates.sort() // Short form for: dates.sort(by: Int in
calendar.dateComponents([.day], from: referenceDate, to: date).day!
}Now we have an array of (non-decreasing) integers, in your example
that would be
print(dayDiffs)
// [0, 1, 1, 1, 2, 3, 3, 3, 4, 5]It remains to find the longest
sequence of consecutive numbers (ignoring duplicates) in that array, i.e. no date/calendar calculations
are needed from this point on.
This should be done in a separate function, which could look like this:
/// Find maximal length of a subsequence of consecutive numbers in the array.
/// It is assumed that the array is sorted in non-decreasing order.
/// Consecutive equal elements are ignored.
func maximalConsecutiveNumbers(in array: [Int]) -> Int {
var longest = 0 // length of longest subsequence of consecutive numbers
var current = 1 // length of current subsequence of consecutive numbers
for (prev, next) in zip(array, array.dropFirst()) {
if next > prev + 1 {
// Numbers are not consecutive, start a new subsequence.
current = 1
} else if next == prev + 1 {
// Numbers are consecutive, increase current length
current += 1
}
if current > longest {
longest = current
}
}
return longest
}The approach is actually similar as in your last loop, only that
- we only have to compare integers,
- we continue to search for longer subsequences if one consecutive sequence ends,
- consecutive equal numbers are simply ignored, so that it is no longer necessary
to remove duplicates in advance,
zip()+dropFirst()is used to iterate over all pairs of adjacent array elements, that saves a look of bookkeeping (dateIndex,
nextDateIndex).You should add unit tests to verify that the function works correctly.
This is easier for an array of integers than for an array of dates.
With these preparations, the number of consecutive days is simply obtained as
let consecutiveDaysCount = maximalConsecutiveNumbers(in: dayDiffs)Code Snippets
dates.sort() // Short form for: dates.sort(by: < )
let referenceDate = calendar.startOfDay(for: dates.first!)
let dayDiffs = dates.map { (date) -> Int in
calendar.dateComponents([.day], from: referenceDate, to: date).day!
}print(dayDiffs)
// [0, 1, 1, 1, 2, 3, 3, 3, 4, 5]/// Find maximal length of a subsequence of consecutive numbers in the array.
/// It is assumed that the array is sorted in non-decreasing order.
/// Consecutive equal elements are ignored.
func maximalConsecutiveNumbers(in array: [Int]) -> Int {
var longest = 0 // length of longest subsequence of consecutive numbers
var current = 1 // length of current subsequence of consecutive numbers
for (prev, next) in zip(array, array.dropFirst()) {
if next > prev + 1 {
// Numbers are not consecutive, start a new subsequence.
current = 1
} else if next == prev + 1 {
// Numbers are consecutive, increase current length
current += 1
}
if current > longest {
longest = current
}
}
return longest
}let consecutiveDaysCount = maximalConsecutiveNumbers(in: dayDiffs)Context
StackExchange Code Review Q#159788, answer score: 4
Revisions (0)
No revisions yet.