In a for loop, when are the loop parameters calculated?

Here's the real beginner question ...

In a for loop, when are the loop parameters calculated?

Here the loop runs forever, so c is obviously β€œchecked” every time the loop starts:

 c= [1] for i in c push!(c, i) @show c end c = [1,1] c = [1,1,1] c = [1,1,1,1] ... 

But this cycle is evaluated only once:

 c= [1] for i in 1:length(c) push!(c, i) @show c end c = [1,1] 

It seems to evaluate enumerate(c) every cycle:

 c= [1] for (i, _) in enumerate(c) push!(c, i) @show c end c = [1,1] c = [1,1,1] c = [1,1,1,1] ... 

But this loop obviously does not:

 c= [1] for i in eachindex(c) push!(c, i) @show c end c = [1,1] 

And it does:

 c= [1] foreach(a -> (push!(c, a); @show c), c) c = [1,1] c = [1,1,1] c = [1,1,1,1] ... 

As I said, this is a real question for beginners. But am I missing a common template?

+5
source share
1 answer

I believe that the main point is that your various loops reference the Julia iterator interface on two different types of objects:

  • array object c

  • a AbstractUnitRange object (or one of its subtypes)


When you work with for i in c , Julia does not know how big c . All Julia needs is the current iteration state (the index he reached), and the next index to visit in c should be. It can test if it completes an iteration over c if the next index takes it out of bounds.

Copying from Julia iterator documentation, such a cycle essentially boils down to:

 state = start(c) while !done(c, state) (i, state) = next(c, state) # body end 

If you join c in the body of the loop, there will always be the next index to visit (i.e. while !done(c, state) will always be true ). Array c can grow until your memory is full.

Loops using enumerate and foreach equally dependent on the iterator interface to the c array, so you see a similar behavior when c changes during these loops.


On the other hand, loops using for i in 1:length(c) and for i in eachindex(c) do not iterate over c , but different objects that support the iterator interface.

The key point is that these objects are created before the start of the iteration, and they are not affected when c changes in the body of the loop.

In the first case, length(c) is computed, and then 1:length(c) is created as an object of type UnitRange . In your example, this starts at 1 and stops at 1, so we push! to c only once during iteration.

In the second case, calling eachindex([1]) returns a Base.OneTo object; just like a UnitRange object, except for a guaranteed start from 1. In your example, the case Base.OneTo(1) and starts at 1 and stops at 1 as well.

Both of these objects are subtypes of AbstractUnitRange and ultimately subtypes of AbstractArray . The iterator interface allows you to sequentially retrieve the values ​​stored by these objects.

+6
source

Source: https://habr.com/ru/post/1261101/


All Articles