Как использовать переменную 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
, что тоже не имеет смысла.
Любые мысли?
Ответы
Ответ 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)")
}
Для получения дополнительной информации проверьте документацию.