Make struct Hashable? - swift

I'm trying to create a dictionary of the sort [petInfo : UIImage]() but I'm getting the error Type 'petInfo' does not conform to protocol 'Hashable'. My petInfo struct is this:
struct petInfo {
var petName: String
var dbName: String
}
So I want to somehow make it hashable but none of its components are an integer which is what the var hashValue: Int requires. How can I make it conform to the protocol if none of its fields are integers? Can I use the dbName if I know it's going to be unique for all occurrences of this struct?

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
}
}

As of Swift 5 var hashValue:Int has been deprecated in favour of func hash(into hasher: inout Hasher) (introduced in Swift 4.2), so to update the answer #rmaddy gave use:
func hash(into hasher: inout Hasher) {
hasher.combine(dbName)
}

Related

How is possible to a property in a Set of objects conform to Hashable protocol in Swift?

I'm implementing a tag feature for an item list. I'm trying implementing a computed property that calculate the tag set in a list of item as the union of different tag set of each item like:
item1 - [tag1, tag2]
item2 - [tag1, tag3]
output > [tag1, tag2, tag3]
The problem is that the Tag class need to be hashable and an UID is given at each instance of the tag, even tag with the same description. So when I loop in all the item taglist to create the tag set of the whole list the results is wrong like:
output > [tag1, tag1, tag2, tag3]
Here's the code:
class TTDItem: Identifiable {
var id: UUID = UUID()
var itemDesc: String
var itemTags: Set<TTDTag>
init(itemDesc: String, itemTags: Set<TTDTag>) {
self.itemDesc = itemDesc
self.itemTags = itemTags
}
}
class TTDTag: Identifiable, Hashable {
var TTDTagDesc: String
var hashValue: Int {
return id.hashValue
}
init(TTDTagDesc: String){
self.TTDTagDesc = TTDTagDesc
}
static func ==(lhs: TTDTag, rhs: TTDTag) -> Bool {
return lhs.id == rhs.id
}
}
class TTDItemList {
var itemList: [TTDItem]
init(itemList: [TTDItem]) {
self.itemList = itemList
}
//(...)
// implement computed property taglist
func itemTagsList()-> Set<TTDTag> {
var tagSet = Set<TTDTag>()
for item in self.itemList {
tagSet = tagSet.union(item.itemTags)
}
return tagSet
}
}
How can I access only to the tag description in order to obtain the correct result? Thanks
This can be done using reduce and the union function
func itemTagsList()-> Set<TTDTag> {
itemList.map(\.itemTags).reduce(Set<TTDTag>()){ $0.union($1) }
}
Note that for Hashable you need to implement hash(into:) for TTDTag
func hash(into hasher: inout Hasher) {
hasher.combine(TTDTagDesc)
}
You should start property names with a lowercase letter and make them descriptive so maybe you could change TTDTagDesc to tagDescription
hashValue is deprecated (and you should have received a warning for this). You should override hash(into:) and use your TTDTagDesc property there.
Also, you should implement id to return TTDTagDesc, because that is what identifies a tag.
class TTDTag: Identifiable, Hashable {
var TTDTagDesc: String
// Note here
func hash(into hasher: inout Hasher) {
hasher.combine(TTDTagDesc)
}
// and here
var id: String { TTDTagDesc }
init(TTDTagDesc: String){
self.TTDTagDesc = TTDTagDesc
}
static func ==(lhs: TTDTag, rhs: TTDTag) -> Bool {
return lhs.id == rhs.id
}
}

Comparing specific values in Dictionary key

I have struct that I use for Dictionary key:
struct MyKey {
let name: String
let flag: Bool
}
What I want is, to change the "flag" property and still being able to find a value specified by that key. Here is complete playground example:
struct MyKey {
let name: String
let flag: Bool
}
extension MyKey: Hashable {
static func == (lhs: MyKey, rhs: MyKey) -> Bool {
return lhs.name == rhs.name
}
}
var fKey = MyKey(name: "fKey", flag: false)
var sKey = MyKey(name: "sKey", flag: true)
var dictFirst: [MyKey: String] = [fKey: "fValue",
sKey: "sValue"]
var dictSecond: [MyKey: String] = [fKey: "fValue",
sKey: "sValue"]
let changedFKey = MyKey(name: "fKey", flag: true)
print(dictFirst[changedFKey]) // Prints nil, want it to be fValue
In fact, in extension I tried to specify that I only cares about "name", but still It's not work as intended
You also have to implement hash value correctly, the generated hash takes flag into account:
extension MyKey: Hashable {
static func == (lhs: MyKey, rhs: MyKey) -> Bool {
return lhs.name == rhs.name
}
func hash(into hasher: inout Hasher) {
hasher.combine(name)
}
}
However, in principle this is bad design. If the flag does not affect the key, it shouldn't be part of the key. It would be better to index by name directly.

Cache results of Swift hash(into:) Hashable protocol requirement

