web-dev-qa-db-de.com

Wie kann ich eine Swift-Enum als Wörterbuchschlüssel verwenden? (Konform zu gleichwertig)

Ich habe eine Aufzählung definiert, um eine Auswahl einer "Station" darzustellen. Stationen werden durch eine eindeutige positive ganze Zahl definiert. Daher habe ich die folgende Aufzählung erstellt, um negative Werte zur Darstellung von Sonderauswahlen zuzulassen:

enum StationSelector : Printable {
    case Nearest
    case LastShown
    case List
    case Specific(Int)

    func toInt() -> Int {
        switch self {
        case .Nearest:
            return -1
        case .LastShown:
            return -2
        case .List:
            return -3
        case .Specific(let stationNum):
            return stationNum
        }
    }

    static func fromInt(value:Int) -> StationSelector? {
        if value > 0 {
            return StationSelector.Specific(value)
        }
        switch value {
        case -1:
            return StationSelector.Nearest
        case -2:
            return StationSelector.LastShown
        case -3:
            return StationSelector.List
        default:
            return nil
        }
    }

    var description: String {
    get {
        switch self {
        case .Nearest:
            return "Nearest Station"
        case .LastShown:
            return "Last Displayed Station"
        case .List:
            return "Station List"
        case .Specific(let stationNumber):
            return "Station #\(stationNumber)"
        }
    }
    }
}

Ich möchte diese Werte als Schlüssel in einem Wörterbuch verwenden. Die Deklaration eines Wörterbuchs führt zu dem erwarteten Fehler, dass StationSelector nicht Hashable entspricht. Die Konformität mit Hashable ist mit einer einfachen Hashfunktion einfach:

var hashValue: Int {
get {
    return self.toInt()
}
}

Allerdings erfordert Hashable die Konformität mit Equatable, und ich kann anscheinend nicht den Gleichheitsoperator in meinem Enum definieren, um den Compiler zufrieden zu stellen.

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

Der Compiler beschwert sich, dass es sich um zwei Deklarationen in einer einzelnen Zeile handelt, und möchte einen ; hinter func setzen, was ebenfalls keinen Sinn macht.

Irgendwelche Gedanken?

35
Doug Knowles

Infos zu Enumerationen als Wörterbuchschlüssel:

Aus dem Swift-Buch:

Enumerations-Member-Werte ohne zugehörige Werte (wie in Enumerationen beschrieben) sind ebenfalls standardmäßig hashierbar.

Ihre Aufzählung hat jedoch einen Member-Wert mit einem zugehörigen Wert. Daher muss die Konformität von Hashable von Ihnen manuell hinzugefügt werden.

Lösung

Das Problem bei Ihrer Implementierung ist, dass Operatordeklarationen in Swift einen globalen Gültigkeitsbereich haben müssen.

Beweg dich einfach:

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

außerhalb der enum-Definition und es wird funktionieren.

Überprüfen Sie die Dokumente für mehr darüber.

22
Cezar

Ich bemühte mich ein wenig darum, eine enum mit zugehörigen Werten an Hashable anzupassen. 

Hier habe ich meine enum mit zugehörigen Werten an Hashable angepasst, so dass sie sortiert oder als Dictionary-Schlüssel verwendet werden kann oder alles, was Hashable tun kann. 

Sie müssen die zugehörigen Werte enum an Hashable anpassen, da die zugehörigen Werte enums keinen Rohtyp haben können. 

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
        }

    }

}

Sie müssen auch den Gleichheitsoperator überschreiben:

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

Um die Lesbarkeit zu verbessern, lassen Sie uns StationSelector mit Swift 3 erneut implementieren:

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")
        }
    }

}

In der Apple-Entwickler-API-Referenz wird das Protokoll " Hashable " angegeben:

Wenn Sie eine Aufzählung ohne zugehörige Werte definieren, wird automatisch die Konformität von Hashable erhalten. Sie können die Konformität von Hashable auch Ihren anderen benutzerdefinierten Typen hinzufügen, indem Sie eine einzige hashValue-Eigenschaft hinzufügen.

Da StationSelector zugehörige Werte implementiert, müssen Sie StationSelector manuell an das Hashable-Protokoll anpassen.


Der erste Schritt besteht darin, den ==-Operator zu implementieren und StationSelector an das Equatable-Protokoll anzupassen:

extension StationSelector: Equatable {

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

}

Verwendungszweck:

let nearest = StationSelector.nearest
let lastShown = StationSelector.lastShown
let specific0 = StationSelector.specific(0)

// Requires == operator
print(nearest == lastShown) // prints false
print(nearest == specific0) // prints false

// Requires Equatable protocol conformance
let array = [nearest, lastShown, specific0]
print(array.contains(nearest)) // prints true

Sobald das Equatable-Protokoll implementiert ist, können Sie StationSelector an das Hashable-Protokoll anpassen:

extension StationSelector: Hashable {

    var hashValue: Int {
        return self.rawValue.hashValue
    }

}

Verwendungszweck:

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

Der folgende Code zeigt die erforderliche Implementierung für StationSelector, um sie mit Swift 3 an das Hashable-Protokoll anzupassen:

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
    }

}
2
Imanou Petit

Nur um zu betonen, was Cezar vorher gesagt hat. Wenn Sie vermeiden können, eine Membervariable zu verwenden, müssen Sie den Gleichheitsoperator nicht implementieren, um enum hashable zu machen. Geben Sie ihnen einfach einen Typ!

enum StationSelector : Int {
    case Nearest = 1, LastShown, List, Specific
    // automatically assigned to 1, 2, 3, 4
}

Das ist alles was du brauchst. Jetzt können Sie sie auch mit dem rawValue initiieren oder später abrufen.

let a: StationSelector? = StationSelector(rawValue: 2) // LastShown
let b: StationSelector = .LastShown

if(a == b)
{
    print("Selectors are equal with value \(a?.rawValue)")
}

Weitere Informationen finden Sie unter in der Dokumentation .

1
Sebastian Hojas