arr = ('a'..'z').to_a
arr.each { arr << arr.shift }
这样为什么没有死循环啊?
我的理解是,each 的时候,会有一个变量 index , 从 0 开始,每次迭代的时候把 arr[index] 作为参数传递给 block (当然这个例子里面没用到这个参数),并且 index 会加 1。当 arr[index] 为 nil 的时候,代表整个数组 arr 已经迭代完了,就停止了。
那么,你这个例子的重点就在于,arr << arr.shift 这个语句,不会改变 arr 的长度,它的长度始终都是 26. 我觉得 Array#shift 是 in-place 的操作,也就是说,执行 arr.shift 的时候,会返回 arr[0] 作为返回值,但是也会有一个副作用,就是 arr 本身已经被修改了,arr 中的第一个元素已经被删除了,此时再把这个返回的结果加入到 arr 中,实际上只是把之前 arr[0] 这个元素移位到了 arr.last , 而最终的结果是 arr 本身的大小没变。
那么,arr.each 的时候,index 从 0 开始,一路运行到 26 的时候就会停止迭代。
可以试试下面这段代码,看一下迭代的过程:
arr = (1..9).to_a
arr.each do |number|
puts '-' * 30
p arr
puts number
arr << arr.shift
p arr
puts '=' * 30
puts
end
... 这段代码有点丑,然后我也只能说这是我自己的想法,抛砖引玉吧,也不知道对不对。
pry(main)> arr.each { |s| arr << arr.shift; puts s }
a
c
e
g
i
k
m
o
q
s
u
w
y
a
c
e
g
i
k
m
o
q
s
u
w
y
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
自己测试一下就知道了。
因为你 push 有限的次数啊。arr.shift 又不是无限的。 这样看的清楚点。 arr.each { |s| arr << arr.shift;puts arr.to_s; puts s }
实际在循环的是arr
的 copy
源码:
VALUE
rb_ary_each(VALUE array)
{
long i;
volatile VALUE ary = array;
RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length);
for (i=0; i<RARRAY_LEN(ary); i++) {
rb_yield(RARRAY_AREF(ary, i));
}
return ary;
}
是会死循环的,不信你试试 arr = [1,2,3]; arr.each {|i| puts i; arr << rand(100) }
。
你给的例子之所以没有死循环,是因为 arr << arr.shift
相当于只是把 arr 的第一个元素移到了末尾,数组的长度并没有变化。
结合楼上给出的源代码,循环的判断条件是 i<RARRAY_LEN(ary)
,既然数组长度没变,循环次数也就是固定的。
哦哦 我知道是什么原因了 运行下面这个代码:
arr = ('a'..'z').to_a
arr.each{ |i|
p [arr.join.gsub(/#{i}/, " <#{i}> "), i]
arr << arr.shift
}