I have a class being heavily used in Sets and Dictionaries.
For performance reasons this class implements Hashable in a old way and caches the computed hash:
let hashValue: Int
init(...) {
self.hashValue = ...
}
In Xcode 10.2 I see a warning, that hashValue is deprected and will stop being a protocol requirement soon.
What bothers me is a lack of ability to cache the computed hash anyhow, because hash(into:) does not return anything.
func hash(into hasher: inout Hasher) {
hasher.combine(...)
}
Consider the following example in a playground
class Class: Hashable {
let param: Int
init(param: Int) {
self.param = param
}
static func ==(lhs: Class, rhs: Class) -> Bool {
return lhs.param == rhs.param
}
public func hash(into hasher: inout Hasher) {
print("in hash")
hasher.combine(param)
}
}
var dict = [Class: Int]()
let instance = Class(param: 1)
dict[instance] = 1
dict[instance] = 2
You will see the following logs
in hash
in hash
in hash
I have no idea, why we see 3 calls instead of 2, but we do =).
So, every time you use a same instance as a dictionary key or add this instance into a set, you get a new hash(into:) call.
In my code such an overhead turned out to be very expensive. Does anyone know a workaround?
One option is to create your own Hasher, feed it the "essential components" of your instance and then call finalize() in order to get out an Int hash value, which can be cached.
For example:
class C : Hashable {
let param: Int
private lazy var cachedHashValue: Int = {
var hasher = Hasher()
hasher.combine(param)
// ... repeat for other "essential components"
return hasher.finalize()
}()
init(param: Int) {
self.param = param
}
static func ==(lhs: C, rhs: C) -> Bool {
return lhs.param == rhs.param
}
public func hash(into hasher: inout Hasher) {
hasher.combine(cachedHashValue)
}
}
A couple of things to note about this:
It relies on your "essential components" being immutable, otherwise a new hash value would need calculating upon mutation.
Hash values aren't guaranteed to remain stable across executions of the program, so don't serialise cachedHashValue.
Obviously in the case of storing a single Int this won't be all that effective, but for more expensive instances this could well help improve performance.

How should I compute an hashValue?

I saw several examples of implementations of the Hashable variable hashValue, such as these:
extension Country: Hashable {
var hashValue: Int {
return name.hashValue ^ capital.hashValue ^ visited.hashValue
}
}
var hashValue: Int {
return self.scalarArray.reduce(5381) {
($0 << 5) &+ $0 &+ Int($1)
}
}
var hashValue : Int {
get {
return String(self.scalarArray.map { UnicodeScalar($0) }).hashValue
}
}
and so on.
In some cases OR, XOR or BITWISE operators are used. In other cases, other algorithms, map or filter functions etc.
Now I'm a bit confused. What is the rule of thumb to compute a good hashValue?
In the simplest case with two string variables, should I combine these two with an OR operator?
From Swift 4.2, you can use Hasher
Swift 4.2 implements hashing based on the SipHash family of
pseudorandom functions, specifically SipHash-1-3 and SipHash-2-4, with
1 or 2 rounds of hashing per message block and 3 or 4 rounds of
finalization, respectively.
Now if you want to customize how your type implements Hashable, you
can override the hash(into:) method instead of hashValue. The
hash(into:) method passes a Hasher object by reference, which you call
combine(_:) on to add the essential state information of your type.
// Swift >= 4.2
struct Color: Hashable {
let red: UInt8
let green: UInt8
let blue: UInt8
// Synthesized by compiler
func hash(into hasher: inout Hasher) {
hasher.combine(self.red)
hasher.combine(self.green)
hasher.combine(self.blue)
}
// Default implementation from protocol extension
var hashValue: Int {
var hasher = Hasher()
self.hash(into: &hasher)
return hasher.finalize()
}
}

Implementing a hash combiner in Swift

I'm extending a struct conform to Hashable. I'll use the DJB2 hash combiner to accomplish this.
To make it easy to write hash function for other things, I'd like to extend the Hashable protocol so that my hash function can be written like this:
extension MyStruct: Hashable {
public var hashValue: Int {
return property1.combineHash(with: property2).combineHash(with: property3)
}
}
But when I try to write the extension to Hashable that implements `combineHash(with:), like this:
extension Hashable {
func combineHash(with hashableOther:Hashable) -> Int {
let ownHash = self.hashValue
let otherHash = hashableOther.hashValue
return (ownHash << 5) &+ ownHash &+ otherHash
}
}
… then I get this compilation error:
/Users/benjohn/Code/Nice/nice/nice/CombineHash.swift:12:43: Protocol 'Hashable' can only be used as a generic constraint because it has Self or associated type requirements
Is this something that Swift won't let me do, or am I just doing it wrong and getting an unhelpful error message?
Aside A comment from JAL links to a code review of a swift hash function that is also written by Martin who provides the accepted answer below! He mentions a different hash combiner in that discussion, which is based on one in the c++ boost library. The discussion really is worth reading. The alternative combiner has fewer collisions (on the data tested).
Use the method hash(into:) from the Apple Developer Documentation:
https://developer.apple.com/documentation/swift/hashable
struct GridPoint {
var x: Int
var y: Int
}
extension GridPoint: Hashable {
static func == (lhs: GridPoint, rhs: GridPoint) -> Bool {
return lhs.x == rhs.x && lhs.y == rhs.y
}
func hash(into hasher: inout Hasher) {
hasher.combine(x)
hasher.combine(y)
}
}
You cannot define a parameter of type P if P
is a protocol which has Self or associated type requirements.
In this case it is the Equatable protocol from which Hashable
inherits, which has a Self requirement:
public static func ==(lhs: Self, rhs: Self) -> Bool
What you can do is to define a generic method instead:
extension Hashable {
func combineHash<T: Hashable>(with hashableOther: T) -> Int {
let ownHash = self.hashValue
let otherHash = hashableOther.hashValue
return (ownHash << 5) &+ ownHash &+ otherHash
}
}