HiveBrain v1.2.0
Get Started
← Back to all entries
patternswiftMinor

Finding consecutive days in an array of dates

Submitted by: @import:stackexchange-codereview··
0
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:

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:

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.