Error: Assigning a set to multiple positions of a vector of sets

Hi,
I recently started working with Julia and I ran into an error that I do not understand.
I have a vector of sets of which one set contains one element. I now have candidate elements that I would like to add to the empty sets in the vector but only those elements that are not contained in any other sets should be added.

setlist = [Set() for _ = 1:3]

setlist[1] = Set('a')
potential_add = Set(['a', 'b'])

## This does not work
setlist[[2, 3]] .= setdiff!(potential_add, setlist...)


## For one position it works
setlist[2] = setdiff!(potential_add, setlist...)

## This produces the desired result 
union!.(setlist[[2, 3]], setdiff!(potential_add, setlist...))

Trying to assign the value to multiple positions at once yields the following error

ERROR: LoadError: MethodError: Cannot `convert` an object of type Char to an object of type Set{Any}
Closest candidates are:
  convert(::Type{T}, ::T) where T<:AbstractSet at set.jl:422  
  convert(::Type{T}, ::AbstractSet) where T<:AbstractSet at set.jl:423
  convert(::Type{T}, ::T) where T at essentials.jl:205        
  ...
Stacktrace:
 [1] setindex!(A::Vector{Set{Any}}, x::Char, i1::Int64)       
   @ Base .\array.jl:839
 [2] setindex!
   @ .\subarray.jl:317 [inlined]
 [3] macro expansion
   @ .\broadcast.jl:984 [inlined]
 [4] macro expansion
   @ .\simdloop.jl:77 [inlined]
 [5] copyto!
   @ .\broadcast.jl:983 [inlined]
 [6] copyto!
   @ .\broadcast.jl:936 [inlined]
 [7] materialize!
   @ .\broadcast.jl:894 [inlined]
 [8] materialize!(dest::SubArray{Set{Any}, 1, Vector{Set{Any}}, Tuple{Vector{Int64}}, false}, bc::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1}, Nothing, typeof(identity), Tuple{Vector{Char}}})
   @ Base.Broadcast .\broadcast.jl:891
 [9] top-level scope

Desired result

3-element Vector{Set{Any}}:
 Set(['a'])
 Set(['b'])
 Set(['b'])

Why is it not possible to assign the set to multiple positions in the vector? Thank you for your thoughts!

On closer inspection I noticed that the solution with the union only works with sets of size one.

When we write

a .= b

we invoke broadcasting. If b is a scaler (e.g. 1 or 'c') then index of a is set to b. If b is itself a collection, then each element of a is set to the corresponding element of b (e.g. a .= [1,2,3]). In your example, b is a Set, so it is trying to set each element of setlist to the corresponding element of your setdiff!. To avoid this, use

setlist[[2, 3]] .= [setdiff!(potential_add, setlist...)]

However! this may not be what you are looking for because this will make setlist[2] and setlist[3] be the same reference in memory, so a subsequent call to push!(setlist[2], 'd') will also impact setlist[3].

The union! operation avoids this reference unification, but also uses broadcasting and so should be written

union!.(setlist[[2, 3]], [setdiff!(potential_add, setlist...)])
1 Like

Thank you very much for this quick and detailed response!

Very interesting point regarding the reference to memory, that would have potentially caused me a headache later on - thank you for pointing that out!

1 Like