Подтвердить что ты не робот

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

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

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, что тоже не имеет смысла.

Любые мысли?

4b9b3361

Ответ 1

Информация о перечислениях в качестве словарных клавиш:

Из книги Swift:

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

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

Решение

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

Просто переместите:

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

вне определения enum, и он будет работать.

Подробнее об этом docs.

Ответ 2

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

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

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

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

Ответ 3

Для большей читаемости позвольте переопределить 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
    }

}

Ответ 4

Просто для того, чтобы подчеркнуть то, что сказал Цезарь раньше. Если вы можете избежать наличия переменной-члена, вам не нужно реализовывать оператор equals, чтобы сделать enums 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)")
}

Для получения дополнительной информации проверьте документацию.