1

I have a class that defines a dictionary:

class InventoryDictionary <U : Hashable, V> : Equatable {

    var dictionary : [ U : V? ]  = [:]

    static func ==(lhs: InventoryDictionary, rhs: InventoryDictionary) -> Bool {
       return    lhs.dictionary.keys == rhs.dictionary.keys
              && lhs.dictionary.values == rhs.dictionary.values
    }
}

XCode shows an error:

Referencing operator function '==' on 'Equatable' requires that 'Dictionary.Values' conform to 'Equatable'

I'm trying to make InventoryDictionary conform to the Equatable Swift protocol.

In the == overload function, dictionary.keys can be compared for equality but not the values (which is understandable, kind of).

But it isn't clear to me from the message whether or not that's solvable without me writing code to check each V (value) or whether there's some way to make Swift-generic V equatable.

What's a good approach to this?

clearlight
  • 12,255
  • 11
  • 57
  • 75
  • U and V must conform to equatable. – Fogmeister May 10 '23 at 17:14
  • 2
    Unrelated to your problem, but the order of keys in a dictionary is not deterministic and could vary depending on the order in which items are added/replaced or deleted. So even two “equal” dictionaries could have different ordering in their arrays of keys and your equality test will incorrectly claim they are different. The values are *probably* returned in the same order as the keys, so they have the same problem. – Geoff Hackworth May 10 '23 at 17:15
  • 2
    A quick playground test suggests that Swift already supports comparing dictionaries for equality if both key and value types are equatable so you can just use `lhs.dictionary == rhs.dictionary` in your implementation (if both U and V are equatable) – Geoff Hackworth May 10 '23 at 17:24
  • 1
    You couldn’t simply use sets because you would lose the association between which key had which value and you might have duplicate values. As I said, I think you can just use the language-provided equality for dictionaries. – Geoff Hackworth May 10 '23 at 17:26

2 Answers2

2

First of all, V must be Equatable, too. That is, the declaration should be

class InventoryDictionary<U: Hashable, V: Equatable> : Equatable {

However, you shouldn't use a class. Preferably you should use a struct for this use case because then equability will be generated for you.

struct InventoryDictionary<U: Hashable, V: Equatable> : Equatable {
    var dictionary: [U: V] = [:]
}

If you really need this to be a reference type:

class InventoryDictionary<U: Hashable, V: Equatable> : Equatable {
    var dictionary: [U: V] = [:]

    static func ==(lhs: InventoryDictionary, rhs: InventoryDictionary) -> Bool {
        return lhs.dictionary == rhs.dictionary
    }
}

Note that I have also removed the optional from the dictionary value. It shouldn't be there unless you really really want to store keys with a nil value.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
2

To add to Sulthan's answer:

If the hashability and equability of V aren't intrinsic to the InventoryDictionary (i.e. they're only needed for the conformances to Hashable and Equatable), then you can use conditional conformances:

// `U` has to be `Hashable` in any case, because it's used as a key
class InventoryDictionary<U: Hashable, V> {
    var dictionary: [U: V?] = [:]

    // ...
}

extension InventoryDictionary: Equatable where V: Equatable {}
extension InventoryDictionary: Hashable where V: Hashable {}
Alexander
  • 59,041
  • 12
  • 98
  • 151