How to initialize enum from raw value - swift

I'm having an enum that I'm trying to initialize to case twentyFourHours like this:
enum Duration: TimeInterval {
case twentyFourHours
var durationInSeconds: TimeInterval {
switch self {
case .twentyFourHours:
return TimeInterval.init(86400)
}
}
var durationInHours: Int {
switch self {
case .twentyFourHours:
return 24
}
}
}
let interval = TimeInterval.init(86400)
guard let duration = Duration.init(rawValue: interval) else { throw ChallengeError.invalidDuration }
But I always get nil. Does anyone know why?
Seems like a basic thing I already should know!

Try this
enum Duration: TimeInterval {
case twentyFourHours = 86400
var durationInSeconds: TimeInterval {
return self.rawValue
}
var durationInHours: Int {
return Int(self.rawValue / 3600)
}
}
This equals to:
Duration(1234) // <- nil
Duration(86400) // <- .twentyFourHours

You need to specify the rawValue if it's something other than 0, TimeInterval is a typealias for type Double and it defaults the first case rawValue to 0 if not specified.
enum Duration: TimeInterval {
case twentyFourHours = 86400
//...
}
let interval: TimeInterval = 86400
let duration = Duration(rawValue: interval) // and then you can initialize like this

Related

Date comparison always returns true - Swift

I want to track when was the last time the user refreshed the api, so I decided to do it like this:
#AppStorage("lastTimeUserRefreshedApi") var lastTimeUserRefreshedApi: Date = Date()
func canUserRefreshAPI() -> Bool {
let readyToBeRefreshed: Date = lastTimeUserRefreshedApi.addingTimeInterval(5) // in seconds
let currentTime: Date = Date()
var canUserRefresh: Bool = false
if(currentTime > readyToBeRefreshed) {
canUserRefresh = true
lastTimeUserRefreshedApi = lastTimeUserRefreshedApi.addingTimeInterval(5)
} else {
canUserRefresh = false
}
return canUserRefresh
}
The problem is that it's always returning true, but why? Also is there a simpler way to do this?
Thanks
EDIT:
This is the extension I'm using to be able to store Date in the #AppStorage:
extension Date: RawRepresentable {
public var rawValue: String {
self.timeIntervalSinceReferenceDate.description
}
public init?(rawValue: String) {
self = Date(timeIntervalSinceReferenceDate: Double(rawValue) ?? 0.0)
}
}
You are making it much harder than it should. Just save the "expiration" date. When you read it just compare if it is past or not.
#AppStorage("expiration")
var expiration: Date = Date(timeIntervalSinceNow: 5)
func canUserRefreshAPI() -> Bool {
let now = Date()
if expiration < now {
expiration = Date(timeIntervalSinceNow: 5)
return true
} else {
return false
}
}

Which "Leap year counting" code run faster?

while practicing coding, I just caught up with question: which code will process faster in swift?
Is this one faster:
var aYear = Int(readLine()!)!
func isLeap(year: Int)
{
if aYear%400 == 0
{
print("YES")
}
else if aYear%4 == 0 && !(aYear%100 == 0)
{
print("YES")
}
else
{
print("NO")
}
}
isLeap(year: aYear)
Or this one faster?
var aYear = Int(readLine()!)!
func isLeap(year: Int) {
var leap = "NO"
if year % 4 == 0 {
leap = "YES"
}
if year % 100 == 0 {
leap = "NO"
}
if year % 400 == 0 {
leap = "YES"
}
print(leap)
}
isLeap(year: aYear)
Thank you. Have a great day :)
One way to check function performance is to this helper struct. Add the following to your project:
struct Benchmark {
static func testElapsedTimeFor(_ title: String, operation: #escaping ()->()) {
let startTime = CFAbsoluteTimeGetCurrent()
operation()
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print("Time elapsed for \(title): \(timeElapsed) s.")
}
// If you would like to get the double instead
static func testElapsedTimeFor(operation: #escaping ()->()) -> Double {
let startTime = CFAbsoluteTimeGetCurrent()
operation()
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
return Double(timeElapsed)
}
}
And use it like so:
Benchmark.testElapsedTimeFor("First Function") {
self.isLeap(year: 2019)
}

Generic next() and previous() method for CaseItearble

