Ruby each 时候,往自己 push 值,为什么没有死循环?

y1feng200156 · 2016年12月06日 · 最后由 romeo 回复于 2016年12月07日 · 1964 次阅读
arr = ('a'..'z').to_a
arr.each { arr << arr.shift }

这样为什么没有死循环啊?

可能 each 处理的是 arr 的副本

我的理解是,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
}
需要 登录 后方可回复, 如果你还没有账号请 注册新账号