Как использовать тип пользовательского класса в качестве ключевого словаря в словарях 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)
    }
}