Suppose we have an enum and want to enumerate over it :).
If it has Int rawValue we can be provided with next and previous items using computed vars like this.
enum Fidelity: Int, CaseIterable {
case pixel
case point
case average
case datapoint
var previousFidelity: Fidelity {
return Fidelity(rawValue: rawValue - 1) ?? .pixel
}
var nextFidelity: Fidelity {
return Fidelity(rawValue: rawValue + 1) ?? .datapoint
}
}
I went further and created and extension for CaseIterable which allows next() and previous() for a wide range of types.
// Let's test Swift 4.2 for enumerating enum
// Too complex, not very efficient, but interesting
extension CaseIterable where Self: Equatable {
func next() -> Self? {
let all = Self.allCases
let idx = all.index(of: self)!
let next = all.index(after: idx)
return (next == all.endIndex) ? nil : all[next]
}
func previous() -> Self? {
let all_reversed = Self.allCases.reversed()
let idx = all_reversed.index(of: self)!
let next = all_reversed.index(after: idx)
return (next == all_reversed.endIndex) ? nil : all_reversed[next]
}
}
The question is how efficient or inefficient my solutions are (i.e. speed, memory)?
Are there any ideas for doing the same or similar things, perhaps offset(by: ).
You can implement previous() using offsetBy this way:
func previous() -> Self? {
let all = Self.allCases
var idx = all.index(of: self)!
if idx == all.startIndex {
return nil
} else {
all.formIndex(&idx, offsetBy: -1)
return all[idx]
}
}
You can combine both next() and previous() in a more generic offset function:
extension CaseIterable where Self: Equatable {
func advanced(by n: Int) -> Self? {
let all = Self.allCases
let idx = all.index(of: self)!
//An enum with a raw type has at least one case
let lastIndex = all.index(all.endIndex, offsetBy: -1)
let limit = n > 0 ? lastIndex : all.startIndex
if let newIndex = all.index(idx, offsetBy: n, limitedBy: limit) {
return all[newIndex]
} else {
return nil
}
}
}
And use it like so
let average = Fidelity.average //average
average.advanced(by: 1) //datapoint
average.advanced(by: 2) //nil
average.advanced(by: -3) //pixel

How to convert DispatchTimeInterval to NSTimeInterval (or Double)?

I need to subtract a DispatchTimeInterval from an NSTimeInterval (or Double).
Is there a standard way to convert a DispatchTimeInterval to an NSTimeInterval?
DispatchTimeInterval is a enum:
public enum DispatchTimeInterval : Equatable {
case seconds(Int)
case milliseconds(Int)
case microseconds(Int)
case nanoseconds(Int)
case never
}
You can initialize DispatchTimeInterval using:
let tenSeconds: DispatchTimeInterval = .seconds(10)
let tenNanoseconds: DispatchTimeInterval = .nanoseconds(10)
To get values from enum you need to match value with a case values in enum
if case .seconds(let value) = tenSeconds {
print("DispatchTimeInterval is seconds \(value)")
} else if case .nanoseconds(let value) = tenNanoseconds {
print("DispatchTimeInterval is seconds \(value)")
}
Converting function might be look following:
func toDouble(_ interval: DispatchTimeInterval) -> Double? {
var result: Double? = 0
switch interval {
case .seconds(let value):
result = Double(value)
case .milliseconds(let value):
result = Double(value)*0.001
case .microseconds(let value):
result = Double(value)*0.000001
case .nanoseconds(let value):
result = Double(value)*0.000000001
case .never:
result = nil
}
return result
}
More about Enumeration see in Apple Documentation
UPDATE:
Create extension to DispatchTimeInterval
extension DispatchTimeInterval {
func toDouble() -> Double? {
var result: Double? = 0
switch self {
case .seconds(let value):
result = Double(value)
case .milliseconds(let value):
result = Double(value)*0.001
case .microseconds(let value):
result = Double(value)*0.000001
case .nanoseconds(let value):
result = Double(value)*0.000000001
case .never:
result = nil
}
return result
}
}
A swifty solution would be to create a TimeInterval extension and add a failable initializer with a DispatchTimeInterval parameter in it. The following Swift 5 code shows how to implement it:
import Foundation
extension TimeInterval {
init?(dispatchTimeInterval: DispatchTimeInterval) {
switch dispatchTimeInterval {
case .seconds(let value):
self = Double(value)
case .milliseconds(let value):
self = Double(value) / 1_000
case .microseconds(let value):
self = Double(value) / 1_000_000
case .nanoseconds(let value):
self = Double(value) / 1_000_000_000
case .never:
return nil
}
}
}
Usage:
let dispatchTimeInterval = DispatchTimeInterval.seconds(5)
let timeInterval = TimeInterval(dispatchTimeInterval: dispatchTimeInterval)
print(String(describing: timeInterval)) // Optional(5.0)
let dispatchTimeInterval = DispatchTimeInterval.milliseconds(30)
let timeInterval = TimeInterval(dispatchTimeInterval: dispatchTimeInterval)
print(String(describing: timeInterval)) // Optional(0.03)

