kekert
kekert

Reputation: 966

strange behaviour of Set of Matrices in Julia

having a set of matrices, when looping through them and modifying them, I'm unable to delete them from the set:

julia> grids = Set([[1 2;3 4],[3 4;5 6]])
Set{Matrix{Int64}} with 2 elements:
  [1 2; 3 4]
  [3 4; 5 6]

julia> for g in grids
           g[g .== 3] .= 0
           println(g)
           println(grids)
           delete!(grids,g)
       end
[1 2; 0 4]
Set([[1 2; 0 4], [3 4; 5 6]])
[0 4; 5 6]
Set([[1 2; 0 4], [0 4; 5 6]])

julia> println(grids)
Set([[1 2; 0 4], [0 4; 5 6]])

however manual delete works:

julia> grids = Set([[1 2;3 4],[3 4;5 6]])
Set{Matrix{Int64}} with 2 elements:
  [1 2; 3 4]
  [3 4; 5 6]

julia> delete!(grids, [1 2;3 4])
Set{Matrix{Int64}} with 1 element:
  [3 4; 5 6]

What am I missing please? I assumed the g in the loop behaves like a pointer to certain matrix, but it doesn't. Clearly, I am missing some Julia concept here...

Upvotes: 3

Views: 86

Answers (1)

Bogumił Kamiński
Bogumił Kamiński

Reputation: 69869

You most likely want to use IdDict not Set in this case:

julia> grids = IdDict([[1 2;3 4],[3 4;5 6]] .=> nothing)
IdDict{Matrix{Int64}, Nothing} with 2 entries:
  [1 2; 3 4] => nothing
  [3 4; 5 6] => nothing

julia> for g in keys(grids)
                  g[g .== 3] .= 0
                  println(g)
                  println(grids)
                  delete!(grids,g)
              end
[1 2; 0 4]
IdDict([1 2; 0 4] => nothing, [3 4; 5 6] => nothing)
[0 4; 5 6]
IdDict([0 4; 5 6] => nothing)

julia> grids
IdDict{Matrix{Int64}, Nothing}()

The difference is that IdDict makes lookup on matrix identity, while Set makes lookup on matrix contents.

Note though that IdDict can store two matrices having identical contents as separate entries if they are not stored in the same memory location (as tested with ===):

julia> IdDict([1;;] => nothing, [1;;] => nothing)
IdDict{Matrix{Int64}, Nothing} with 2 entries:
  [1;;] => nothing
  [1;;] => nothing

so you need to choose what kind of behavior you want.

Alternatively you should delete the element from a Set before mutating it:

julia> grids = Set([[1 2;3 4],[3 4;5 6]])
Set{Matrix{Int64}} with 2 elements:
  [1 2; 3 4]
  [3 4; 5 6]

julia> for g in grids
                  println(grids)
                  delete!(grids,g)
                  g[g .== 3] .= 0
                  println(g)
              end
Set([[1 2; 3 4], [3 4; 5 6]])
[1 2; 0 4]
Set([[3 4; 5 6]])
[0 4; 5 6]

julia> grids
Set{Matrix{Int64}}()

To expand on the problem more see:

julia> a = [1]
1-element Vector{Int64}:
 1

julia> x = Set([a])
Set{Vector{Int64}} with 1 element:
  [1]

julia> a in x
true

julia> a[1] = 2
2

julia> x
Set{Vector{Int64}} with 1 element:
  [2]

julia> a in x
false

So although x shows that it stores a the lookup of a fails, because Set performs the lookup based on hash value of a computed at the time a was stored in x and if you change the contents of a its hash changes.

In short:

  1. You can store mutable values in Set, but then do not mutate them for lookup;
  2. Alternatively you can use IdDict that does lookup using object identity not object value.

Upvotes: 3

Related Questions