I need to make a custom struct conform to Hashable
so that I can use it as a Dictionary key type. The challenge though, is that two of the struct's properties are interchangeable for the purpose of identifying a unique instance.
Here's a simplified example to illustrate the problem:
struct MultiplicationQuestion {
let leftOperand: Int
let rightOperand: Int
var answer: Int { return leftOperand * rightOperand }
}
The two important properties for identifying a unique MultiplicationQuestion
are leftOperand
and rightOperand
, but it doesn't matter what order they are in, because '1 x 2' is essentially the same question as '2 x 1'. (For reasons I won't go into here, they need to be kept as separate properties.)
I tried defining Hashable
conformance as follows, knowing that there is a conflict between equality as I've defined it for ==
and what the built-in Hasher is going to do:
extension MultiplicationQuestion: Hashable {
static func == (lhs: MultiplicationQuestion, rhs: MultiplicationQuestion) -> Bool {
return (lhs.leftOperand == rhs.leftOperand && lhs.rightOperand == rhs.rightOperand) || (lhs.leftOperand == rhs.rightOperand && lhs.rightOperand == rhs.leftOperand)
}
func hash(into hasher: inout Hasher) {
hasher.combine(leftOperand)
hasher.combine(rightOperand)
}
}
I tested this by creating two Sets of questions and performing various operations on them:
var oneTimesTables = Set<MultiplicationQuestion>()
var twoTimesTables = Set<MultiplicationQuestion>()
for i in 1...5 {
oneTimesTables.insert( MultiplicationQuestion(leftOperand: 1, rightOperand: i) )
twoTimesTables.insert( MultiplicationQuestion(leftOperand: 2, rightOperand: i) )
}
let commonQuestions = oneTimesTables.intersection(twoTimesTables)
let allQuestions = oneTimesTables.union(twoTimesTables)
The hoped-for result (wishful thinking) is that commonQuestions
contains one question (1 x 2), while allQuestions
contains nine questions, having removed the duplicate.
The actual result however is unpredictable. If I run the playground multiple times, I get different results. Most of the time, commonQuestions.count
is 0, but sometimes it is 1. And most of the time, allQuestions.count
is 10, but sometimes it is 9. (I'm not sure what I was expecting, but this inconsistency was certainly a surprise!)
How can I make the hash(into:)
method generate the same hash for two instances where the properties are the same but reversed?