How to filter based on Date so there is only one element for each week?

I'm having difficulty wording this, but here goes.
I have an array of Datapoint objects and each of these objects has a createdAt NSDate property. The array has about 10 or so Datapoint objects for each day of the past week.
I want to filter the array so that there is only one datapoint for each day of the week.
I was thinking something along the lines of:
let today = NSDate()
var endRange = today.dateByAddingTimeInterval(-7 * 24 * 60 * 60)
var allThisWeeksPoints = datapoints.filter({ $0.createdAt >= endRange && $0.createdAt < today })
let c = NSCalendar.currentCalendar()
var thisWeeksPoints : [Datapoint] = []
while !c.isDate(endRange, inSameDayAsDate: today) {
//take one datapoint, eliminate all the other datapoints in the same day and then increment the endRange date by a day
}
Here is the code for my Datapoint object:
class Datapoint: NSObject {
let object : PFObject
let objectId : String
let userId : String
let createdAt : NSDate
let totalPosts : Int
let followerCount : Int
let followingCount : Int
let totalLikes : Int
let averageLikes : Float
let totalComments : Int
let averageComments : Float
}
Step One:
Make NSDate Comparable Found here
public func ==(lhs: NSDate, rhs: NSDate) -> Bool {
return lhs === rhs || lhs.compare(rhs) == .OrderedSame
}
public func <(lhs: NSDate, rhs: NSDate) -> Bool {
return lhs.compare(rhs) == .OrderedAscending
}
extension NSDate: Comparable { }
A few general remarks:
Use an input and output.
Respect upper and lower camel naming conventions
Split the function in two parts, one for dividing the points in weeks, another to apply the filter. This makes it easier to maintain.
Use dateByAddingUnit as suggested in the comments. This will prevent localisation issues. Not every week is the same and not every calendar used in the world uses the same system of weeks.
func weeklyPoints(dataPoints: [DataPoint]) -> [[DataPoint]]? {
guard let firstPoint = dataPoints.first else { // alternative to an .isEmpty check since we need the first point anyway
return nil
}
guard let gregorian = NSCalendar(identifier:NSCalendarIdentifierGregorian) else {
return nil
}
var weeklies : [[DataPoint]] = [[]]
var weekStart : NSDate = firstPoint.createdAt // force unwrap allowed because of the guard at the beginning
var maybeWeekEnd : NSDate? = gregorian.dateByAddingUnit(.WeekOfYear, value: 1, toDate: weekStart, options: [])
for point in dataPoints {
guard let weekEnd = maybeWeekEnd else {
break
}
guard point.createdAt >= weekStart && point.createdAt < weekEnd else {
weekStart = weekEnd
maybeWeekEnd = gregorian.dateByAddingUnit(.WeekOfYear, value: 1, toDate: weekStart, options: [])
weeklies.append([])
continue
}
let currentWeekIndex = (weeklies.count - 1)
weeklies[currentWeekIndex].append(point)
}
return weeklies
}
The very simple filter function.
func onePointPerWeek(dataPoints: [DataPoint]) -> [DataPoint]? {
guard let weeklies = weeklyPoints(dataPoints) else {
return nil
}
var pointPerWeek : [DataPoint] = []
for week in weeklies {
guard let point = week.first else { // maybe sort them first if needed
continue
}
pointPerWeek.append(point)
}
return pointPerWeek
}
So I figured out a way to do it:
func thisWeeksPoints() -> [Datapoint] {
let today = NSDate()
var endRange = today.dateByAddingTimeInterval(-7 * 24 * 60 * 60)
var allThisWeeksPoints = datapoints.filter({ $0.createdAt >= endRange && $0.createdAt < today })
let c = NSCalendar.currentCalendar()
var thisWeeksPoints : [Datapoint] = []
while !c.isDate(endRange, inSameDayAsDate: today) {
if let point = allThisWeeksPoints.first {
allThisWeeksPoints = allThisWeeksPoints.filter({!c.isDate(point.createdAt, inSameDayAsDate: $0.createdAt)})
thisWeeksPoints.append(point)
}
endRange = endRange.dateByAddingTimeInterval(24 * 60 * 60)
}
return thisWeeksPoints
}