enumを
Enum IntEnum: Int { case A = 0xA000, B = 0xB000, C = 0xC000 }
Enum StrEnum: String { case A = "Alfa", B = "Bravo", C = "Charlie" }
と定義したとき,IntEnum.A.hashValue
やStrEnum.B.hashValue
はそれぞれ0xA000
や"Alfa"
のハッシュ値になるのかと思いきや,さにあらず.
println(\(IntEnum.A.hashValue)) // 0
println(\(IntEnum.B.hashValue)) // 1
println(\(IntEnum.C.hashValue)) // 2
なんと,列挙した順に値が付けられていました.連番,最小完全ハッシュですね,これ.
StrEnumの方ももちろん同様.3万個以上列挙しても,もちろん同様でした.(限界まで確かめたかったのですがコンパイルが終わらないので断念.)
enumをキーとしたコンパクトなディクショナリー
このhashValue
を使わない手はありません.もろに配列のインデックスに使えます.
これは,enumをDictionaryのkeyに指定するのコメント欄の議論の延長ですが,hashValue
を使うとさらにシンプルに辞書を実装できます.ほんの一つ足りないものがあるため,protocolを定義してextensionします.
protocol FastEnumKey: Hashable {
class var count: Int {get}
}
というプロトコルを定義します.count
とは要は列挙の個数です.
そして例えば,上のIntEnum
をFastEnumKey
プロトコルに準拠させます.
extension IntEnum: FastEnumKey {
static var count: Int { return 3 }
}
この場合,列挙数は3
と決め打ちです.extensionによって後からcase
を追加することは不可能なので決め打ちで問題ないと思います.
あとは,EnumDictionary
を組み込みのディクショナリーに似せて軽く実装するだけ.このとき,KeyのタイプをFastEnumKey
に制約します.(以下のコードはここからほぼ引用してます.)
struct EnumDictionary<Key: FastEnumKey, Value> {
var values = [Value?](count: Key.count, repeatedValue: nil)
subscript(key: Key) -> Value? {
get {
return values[key.hashValue] // hashValueは配列のインデックスである.
}
set {
values[key.hashValue] = newValue!
}
}
}
hashValue
値がそのまま配列のインデックスに使えるのでとてもシンプル.
簡単とはいえ,いちいちextensionしなければならないのが難点ですが,列挙の個数だけは今のSwiftのリフレクションを使っても取得できないので仕方ありません.
ところで,enumに設定した値はどこにあるの?
enum
ではfromRaw
やtoRaw
というメソッドで値<==>列挙
変換ができますが,この変換テーブルはメモリ上に隠されているみたいです.enumのサイズが
sizeofValue(StrEnum.A) == 1
なのです.(caseが255個以下ならサイズは1, それ以上なら2になることは確かめた.)
そしてその1バイトはhashValue
値になっていました.設定した値をプロパティとして包含してるわけではないんですね.
変換テープルのアドレスが分かれば,上のcount
は取得できそうなのですけどね.ちょっと残念です.