web-dev-qa-db-ja.com

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

ただし、HashableEquatableに準拠する必要があり、コンパイラーを満たすために列挙型で等号演算子を定義することはできません。

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

コンパイラは、これが1行の2つの宣言であり、;funcの後。これも意味がありません。

何かご意見は?

35
Doug Knowles

辞書キーとしての列挙に関する情報:

Swift本から:

関連付けられた値のない列挙型メンバー値(列挙型で説明されている)も、デフォルトでハッシュ可能です。

ただし、列挙には関連付けられた値を持つメンバー値があるため、Hashable準拠を手動で追加する必要があります。

解決

実装の問題は、Swiftの演算子宣言はグローバルスコープである必要があることです。

移動するだけです:

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

enum定義の外にあり、それは機能します。

詳細については、 ドキュメント を確認してください。

24
Cezar

関連する値を持つenumHashableに準拠させようと少し努力しました。

関連付けられた値がenumに準拠するようにHashableをソートして使用したり、Dictionaryキーとして使用したり、Hashableができることをすべて実行したりします。行う。

関連付けられた値enumは生の型を持つことができないため、関連付けられた値Hashableenumsに準拠させる必要があります。

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

読みやすくするために、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準拠が自動的に取得され、単一のHashableプロパティを追加することで、他のカスタム型にhashValue準拠を追加できます。

したがって、StationSelectorは関連付けられた値を実装するため、StationSelectorHashableプロトコルに手動で準拠させる必要があります。


最初のステップは、==演算子を実装し、StationSelectorEquatableプロトコルに準拠させることです。

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プロトコルが実装されると、StationSelectorHashableプロトコルに準拠させることができます。

extension StationSelector: Hashable {

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

}

使用法:

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

以下のコードは、Swift 3を使用してStationSelectorプロトコルに準拠させるために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
    }

}
2
Imanou Petit

前にセザールが言ったことを強調するためだけに。メンバー変数の使用を避けることができる場合は、enumをハッシュ可能にするためにequals演算子を実装する必要はありません-型を与えるだけです!

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

詳細については、 ドキュメントを確認してください

1
Sebastian Hojas