##
is basically hashCode
, but it deals with some edge cases regarding certain value types (such as Double
). You can read about it more here.
For an integer, the hash-code will always be the same as the integer itself, so you can just use the integer.
val i: Int = ???
i == i.hashCode // true
i == i.## // true
As for identityHashCode
, it has rather different semantics than hashCode
. The former will generate an arbitrary hash, while the latter is expected to be compatible with equals
(in the class it is defined).
The default implementation of equals
is reference equality (==
in Java, eq
in Scala), and the default hashCode
implementation is identityHashCode
. In this case, hashCode
is compatible with equals
as it is guaranteed that the object is equal only to itself, and identityHashCode
will always generate the same hash for the same object.
When implementing a different equals
that allows separate objects to be considered equal (based on some deterministic logic), one must implement hashCode
so that every pair of objects that are considered equal will generate the same hash code.
Objects which are not equal may still generate the same hash code – that would be considered a collision – and a good implementation of the hashCode
method will try to minimize the amount of collisions (that’s the meaning of a “well-distributed” hash). The reason we don’t want collisions is a direct result of the main usage of hashCode
; i.e, lookup in a hash table. When looking up an object in a hash table, we must test equality of that object with every other object in the table that has the same hash code, so we would like that number to be as low as possible.
The MurmurHash3
algorithm should provide better distribution (less collisions) than a simple ^
.
As for using identityHashCode
in your case, it is so we could emulate the equality logic of eq
between the children. We know that if two objects are the same (eq
), they ought to generate the same identityHashCode
; however, it is possible that different objects will also generate the same identityHashCode
(there is no guarantee for this method to be unique), and then we would get a collision.
You could try mitigating these collisions by generating a unique id for every object you create, though I’m not sure if that would yield much of an improvement on the hash-lookup performance:
case class BddNode {
private val id: Long = rollingNumber.getAndIncrement()
override def hashCode(): Int = {
var h = initialValueThatYouChoose
h = mix(h, label)
h = mix(h, positive.id.toInt)
h = mixLast(h, negative.id.toInt)
finalizeHash(h, 3)
}
}
object BddNode {
// you could initialize this with a `DynamicVariable` like you did with `numAllocations`.
private val rollingNumber = new java.util.concurrent.atomic.AtomicLong()
}
I’m not sure this will be effective, as the potential number of the objects you will allocate may exceed the range of Int
(this is why the id
is Long
), and since a hashCode
is Int
, you may still get collisions.