【问题标题】:How can I use a Swift enum as a Dictionary key? (Conforming to Equatable)如何使用 Swift 枚举作为字典键? (符合 Equatable)
【发布时间】:2014-08-25 02:07:09
【问题描述】:

我已经定义了一个枚举来代表一个“站”的选择;站点由唯一的正整数定义,因此我创建了以下枚举以允许负值表示特殊选择:

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

我想将这些值用作字典中的键。声明 Dictionary 会产生 StationSelector 不符合 Hashable 的预期错误。使用简单的哈希函数很容易符合 Hashable:

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

但是,Hashable 需要符合 Equatable,而且我似乎无法在我的枚举上定义等于运算符以满足编译器的要求。

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

编译器抱怨这是在一行中的两个声明,并想在func 之后放置一个;,这也没有意义。

有什么想法吗?

【问题讨论】:

  • 运算符必须在 Swift 的文件范围内定义。
  • 枚举器已经是相等的。
  • 不应该是@infix func == 吗?
  • @matt 看到我的回答。这仅适用于没有具有关联值的成员值的枚举。
  • @Cezar 很好,感谢您的更正。

标签: dictionary enums swift hashable


【解决方案1】:

关于枚举作为字典键的信息:

来自 Swift 书籍:

没有关联值的枚举成员值(如在 枚举)默认情况下也是可散列的。

但是,您的 Enumeration 确实有一个带有关联值的成员值,因此您必须手动添加 Hashable 一致性。

解决方案

您的实现的问题在于 Swift 中的运算符声明必须在全局范围内。

移动一下:

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

enum 定义之外,它将起作用。

查看the docs 了解更多信息。

【讨论】:

  • 由于SE-0185,从Swift 4.1 开始,Swift 还支持为具有关联值的枚举合成EquatableHashable
  • 对于自定义 ==,如果它是静态的,则可以将实现放在 enum 的主体中:static func ==(...
  • Swift Book 引用来自The Swift Programming Language, p. 186 给予或接受。
  • 这绝对是错误的。为什么这被标记为正确答案?运算符函数(不是“运算符声明”——没有声明新运算符)可以是该类型的静态成员。它们不必在全局范围内。
  • @PeterSchorn 这是一个 6 岁的答案:)。欢迎任何人提出修改建议或使用更新的解决方案添加新答案。
【解决方案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】:

    为了提高可读性,让我们用 Swift 3 重新实现 StationSelector

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

    Apple 开发者 API 参考声明了 Hashable 协议:

    当您定义一个没有关联值的枚举时,它会自动获得 Hashable 一致性,您可以通过添加单个 hashValue 属性将 Hashable 一致性添加到您的其他自定义类型。

    因此,由于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 使用 Swift 3 使其符合Hashable 协议所需的实现:

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

    【讨论】:

    • 如何使用带有关联值的枚举作为字典键?在这种情况下,您能否扩展您的用法示例以展示如何使用 StationSelector.specific 作为该词典中的键?
    【解决方案4】:

    只是为了强调 Cezar 之前所说的话。如果可以避免使用成员变量,则无需实现等号运算符来使枚举可散列 - 只需给它们一个类型!

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

    更多信息,check the documentation

    【讨论】:

      猜你喜欢
      • 2019-06-29
      • 1970-01-01
      • 1970-01-01
      • 2022-08-02
      • 1970-01-01
      • 2017-11-27
      • 2019-03-19
      相关资源
      最近更新 更多