How can I use a Swift enum as a Dictionary key? (Conforming to Equatable) How can I use a Swift enum as a Dictionary key? (Conforming to Equatable) swift swift

How can I use a Swift enum as a Dictionary key? (Conforming to Equatable)


Info on Enumerations as dictionary keys:

From the Swift book:

Enumeration member values without associated values (as described inEnumerations) are also hashable by default.

However, your Enumeration does have a member value with an associated value, so Hashable conformance has to be added manually by you.

Solution

The problem with your implementation, is that operator declarations in Swift must be at a global scope.

Just move:

func == (lhs: StationSelector, rhs: StationSelector) -> Bool {    return lhs.toInt() == rhs.toInt()}

outside the enum definition and it will work.

Check the docs for more on that.


I struggled for a little trying to make an enum with associated values conform to Hashable.

Here's I made my enum with associated values conform to Hashable so it could be sorted or used as a Dictionary key, or do anything else that Hashable can do.

You have to make your associated values enum conform to Hashable because associated values enums cannot have a raw type.

public enum Components: Hashable {    case None    case Year(Int?)    case Month(Int?)    case Week(Int?)    case Day(Int?)    case Hour(Int?)    case Minute(Int?)    case Second(Int?)    ///The hashValue of the `Component` so we can conform to `Hashable` and be sorted.    public var hashValue : Int {        return self.toInt()    }    /// Return an 'Int' value for each `Component` type so `Component` can conform to `Hashable`    private func toInt() -> Int {        switch self {        case .None:            return -1        case .Year:            return 0        case .Month:            return 1        case .Week:            return 2        case .Day:            return 3        case .Hour:            return 4        case .Minute:            return 5        case .Second:            return 6        }    }}

Also need to override the equality operator:

/// Override equality operator so Components Enum conforms to Hashablepublic func == (lhs: Components, rhs: Components) -> Bool {    return lhs.toInt() == rhs.toInt()}


For more readability, let's reimplement StationSelector with Swift 3:

enum StationSelector {    case nearest, lastShown, list, specific(Int)}extension StationSelector: RawRepresentable {    typealias RawValue = Int    init?(rawValue: RawValue) {        switch rawValue {        case -1: self = .nearest        case -2: self = .lastShown        case -3: self = .list        case (let value) where value >= 0: self = .specific(value)        default: return nil        }    }    var rawValue: RawValue {        switch self {        case .nearest: return -1        case .lastShown: return -2        case .list: return -3        case .specific(let value) where value >= 0: return value        default: fatalError("StationSelector is not valid")        }    }}

The Apple developer API Reference states about Hashable protocol:

When you define an enumeration without associated values, it gains Hashable conformance automatically, and you can add Hashable conformance to your other custom types by adding a single hashValue property.

Therefore, because StationSelector implements associated values, you must make StationSelector conform to Hashable protocol manually.


The first step is to implement == operator and make StationSelector conform to Equatable protocol:

extension StationSelector: Equatable {    static func == (lhs: StationSelector, rhs: StationSelector) -> Bool {        return lhs.rawValue == rhs.rawValue    }}

Usage:

let nearest = StationSelector.nearestlet lastShown = StationSelector.lastShownlet specific0 = StationSelector.specific(0)// Requires == operatorprint(nearest == lastShown) // prints falseprint(nearest == specific0) // prints false// Requires Equatable protocol conformancelet array = [nearest, lastShown, specific0]print(array.contains(nearest)) // prints true

Once Equatable protocol is implemented, you can make StationSelector conform to Hashable protocol:

extension StationSelector: Hashable {    var hashValue: Int {        return self.rawValue.hashValue    }}

Usage:

// Requires Hashable protocol conformancelet dictionnary = [StationSelector.nearest: 5, StationSelector.lastShown: 10]

The following code shows the required implementation for StationSelector to make it conform to Hashable protocol using Swift 3:

enum StationSelector: RawRepresentable, Hashable {    case nearest, lastShown, list, specific(Int)    typealias RawValue = Int    init?(rawValue: RawValue) {        switch rawValue {        case -1: self = .nearest        case -2: self = .lastShown        case -3: self = .list        case (let value) where value >= 0: self = .specific(value)        default: return nil        }    }    var rawValue: RawValue {        switch self {        case .nearest: return -1        case .lastShown: return -2        case .list: return -3        case .specific(let value) where value >= 0: return value        default: fatalError("StationSelector is not valid")        }    }    static func == (lhs: StationSelector, rhs: StationSelector) -> Bool {        return lhs.rawValue == rhs.rawValue    }    var hashValue: Int {        return self.rawValue.hashValue    }}