keys() potential bug?

Running the following code:

s = Dict(1=>‘a’, 2=>‘b’)
for key in keys(s)
println(key)
s[key+1] = ‘x’
end

generates the result:
2
3
1

Is this a bug? I expect the result to contain only keys 1 and 2. If I replace keys(s) with collect(keys(s)), I get the correct result.

I have:
Julia Version 1.0.0
Commit 5d4eaca0c9 (2018-08-08 20:58 UTC)
Platform Info:
OS: Linux (x86_64-pc-linux-gnu)
CPU: Intel® Core™ i5-8250U CPU @ 1.60GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-6.0.0 (ORCJIT, skylake)

Thanks!

I would say that this is not a bug.
AFAICT keys is a lazy iterator.
That means that for key in keys(s) does not compute a list of all valid keys and then
executes the loop but rather computes the next key(s) on the fly.
Since you keep adding elements to your dictionary, you end up with more unintended iterations.

Another example:

x = [0]
for el in x
    push!(x, el+1)
    println(el)
end

This gives you an infinite loop. In every iteration a new element has been added.

In general I would say that it is a bad idea to modify the iterator inside the for loop.
Your collect(keys(s)) is better.

1 Like

The line s[key+1] = 'x' is extending the dictionary as you go through the loop.

to see this better


julia> dict = Dict(1 => "1", 2 => "2")
Dict{Int64,String} with 2 entries:
  2 => "2"
  1 => "1"

julia> for k in keys(dict)
           println( keys(dict) )
           dict[k+1] = string(k+1)
           println("dict keys: $(keys(dict))")
       end
[2, 1]
dict keys: [2, 3, 1]
[2, 3, 1]
dict keys: [4, 2, 3, 1]
[4, 2, 3, 1]
dict keys: [4, 2, 3, 1]
2 Likes

Mm… It is counter-intuitive, isn’t it? Are we asking users to play computers, to be operational? What is the semantic definition of keys()? I am thinking from the point of view of language design.

Is it possible to design the iterators not to include the new keys? If not, maybe the documentation can help by emphasize the operational nature of keys() in the dictionary section and for iterators in general.

Thanks.

I don’t think so. If you modify an iterator within the loop, all sorts of strange things can happen. This applies to all languages.

If you don’t want this, then collect(keys(..)) should be used to generate a non-lazy iterator:

julia> for key in collect(keys(s))
       println(key)
       s[key+1] = 'x'
       end
2
1

julia> s
Dict{Int64,Char} with 3 entries:
  2 => 'x'
  3 => 'x'
  1 => 'a'

Here is another example:

s = Dict(1=>‘a’)
for key in keys(s)
println(key)
s[key+1] = ‘x’
end

This time only the key 1 is printed. I have to agree with the statement “If you modify an iterator within the loop, all sorts of strange things can happen.” :wink:

1 Like