I am using a structure where I want to use nested DefaultDicts. The outer one, I want to default to an empty DefaultDict, which in turn I want to have a default value of 1.0.
julia> d = DefaultDict{Int64, DefaultDict}(DefaultDict{Int64, Float64}(1.0))
DefaultDict{Int64,DefaultDict,DefaultDict{Int64,Float64,Float64}} with 0 entries
julia> d[1][1]
1.0
julia> d[1][1] = 4.0
4.0
julia> d[1][3]
1.0
julia> d[6][1]
4.0
I was not expecting the last result (which of course comes out the same whatever the value of the outer Dict index). I was expecting the outer DefaultDict to produce a new, empty inner DefaultDict and so default back to a 1.0. Instead, it seems to be using the existing inner DefaultDict as the new default for any new outer one.
Can you help me understand this and how to achieve my expected behaviour?
(I am new to Julia, and to programming in general, so if you think the issue is too blindingly obvious to need saying, please say it anyway.)
So what is happening is that
outer = DefaultDict{Int64, DefaultDict}(DefaultDict{Int64, Float64}(1.0))
is the same as:
inner = DefaultDict{Int64, Float64}(1.0)
outer = DefaultDict{Int64, DefaultDict}(inner)
So whenever outer
needs a default value,
it returns inner
.
Not a copy of inner, or a new DefaultDict
equal to inner
, but actually inner
itself.
Which means that mutating that returned value, is literally mutating inner
.
If rather than passing in a value, you pass in a function (or a constructor) as the default,
then DefaultDict will invoke that function and return the value.
You can use this to make it construct a new inner dict each time it wants one.
julia> construct_new_inner() = DefaultDict{Int64, Float64}(1.0);
julia> outer = DefaultDict{Int64, DefaultDict}(construct_new_inner)
DefaultDict{Int64,DefaultDict,var"##9#10"} with 0 entries
julia> outer[1][1]
1.0
julia> outer[1][1] = 4.0
4.0
julia> outer[1][3]
1.0
julia> outer[6][1]
1.0
Which can also be written as:
d = DefaultDict{Int64, DefaultDict}(()->DefaultDict{Int64, Float64}(1.0))
4 Likes