Swift: Hashable Struct with Dictionary Property

Swift: Hashable struct with dictionary property

I think you need to review your data model if you have to use a whole struct as a dictionary key. Anyhow, here's one way to do it:

internal struct MapKey: Hashable {
internal let id: String
internal let values: [String:String]

var hashValue: Int {
get {
var hashString = self.id + ";"
for key in values.keys.sort() {
hashString += key + ";" + values[key]!
}

return hashString.hashValue
}
}
}

func ==(lhs: MapKey, rhs: MapKey) -> Bool {
return lhs.id == rhs.id && lhs.values == rhs.values
}

This assumes that you don't have semicolon (;) in id or in the keys and values of values. Hasable implies Equatable so you don't need to declare it conforming to Equatable again.

Hashable struct with interchangeable properties?

This is how Hasher works

https://developer.apple.com/documentation/swift/hasher

However, the underlying hash algorithm is designed to exhibit
avalanche effects: slight changes to the seed or the input byte
sequence will typically produce drastic changes in the generated hash
value.

So the problem here in hash(into:) func

Since the sequence matters combine operation is not commutative. You should find some other function to be a hash for this struct. In your case the best option is

    func hash(into hasher: inout Hasher) {
hasher.combine(leftOperand & rightOperand)
}

As @Martin R pointed out to have less collisions it's better to use ^

    func hash(into hasher: inout Hasher) {
hasher.combine(leftOperand ^ rightOperand)
}

Make struct Hashable?

Simply return dbName.hashValue from your hashValue function. FYI - the hash value does not need to be unique. The requirement is that two objects that equate equal must also have the same hash value.

struct PetInfo: Hashable {
var petName: String
var dbName: String

var hashValue: Int {
return dbName.hashValue
}

static func == (lhs: PetInfo, rhs: PetInfo) -> Bool {
return lhs.dbName == rhs.dbName && lhs.petName == rhs.petName
}
}

Conforming to Hashable protocol?

You're missing the declaration:

struct DateStruct: Hashable {

And your == function is wrong. You should compare the three properties.

static func == (lhs: DateStruct, rhs: DateStruct) -> Bool {
return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day
}

It's possible for two different values to have the same hash value.

How to handle hash collisions for Dictionaries in Swift

func ==(lhs: MyStructure, rhs: MyStructure) -> Bool {
return lhs.hashValue == rhs.hashValue
}

Note the global function to overload the equality operator (==) in order to conform to the Equatable Protocol, which is required by the Hashable Protocol.

Your problem is an incorrect equality implementation.

A hash table (such as a Swift Dictionary or Set) requires separate equality and hash implementations.

hash gets you close to the object you're looking for; equality gets you the exact object you're looking for.

Your code uses the same implementation for hash and equality, and this will guarantee a collision.

To fix the problem, implement equality to match exact object values (however your model defines equality). E.g.:

func ==(lhs: MyStructure, rhs: MyStructure) -> Bool {
return lhs.id == rhs.id
}

Struct hash for different types

You can make a protocol containing all the shared data, so both S1 and S2 go through the same hasher.

I created a SharedData protocol containing the shared properties (only data in this case) and gave it a Hashable implementation. Then S1 and S2 now conform to SharedData. It looks like this:

protocol SharedData: Hashable {
var data: String { get }
}

extension SharedData {
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
}

struct S1: SharedData {
let data: String
let otherData: String

// ... Equality & Hash functions, both only use data
}
struct S2: SharedData {
let data: String
let otherData: Int

// ... Equality & Hash functions, both only use data
}

And the comparison looks like this:

print(S1(data: "hi", otherData: "there").hashValue)
print(S2(data: "hi", otherData: 1).hashValue)

let set: Set<Int> = [S1(data: "Hello", otherData: "world!").hashValue, S1(data: "abc", otherData: "123").hashValue]

let obj2 = S2(data: "Hello", otherData: 0)
print(set.contains(obj2.hashValue))

Prints:

-5068166682035457964  // <- these change every program execution
-5068166682035457964
true

Unfortunately it's not possible to do the following, yet:

let set: Set<SharedData> = [S1(data: "Hello", otherData: "world!"), S1(data: "abc", otherData: "123")]

Error:

Protocol 'SharedData' as a type cannot conform to 'Hashable'

I believe SE-0309 (Unlock existentials for all protocols) could fix this.

What is the use of hashable protocol in swift4?

To make an object conform to Hashable we need to provide a hashValue property that will return a unique, consistent number for each instance.
The Hashable protocol inherits from Equatable, so you may also need to implement an == function.

Note: If two objects compare as equal using == they should also generate the same hash value, but the reverse isn’t true – hash collisions can happen.

Before Swift 4.1, conforming to Hashable was complex because you needed to calculate a hashValue property by hand.
In Swift 4.1 this improved so that hashValue could be synthesized on your behalf if all the properties conform to Hashable .
Swift 4.2 introduces a new Hasher struct that provides a randomly seeded, universal hash function to make all our lives easier. Refer for more

Why does struct need to conform to Hashable as well as Generic array when converting to a dictionary

There's no need to make CustomSet conform to Hashable. Simply adding the Hashable restriction to the generic type parameter solves the error

Cannot subscript a value of incorrect or ambiguous type

,which is expected, since a Dictionary's keys need to conform to Hashable.

Btw the syntax for declaring conformance to several protocols is &, not ,, so there's no need for the where clause.

struct CustomSet<T : Comparable & Hashable> {
private var list: [T]
init(_ list: [T]){
let uniqueKeys = list.reduce(into: [:]){ dict, item in
dict[item, default: 0] += 1
}.keys
self.list = Array(uniqueKeys)
}
}

extension CustomSet : Equatable {
static func == (lhs: CustomSet, rhs: CustomSet) -> Bool {
return lhs.list.count == rhs.list.count && lhs.list.sorted() == rhs.list.sorted()
}
}

Moreover, you can simply do self.list = Array(Set(list)) in the initializer to store only the unique elements from the input in your list, there's no need for messing around with a Dictionary.



Related Topics



Leave a reply



Submit