Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overload object comparison when adding to a set in Julia?

Tags:

types

julia

Is there a way to overload how Base.Set does its object comparisons in Julia?

I tried overloading isequal and ==, but my objects are still marked as different when they should be the same.

E.g.

type Test
    x
    y
end

function ==(a::Test, b::Test)
    return a.x == b.x && a.y == b.y
end

Set([Test(2,3), Test(2,3)])

gives

Set([Test(2,3),Test(2,3)])
like image 649
tlnagy Avatar asked Jan 21 '16 23:01

tlnagy


2 Answers

There is a useful package for composite type comparisons, AutoHashEquals:

using AutoHashEquals

@auto_hash_equals type Test
    x
    y
end

X = Test(2, 3)
Y = Test(2, 3)
Z = Test(1, 3)

X == Y  # = true
X == Z  # = false

Set([X, Y, Z]) # = Set([Test(2,3),Test(1,3)])

However, like @Gnimuc-K pointed out "this macro is only useful for mutable types when they are used as immutable records."

like image 194
amrods Avatar answered Oct 31 '22 19:10

amrods


A Set is a kind of Dict with values of type Void: ref

type Set{T} <: AbstractSet{T}
    dict::Dict{T,Void}

    Set() = new(Dict{T,Void}())
    Set(itr) = union!(new(Dict{T,Void}()), itr)
end  

And Julia-lang documentation describes Dic type as follow:

Dict is the standard associative collection. Its implementation uses hash() as the hashing function for the key, and isequal() to determine equality. Define these two functions for custom types to override how they are stored in a hash table.

Check, dict.jl, and look for ht_keyindex2() and ht_keyindex() functions. Both will return an index only if these two conditions are true:

if !isslotmissing(h,index) && isequal(key,keys[index])
    return index
end

Where in the above: index = hashindex(key, sz)
Julia uses hash() function do the task of hashing:

hash(x[, h])
Compute an integer hash code such that isequal(x,y) implies hash(x)==hash(y). The optional second argument h is a hash code to be mixed with the result. New types should implement the 2-argument form, typically by calling the 2-argument hash method recursively in order to mix hashes of the contents with each other (and with h). Typically, any type that implements hash should also implement its own == (hence isequal) to guarantee the property mentioned above.

So with these preparations it is obvious that overriding Base.==, is not the right & complete way to do the task, but

  1. We need to override hash() function to return the same hashindex and
  2. Instead of Base.== it is sufficient to override Base.isequal

Code:

type Test
    x
    y
end

Base.hash(a::Test, h::UInt) = hash(a.y, hash(a.x, hash(:Test, h)))
Base.isequal(a::Test, b::Test) = Base.isequal(hash(a), hash(b))
Set([Test(2,3), Test(2,3)])

The point is, although Test(2,3) == Test(2,3) #=> false it works as expected.

like image 43
Reza Afzalan Avatar answered Oct 31 '22 20:10

Reza Afzalan