Как я могу использовать Swift enum в качестве словарного ключа? (В соответствии с Equatable)

Я определил перечисление для представления выбора "станции"; станции определяются уникальным положительным целым числом, поэтому я создал следующее перечисление, чтобы позволить отрицательным значениям представлять специальные выборки:

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

Я хочу использовать эти значения в качестве ключей в словаре. Объявление словаря дает ожидаемую ошибку, что StationSelector не соответствует Hashable. В соответствии с Hashable легко с простой хэш-функцией:

var hashValue: Int {
get {
    return self.toInt()
}
}
, Hashable требует соответствия Equatable, и я не могу определить оператор equals в моем перечислении, чтобы удовлетворить компилятор.
func == (lhs: StationSelector, rhs: StationSelector) -> Bool {
    return lhs.toInt() == rhs.toInt()
}

компилятор жалуется, что это два объявления в одной строке и хочет поставить ; после func, что тоже не имеет смысла.

какие мысли?

4 ответов


информация о перечислениях в качестве ключей словаря:

из книги Swift:

значения элементов перечисления без связанных значений (как описано в Перечисления) также хэшируются по умолчанию.

однако ваше перечисление имеет значение члена со связанным значением, поэтому Hashable соответствие должно быть добавлено вами вручную.

решение

проблема с вашей реализацией заключается в том, что оператор декларации в Swift должны иметь глобальный охват.

просто двигаться:

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

за пределами enum определение и оно будет работать.

Регистрация документы подробнее об этом.


я боролся немного, пытаясь сделать enum со связанными значениями соответствует Hashable.

вот я enum со связанными значениями соответствует Hashable таким образом, он может быть отсортирован или использован как Dictionary ключ, или сделать что-нибудь еще, что Hashable можно сделать.

вы должны сделать свои связанные значения enum соответствуют Hashable потому что соответствующие значения enums не может иметь тип raw.

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
        }

    }

}

также необходимо переопределить оператор равенства:

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

для более читабельности, давайте переопределим StationSelector С 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")
        }
    }

}

ссылка API разработчика Apple заявляет о Hashable протокол:

когда вы определяете перечисление без связанных значений, оно получает Hashable соответствие автоматически, и вы можете добавить Hashable соответствие другим пользовательским типам путем добавления одного hashValue собственность.

таким образом, поскольку StationSelector реализует связанные значения, вы должны сделать StationSelector соответствуют Hashable протокол вручную.


первым шагом является реализация == оператор и принять StationSelector соответствуют Equatable протокол:

extension StationSelector: Equatable {

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

}

использование:

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

один раз Equatable протокол реализован, вы можете сделать StationSelector соответствуют Hashable протокол:

extension StationSelector: Hashable {

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

}

использование:

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

следующий код показывает, необходимых для осуществления StationSelector чтобы он соответствовал Hashable протокол с использованием 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
    }

}

просто для того, чтобы подчеркнуть, что Цезарь сказал раньше. Если вы можете обойтись без переменной-члена, вам не нужно использовать оператор равенства, чтобы сделать перечисления hashable – просто дать им тип!

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

Это все, что вам нужно. Теперь вы также можете инициировать их с помощью rawValue или получить его позже.

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

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

дополнительная информация Регистрация документации.