web-dev-qa-db-fra.com

Comment utiliser un enum Swift en tant que clé de dictionnaire? (Conforme à Equatable)

J'ai défini une énumération pour représenter une sélection d'une "station"; Les stations sont définies par un entier positif unique. J'ai donc créé l'énumération suivante afin de permettre aux valeurs négatives de représenter des sélections spéciales:

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

J'aimerais utiliser ces valeurs comme clés dans un dictionnaire. La déclaration d'un dictionnaire génère l'erreur attendue: StationSelector n'est pas conforme à Hashable. Se conformer à Hashable est facile avec une simple fonction de hachage:

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

Cependant, Hashable requiert la conformité à Equatable et je n'arrive pas à définir l'opérateur égal à mon enum pour satisfaire le compilateur.

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

Le compilateur se plaint qu'il s'agit de deux déclarations sur une seule ligne et veut mettre un ; après func, ce qui n'a pas de sens non plus.

Des pensées?

35
Doug Knowles

Informations sur les énumérations en tant que clés de dictionnaire:

Du livre Swift:

Les valeurs de membre d'énumération sans valeurs associées (comme décrit dans Enumérations) peuvent également être définies par défaut.

Cependant, votre énumération a une valeur de membre avec une valeur associée. La conformité Hashable doit donc être ajoutée manuellement par vous.

Solution

Le problème de votre implémentation est que les déclarations d'opérateur dans Swift doivent avoir une portée globale.

Juste bouge:

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

en dehors de la définition enum et cela fonctionnera.

Vérifiez la documentation pour plus d'informations à ce sujet.

22
Cezar

J'ai eu du mal à essayer un peu de rendre une enum avec les valeurs associées conforme à Hashable

Voici que j'ai rendu ma enum avec les valeurs associées conforme à Hashable afin qu'elle puisse être triée ou utilisée comme clé Dictionary ou faire tout ce que Hashable peut faire. 

Vous devez rendre vos valeurs associées enum conformes à Hashable car les valeurs associées enums ne peuvent pas avoir un type brut. 

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
        }

    }

}

Il faut également remplacer l'opérateur d'égalité:

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

Pour plus de lisibilité, réimplémentons StationSelector avec 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")
        }
    }

}

La référence API des développeurs Apple indique à propos de Hashable protocol:

Lorsque vous définissez une énumération sans valeurs associées, elle acquiert automatiquement la conformité Hashable et vous pouvez ajouter la conformité Hashable à vos autres types personnalisés en ajoutant une seule propriété hashValue.

Par conséquent, étant donné que StationSelector implémente les valeurs associées, vous devez rendre StationSelector conforme au protocole Hashable manuellement.


La première étape consiste à implémenter l'opérateur == et à rendre StationSelector conforme au protocole Equatable:

extension StationSelector: Equatable {

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

}

Utilisation:

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

Une fois que le protocole Equatable est mis en œuvre, vous pouvez adapter StationSelector au protocole Hashable:

extension StationSelector: Hashable {

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

}

Utilisation:

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

Le code suivant montre l'implémentation requise pour StationSelector afin de la rendre conforme au protocole Hashable à l'aide de 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
    }

}
2
Imanou Petit

Juste pour souligner ce que Cezar a dit auparavant. Si vous pouvez éviter d'avoir une variable membre, vous n'avez pas besoin d'implémenter l'opérateur equals pour rendre les énumérations hashable: donnez-leur simplement un type!

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

C'est tout ce dont vous avez besoin. Maintenant, vous pouvez également les initier avec la rawValue ou les récupérer plus tard.

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

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

Pour plus d'informations, consultez la documentation .

1
Sebastian Hojas