# Comparing Dictionaries of Dictionaries

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.

Whats is wrong with `D1.sdops == D2.sdops` ?
Julia compares dictionaries by comparing all keys and values already.

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.

`````````
like this
```
``````

Please show two dicts that you want to be equal so we can see why `==` says they are not.

Printing out the two dictionaries -

``````D1.sdops = Dict{Pdop, Any}(Pdop(Dict{Any, Any}(y => 1, x => 1)) => 1)
D2.sdops = Dict{Pdop, Any}(Pdop(Dict{Any, Any}(y => 1, x => 1)) => 1)
``````

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

minimal.jl (4.72 KB)

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.

Thank both of you very much. Neither my `Pdop` or `Sdop` structs are mutable.

Yeah but the dict inside it is mutable, and changes to that dict might affect the hash of the outer container.

1 Like

Is using copy on the dictionary enough or should I do deepcopy?

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?

My view on `deepcopy` is that `deepcopy` shouldnâ€™t exist and any users should explicitly define their own `copy` methods instead.

Agreed.

You are both telling me that for my case be very careful using dictionaries of better yet find an alternative. I will find an alternative.

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.

1 Like