
This video is only available to subscribers. Start a subscription today to get access to this and 472 other videos.
Let's Build Activity++ - Part 7
This episode is part of a series: Let's Build Activity++!.
Episode Links
Defining the Streak Data Structure
First we need to define a structure to hold our streak data.
enum MetricType {
case Movement
case Exercise
case Standing
}
struct Streak {
let metric: MetricType
var numberOfDays: Int
}
Now we need a way of computing the streaks, given the activity logs we were provided.
Computing Streaks
lazy var streaks: [Streak] = {
var streaks: [Streak] = []
var potentialStreaks: [MetricType: Int] = [:]
var lookup: [MetricType : (ActivityLog) -> Bool] = [
.Movement : { $0.activityProgress >= 1.0 },
.Exercise : { $0.exerciseProgress >= 1.0 },
.Standing : { $0.standProgress >= 1.0 }
]
for activity in self.activities {
for metric in lookup.keys {
let hasCompletedMetric = lookup[metric]!(activity)
if hasCompletedMetric {
let numberOfDays = (potentialStreaks[metric] ?? 0) + 1
potentialStreaks[metric] = numberOfDays
} else {
if let numberOfDays = potentialStreaks[metric] where numberOfDays > 1 {
streaks.append(Streak(metric: metric, numberOfDays: numberOfDays))
}
potentialStreaks[metric] = nil
}
}
}
return streaks
}()
Testing the Algorithm
describe("streak detection") {
var samples: [ActivityLog]!
let calendar = NSCalendar.currentCalendar()
var streaks: [Streak]!
context("no streaks") {
beforeEach {
samples = FakeHealthData.randomSamplesWithNoStreaks(calendar: calendar, startingWithDate: NSDate())
var datasource = ActivityDataSource(calendar: calendar, activities: samples)
streaks = datasource.streaks
}
it("returns no streaks") {
expect(streaks).to(beEmpty())
}
}
context("known streaks") {
beforeEach {
samples = FakeHealthData.randomSampleWithStreaks(calendar: calendar, startingWithDate: NSDate())
var datasource = ActivityDataSource(calendar: calendar, activities: samples)
streaks = datasource.streaks
}
it("should return 2 streaks") {
expect(streaks).to(haveCount(2))
}
it("should have a movement streak of 4 days") {
let movementStreak = streaks.filter { $0.metric == .Movement }.first
expect(movementStreak).notTo(beNil())
expect(movementStreak!.numberOfDays).to(equal(4))
}
it("should have a standing streak of 5 days") {
let standingStreak = streaks.filter { $0.metric == .Standing }.first
expect(standingStreak).notTo(beNil())
expect(standingStreak!.numberOfDays).to(equal(5))
}
}
Adding the Date
In the video I neglected to add an important piece: the date of the streak! The easiest solution would be to have the streak's date be the starting date of the streak:
struct Streak {
let metricType: MetricType
let startingDate: NSDate
let numberOfDays: Int
}
Then when we create the streak, we can use the date of the current log, which we've already guaranteed will be in reverse chronological order -- in other words the last element in a streak will be the earliest date. We can then create the streak like this:
streaks.append(Streak(metric: metric, startingDate: activity.date, numberOfDays: numberOfDays))