在for循环中,何时评估循环参数?

时间:2016-12-11 09:45:54

标签: loops for-loop julia

这是一个真正的初学者问题......

for循环中,何时评估循环参数?

在这里,循环会永远运行,所以显然c正在检查'每次循环开始时:

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

但是这个循环只评估一次:

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

这个看起来像每个循环评估enumerate(c)

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]
...

但是这个循环显然不是:

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

这样做:

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

正如我所说,这是一个真正的初学者问题。但我错过了一般模式吗?

1 个答案:

答案 0 :(得分:6)

我认为主要的一点是你的各种循环在两种不同类型的对象上调用Julia的迭代器接口:

  • 数组对象c本身

  • AbstractUnitRange个对象(或其子类型之一)

当您使用for i in c循环时,Julia不知道c有多大。所有Julia需要的是迭代的当前状态(它已达到的索引)以及c中要访问的下一个索引应该是什么。如果下一个索引超出范围,它可以测试是否完成迭代c

从Julia的iterator interface文档中复制,这样的循环基本归结为:

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

如果您在循环体中附加c,则始终会有下一个要访问的索引(即while !done(c, state)始终为true)。数组c可以增长,直到你的记忆已满。

使用enumerateforeach的循环都以相同的方式依赖于数组c的迭代器接口,因此在修改c期间会看到类似的行为这些循环。

另一方面,使用for i in 1:length(c)for i in eachindex(c)的循环不会迭代c本身,而是迭代支持迭代器接口的不同对象。

关键是这些对象是在迭代开始之前创建的,并且在修改循环体中的c时不会受到影响。

在第一种情况下,计算length(c),然后将1:length(c)创建为UnitRange类型的对象。在您的示例中,它从1开始并在1处停止,因此我们在迭代期间仅push!c一次。

在第二种情况下,调用eachindex([1])会返回Base.OneTo个对象;就像一个UnitRange对象一样,除了保证从1开始。在你的示例中创建了Base.OneTo(1)并从1开始并在1处停止。

这两个对象都是AbstractUnitRange的子类型,最终是AbstractArray的子类型。迭代器接口允许您按顺序访问这些对象持有的值。