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.

Please enclose your code in triple backticks to make it easier to read

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

https://galgebra.readthedocs.io/en/latest/

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