Как использовать тип пользовательского класса в качестве ключевого словаря в словарях Swift?
Я работаю над проектом, созданным с помощью Swift, и я пытаюсь создать словарь для хранения объектов пользовательского класса Pixel (KEY, для хранения информации о цвете, такой как значения RGB) и значений int (значение для подсчитывая, сколько раз один и тот же цвет появляется на одном изображении).
Если это в С#, рабочий код должен быть:
Dictionary<Pixel, int> colorDictionary = new Dictionary< Pixel, int> ()
;
В Swift я попытался:
var colorDictionary = Dictionary<Pixel, Int>()
Однако ошибка, которую я получил:
"Тип" Пиксель "не соответствует протоколу" Hashable ""
Что мне делать, чтобы решить эту проблему? Большое спасибо!
Ответы
Ответ 1
Любой пользовательский тип, который вы хотите использовать ключ словаря, должен соответствовать протоколу Hashable
.
Этот протокол имеет одно свойство, которое вы должны реализовать.
var hashValue: Int { get }
Используйте это свойство для генерации int, который словарь может использовать для поиска. Вы должны попытаться сделать так, чтобы сгенерированный hashValue был уникальным для каждого пикселя.
В книге Swift есть следующее примечание, поэтому вы можете сделать случайный хеш (пока он уникален):
Значение, возвращаемое свойством типа hashValue
, не обязательно должно быть одинаковым для разных исполнений одной и той же программы или в разных программах.
Обратите внимание, что поскольку Hashable
наследуется от Equatable
, вы также должны реализовать:
func ==(_ lhs: Self, _ rhs: Self) -> Bool.
Я не уверен, какова внутренняя структура вашего пикселя, но вы, вероятно, можете считать два пиксела равными, если оба имеют одинаковые значения "x" и "y". Окончательная логика зависит от вас.
Измените это, как вам нужно:
struct Pixel : Hashable {
// MARK: Hashable
var hashValue: Int {
get {
// Do some operations to generate a unique hash.
}
}
}
//MARK: Equatable
func ==(lh: Pixel, rh: Pixel) -> Bool {
return lh.x == rh.x && rh.y == lh.y
}
Ответ 2
Продолжая рассказ Энди Ибанеса. Ярлык для реализации hashValue заключается в том, чтобы скомпоновать String hashValue. Вы могли бы сделать что-то вроде этого.
class Pixel: Hashable {
var r:Int = 0;
var g:Int = 0;
var b:Int = 0;
var a:Int = 0;
var hashValue: Int {
get {
return "\(r)\(g)\(b)\(a)".hashValue;
}
}
}
Вам также нужна функция Equatable, потому что hashValues в этом случае являются просто быстрой проверкой для проверки двух объектов, не равных. Поскольку для двух объектов может иметь один и тот же hashValue, но не быть равным, вам все равно нужно реализовать == для определения равенства, как показано ниже.
func ==(lhs: Pixel, rhs: Pixel) -> Bool{
if lhs.r != rhs.r{
return false;
}
if lhs.g != rhs.g{
return false;
}
if lhs.b != rhs.b{
return false;
}
if lhs.a != rhs.a{
return false;
}
return true;
}
Ответ 3
Реализовать протокол Hashable следующим образом:
class Pixel : Hashable {
var alpha, red, green, blue : Int
init(red: Int, green: Int, blue: Int, alpha: Int) {
self.red = red
self.green = green
self.blue = blue
self.alpha = alpha
}
var hashValue : Int {
get {
return alpha ^ red ^ green ^ blue
}
}
}
func ==(lhs: Pixel, rhs: Pixel) -> Bool {
return lhs.alpha == rhs.alpha && lhs.red == rhs.red && lhs.green == rhs.green && lhs.blue == rhs.blue
}
Ответ 4
Начиная с swift 4.2, hashValue
устарела как требование Hashable.
Теперь, если вы хотите настроить, как ваш тип реализует Hashable
, вы можете переопределить метод hash(into:)
вместо hashValue. Метод hash(into:)
передает объект Hasher по ссылке, которую вы вызываете combine(_:)
чтобы добавить необходимую информацию о состоянии вашего типа.
class Pixel {
var alpha, red, green, blue : Int
}
// Hashable implementation
extension Pixel : Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(self.red)
hasher.combine(self.green)
hasher.combine(self.blue)
hasher.combine(self.alpha)
}
}