Initialize a Dict by broadcasting

I’d like to initialise a dictionary of type Dict{Date, Vector{Float64}} . Clearly, I can do

Dict(d => Vector{Float64}() for d in mydates)

but can I do it by broadcasting? I have tried

Dict(mydates .=> Vector{Float64}[])  # Vector{Float64}() also fails

but this returns a DimensionMismatch error.

Many thanks.

If mydates isn’t empty, then the broadcast you’re asking for doesn’t really make sense because one side is an empty container. There are plenty of ways to instantiate a Vector of Vector{Float64}, but one possible way that I think illustrates the source of the error is

Dict(mydates .=> [Vector{Float64}() for _ in 1:length(mydates)])
1 Like

or

Dict(d => Float64[] for d in mydates)

is the best you can do.

The reason is:

  1. it is readable.
  2. it is correct.

The point about correctness is that you need to make sure that you allocate a fresh vector for each dictionary entry, and this is what comprehension does (that is why in the solution by @cgeoga also a comprehension had to be used)

You most likely wanted to write something like:

Dict(mydates .=> Ref(Vector{Float64}()))

unfortunately this would not be correct as dictionary values would be aliases.

4 Likes

This would have been my understanding, too, up until I saw that (in a different context) I could do

Dict(mydates .=> 0)

or even

Dict(mydates .=> "this is a string of not inconsiderable length")

Is something akin to a constructor not being called in order to make multiple copies of the string?

Thanks for your patience.

String is immutable, so it does not have to be copied. It is not a problem to reuse it. Same with 0.

Also note that this works as numbers and strings are considered as 0-dimensional containers by broadcasting, so you do not have to wrap them in Ref.

1 Like

Shouldn’t it be d = Dict (mydates. => Ref (Float64 [])) instead?
Can you explain in more detail, perhaps with examples, this fact: “as dictionary values would be aliases.”?

1 Like

It was a typo, as I was copy-pasting from both solutions. Fixed.

An example of aliasing consequences:

julia> d = Dict(1:2 .=> Ref(Float64[]))
Dict{Int64, Vector{Float64}} with 2 entries:
  2 => []
  1 => []

julia> d[1] === d[2]
true

julia> push!(d[1], 100)
1-element Vector{Float64}:
 100.0

julia> d
Dict{Int64, Vector{Float64}} with 2 entries:
  2 => [100.0]
  1 => [100.0]
1 Like

To be clear also, this isn’t just a feature of Ref. The docs on broadcasting comment on this: wrapping elements in a container changes the way that broadcasting works. So this also has the same issue:

julia> x = rand(5);

julia> t = Float64[];

julia> d = Dict(x .=> [t]);

julia> push!(t, 1.0)
1-element Vector{Float64}:
 1.0

julia> d
Dict{Float64, Vector{Float64}} with 5 entries:
  0.251673 => [1.0]
  0.877861 => [1.0]
  0.766795 => [1.0]
  0.814601 => [1.0]
  0.733239 => [1.0]

Note that if you just tried to make Dict(x .=> t) before push!-ing to it, you’d get your error. But after push!-ing, you get this:

julia> Dict(x .=> t)
Dict{Float64, Float64} with 5 entries:
  0.336598  => 1.0
  0.506931  => 1.0
  0.0600524 => 1.0
  0.874898  => 1.0
  0.711234  => 1.0

But look at the types—it isn’t a Dict{Float64, Vector{Float64}} anymore!

To see that this is a special case of singleton containers, here’s one more demo:

push!(t, 2.0);
Dict(x .=> t) # Error: DimensionMismatch: [...]

So the way that broadcasting works here is actually a bit subtle.

EDIT: I’ve tried to be pretty focused on actually answering your question about broadcasting, but to be clear and for what it’s worth, I think @bkamins advice is best and that broadcasting just seems like a bit of a footgun here. That’s not how I’d choose to construct a Dict like this. But then, it’s your code and your use case and stuff, so take that for whatever it’s worth.

Yes, there is nothing special about Ref. Ref is just a container (it is typically used in such cases as it has least impact on BroadcastStyle).

2 Likes

if instead of the push! () function you use setindex!() this problem is avoided or are there potentially other unwanted effects?

d[Date("2022-04-02")]=[1.,2.]
d[Date("2022-04-30")]=[10.,20.]
julia> push!(d[Date("2022-04-30")],30.)
3-element Vector{Float64}:
 10.0
 20.0
 30.0

julia> d
Dict{Date, Vector{Float64}} with 40 entries:
  Date("2022-04-02") => [1.0, 2.0]
  Date("2022-04-30") => [10.0, 20.0, 30.0]
  Date("2022-01-15") => []
...
julia> push!(d[Date("2022-04-23")],30.)
1-element Vector{Float64}:
 30.0

julia> d
Dict{Date, Vector{Float64}} with 40 entries:
  Date("2022-04-02") => [1.0, 2.0]
  Date("2022-04-30") => [10.0, 20.0, 30.0]
  Date("2022-01-15") => [30.0]
  Date("2022-10-01") => [30.0]
  Date("2022-07-02") => [30.0]
....

If vectors are aliases then both setindex! and push! will be problematic.