dictionary - ¿Cómo puedo usar Swift enum como una clave de diccionario?(Conforme a Equatable)
enums hashable (4)
Información sobre las enumeraciones como claves del diccionario:
Del libro de Swift:
Los valores de los miembros de la enumeración sin valores asociados (como se describe en Enumeraciones) también se pueden cargar de forma predeterminada.
Sin embargo, su enumeración tiene un valor de miembro con un valor asociado, por lo que Hashable
debe agregar manualmente la conformidad de Hashable
.
Solución
El problema con su implementación es que las declaraciones del operador en Swift deben tener un alcance global.
Sólo muévete:
func == (lhs: StationSelector, rhs: StationSelector) -> Bool {
return lhs.toInt() == rhs.toInt()
}
fuera de la definición de enum
y funcionará.
Consulte los documentos para obtener más información al respecto.
Definí una enumeración para representar una selección de una "estación"; las estaciones están definidas por un único entero positivo, así que he creado la siguiente enumeración para permitir que los valores negativos representen selecciones especiales:
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)"
}
}
}
}
Me gustaría usar estos valores como claves en un diccionario. Declarar un diccionario produce el error esperado de que StationSelector no se ajusta a Hashable. Ajustarse a Hashable es fácil con una simple función hash:
var hashValue: Int {
get {
return self.toInt()
}
}
Sin embargo, Hashable
requiere conformidad con Equatable
, y no puedo definir el operador equals en mi enumeración para satisfacer al compilador.
func == (lhs: StationSelector, rhs: StationSelector) -> Bool {
return lhs.toInt() == rhs.toInt()
}
El compilador se queja de que son dos declaraciones en una sola línea y quiere poner una ;
después de la func
, que tampoco tiene sentido.
¿Alguna idea?
Hashable
un poco tratando de hacer una enum
con valores asociados conforme a Hashable
.
Aquí hice mi enum
con valores asociados que se ajustan a Hashable
para que pueda ser ordenada o utilizada como una clave de Dictionary
, o hacer cualquier otra cosa que Hashable
pueda hacer.
Hashable
hacer que los valores asociados enum
ajusten a Hashable
porque las enums
valores asociados no pueden tener un tipo sin 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
}
}
}
También es necesario anular el operador de igualdad:
/// Override equality operator so Components Enum conforms to Hashable
public func == (lhs: Components, rhs: Components) -> Bool {
return lhs.toInt() == rhs.toInt()
}
Solo por enfatizar lo que dijo Cezar antes. Si puede evitar tener una variable miembro, no necesita implementar el operador igual para hacer que las enums sean transferibles, ¡simplemente deles un tipo!
enum StationSelector : Int {
case Nearest = 1, LastShown, List, Specific
// automatically assigned to 1, 2, 3, 4
}
Eso es todo lo que necesitas. Ahora también puede iniciarlos con rawValue o recuperarlos más tarde.
let a: StationSelector? = StationSelector(rawValue: 2) // LastShown
let b: StationSelector = .LastShown
if(a == b)
{
print("Selectors are equal with value /(a?.rawValue)")
}
Para más información, consulte la documentación .
Para una mayor legibilidad, reemplace StationSelector
con 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 API de desarrolladores de Apple dice sobre el protocolo Hashable
:
Cuando define una enumeración sin valores asociados, gana la conformidad de
Hashable
automáticamente y puede agregar la conformidad deHashable
a sus otros tipos personalizados agregando una única propiedad dehashValue
.
Por lo tanto, dado que StationSelector
implementa los valores asociados, debe hacer que StationSelector
ajuste al protocolo Hashable
manualmente.
El primer paso es implementar ==
operator y hacer que StationSelector
ajuste al protocolo Equatable
:
extension StationSelector: Equatable {
static func == (lhs: StationSelector, rhs: StationSelector) -> Bool {
return lhs.rawValue == rhs.rawValue
}
}
Uso:
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
Una vez Equatable
protocolo Equatable
, puede hacer que StationSelector
ajuste al protocolo Hashable
:
extension StationSelector: Hashable {
var hashValue: Int {
return self.rawValue.hashValue
}
}
Uso:
// Requires Hashable protocol conformance
let dictionnary = [StationSelector.nearest: 5, StationSelector.lastShown: 10]
El siguiente código muestra la implementación requerida para que StationSelector
haga conforme al protocolo Hashable
usando 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
}
}