I am trying to compare two dictionaries to see if they are equal. In the code the dictionaries are S1.sdops and S2.sdops
function (==)(D1::Sdop,D2::Sdop)::Bool
if string(D1.s) != string(D2.s)
return false
end
keys1 = keys(D1.sdops)
keys2 = keys(D2.sdops)
vkeys1 = collect(keys1)
vkeys2 = collect(keys2)
if vkeys1 != vkeys2
return false
end
for key1 in vkeys1
A = D1.sdops[key1]
i2 = findall(x->x==key1,vkeys2)
B = D2.sdops[vkeys2[i2[1]]]
if A != B
return false
end
end
return true
end
When I test “vkeys1 == vkeys2” is true but if take a key from vkeys1, say key1, and use it in in S2.dops, say S2.dops[key1], I get an error. That is why I calculate “i2” so I can use the correct entry in “vkeys2.” Is there a simpler way to compare the two dictionaries that what I am doing now? Note that the keys are themselves dictionaries.
Because it tells me the dictionaries are not equal when they are. Note that if I use string(D1.sdops) == string(D2.sdops) that works. If I print S1.sdops I get -
Dict{Pdop, Any}(Pdop(Dict{Any, Any}(y => 1, x => 1)) => 1)
I have defined “==” for the “Pdop” structure and that works.
Note that D1.sdops == D2.sdops is false but string(D1.sdops) == string(D2.sdops) is true. For the struct Pdop I defined the operator == and when tested that works. The thing I noticed is that when I used a key from D1.sdops to reference an entry in D2.sdops I got an error.
Ok I can’t see the problem yet. Next step please create a minimal runnable example that demonstrates the problem. Something that I can just paste into my REPL and it will run.
It seems like your code (reformatted below) is relying on the key order of Dicts. Dict order is not guaranteed so you can’t rely on it. It’s also making string comparisons, I’m not sure why.
function (==)(D1::Sdop,D2::Sdop)::Bool
if string(D1.s) != string(D2.s)
return false
end
keys1 = keys(D1.sdops)
keys2 = keys(D2.sdops)
vkeys1 = collect(keys1)
vkeys2 = collect(keys2)
if vkeys1 != vkeys2
return false
end
for key1 in vkeys1
A = D1.sdops[key1]
i2 = findall(x->x==key1,vkeys2)
B = D2.sdops[vkeys2[i2[1]]]
if A != B
return false
end
end
return true
end
I am attaching as minimal as I can make it. I don’t use the REPL. I use the geany editor and run the program in the editor julia ./minimal.jl. I am using the Sybolics.jl symbolic algebra package. Pdop is a generalize partial derivative operator and Sdop is a scalar differential operator (linear combination of Pdop’s and scalar coefficients which could be algebraic expressions). I am working on converting what I originally did here in Python into Julia. -
Deleting your implementations of == and using AutoHashEquals.@auto_hash_equals on struct Pdop seems to work. But you do have to be careful about mutating hashable values.
I think your problem is that you haven’t defined a custom hash for Pdop (@jar1 beat me to it), and the default method computes different values for these two keys since they wrap dicts of different identity, even if the wrapped dicts happen to have the same content. Here’s a minimal example showing how to define hash and == to make this work:
struct Foo{D<:Dict}
d::D
end
f1 = Foo(Dict('a' => 1))
f2 = Foo(Dict('a' => 1))
@show(f1)
@show(f2)
# Output:
# f1 = Foo{Dict{Char, Int64}}(Dict('a' => 1))
# f2 = Foo{Dict{Char, Int64}}(Dict('a' => 1))
# They look equal. Do they compare equal?
@show(hash(f1))
@show(hash(f2))
@show(f1 == f2)
# Output:
# hash(f1) = 0xf856fde93fedf01f
# hash(f2) = 0x206a57ed32981578
# f1 == f2 = false
# Nope---different hashes and not ==
# Define custom hash and ==
Base.hash(a::Foo, h::UInt) = hash(a.d, h)
Base.:(==)(a::Foo, b::Foo) = isequal(a.d, b.d)
@show(hash(f1))
@show(hash(f2))
@show(f1 == f2)
# Output:
# hash(f1) = 0x08186ea4a12ae8a5
# hash(f2) = 0x08186ea4a12ae8a5
# f1 == f2 = true
# There we go! Same hash and ==
I suppose this is what @auto_hash_equals does for you.
As @jar1 points out, be careful with this. Once such a Pdop instance has been used as a dict key, the dict wrapped inside it must never be mutated, otherwise the outer dict would be rendered unusable.
Definitely deepcopy. The hash of the dict may depend on the actual content of any mutable containers nested inside it. For example:
julia> d = Dict('a' => [1, 2, 3])
Dict{Char, Vector{Int64}} with 1 entry:
'a' => [1, 2, 3]
julia> hash(d)
0x8f166356a55c01fc
julia> push!(d['a'], 4);
julia> hash(d) # Not the same as before
0x1c2f622f4dcb3ed6
But a better question is perhaps whether you should really be using an object that wraps a dict (or any other mutable container) as a key. Is it possible to make Pdop wrap a NamedTuple instead of a dict? Or simply extract the data from the dict and store them in dedicated fields in the Pdop struct?
The actual rule is a == b implies hash(a) == hash(b) for all a and b. You need to make sure that always holds. If you define hash and == or use AutoHashEquals.jl which defines them for you, it should be fine as long as you don’t then change the values later.
A guideline is once you hash a value, you must not mutate it afterwards. Looking quickly at your code I don’t notice any violations of that. So I’d guess you’re fine with a Dict and applying AutoHashEquals.jl to your structs.
The only complicating factor is that this code uses Symbolics.jl which does something special with ==. I don’t know much about Symbolics.jl so I can’t really help with that part.