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 addHashable
conformance to your other custom types by adding a singlehashValue
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 }}