to_enum(:method)如何在这里收到它的块?

时间:2013-10-21 15:47:55

标签: ruby enumerable enumerator

从我发现的一个例子中,这段代码计算了数组中与其索引相等的元素数。但是怎么样?

[4, 1, 2, 0].to_enum(:count).each_with_index{|elem, index| elem == index}

我不可能仅仅通过链接来完成它,链中的评估顺序令人困惑。

我理解的是我们正在使用Enumerable#count的重载,如果给出一个块,则计算产生真值的元素数。我看到each_with_index具有该项是否等于它的索引的逻辑。

我不明白的是,each_with_index如何成为count的阻止参数,或each_with_index为何直接在[4,1,2,0]上调用。如果map_with_index存在,我本可以做到:

[4,1,2,0].map_with_index{ |e,i| e==i ? e : nil}.compact

但请帮助我理解这种基于可枚举的风格 - 它很优雅!

3 个答案:

答案 0 :(得分:5)

让我们从一个更简单的例子开始:

[4, 1, 2, 0].count{|elem| elem == 4}
=> 1

所以这里count方法返回1,因为该块对于数组的一个元素(第一个元素)返回true。

现在让我们来看看你的代码。首先,当我们调用to_enum时,Ruby会创建一个枚举器对象:

[4, 1, 2, 0].to_enum(:count)
=> #<Enumerator: [4, 1, 2, 0]:count>

这里枚举器正在等待使用[4,1,2,0]数组和count方法执行迭代。枚举器就像一个挂起的迭代,等待以后发生。

接下来,在枚举器上调用each_with_index方法,并提供一个块:

...each_with_index{|elem, index| elem == index}

这将调用您在上面创建的枚举器对象上的Enumerator#each_with_index方法。 Enumerator#each_with_index所做的是使用给定的块启动挂起的迭代。但它也将索引值与迭代中的值一起传递给块。由于挂起的迭代设置为使用count方法,因此枚举器将调用Array#count。这将每个元素从数组传递回枚举器,枚举器将它们与索引一起传递到块中。最后,Array#count计算真实值,就像上面更简单的例子一样。

对我来说,理解这一点的关键是你使用的是Enumerator#each_with_index方法。

答案 1 :(得分:2)

答案只需点击一下即可:Enumerator的文档:

  

大多数[Enumerator]方法[但大概也是Kernel#to_enumKernel#enum_for]有两种形式:一种块形式,其中为枚举中的每个项目评估内容,以及非-block表单,它返回一个新的Enumerator包装迭代。

这是第二个适用于此的地方:

enum = [4, 1, 2, 0].to_enum(:count) # => #<Enumerator: [4, 1, 2, 0]:count> 
enum.class # => Enumerator
enum_ewi = enum.each_with_index
  # => #<Enumerator: #<Enumerator: [4, 1, 2, 0]:count>:each_with_index> 
enum_ewi.class #  => Enumerator
enum_ewi.each {|elem, index| elem == index} # => 2

特别注意irb从第三行返回。它继续说,“这允许你将枚举器链接在一起。”并以map.with_index为例。

为何停在这里?

    enum_ewi == enum_ewi.each.each.each # => true
    yet_another = enum_ewi.each_with_index
       # => #<Enumerator: #<Enumerator: #<Enumerator: [4, 1, 2, 0]:count>:each_with_index>:each_with_index>    
    yet_another.each_with_index {|e,i| puts "e = #{e}, i = #{i}"}

    e = [4, 0], i = 0
    e = [1, 1], i = 1
    e = [2, 2], i = 2
    e = [0, 3], i = 3

    yet_another.each_with_index {|e,i| e.first.first == i} # => 2

(编辑1:用与问题相关的文档替换示例。编辑2:添加“为什么要停在这里?”

答案 2 :(得分:0)

很好的回答@Cary ..我不确定该块是如何通过对象链的,但是尽管出现了,该块正在由count方法执行,就像在这个堆栈跟踪中一样,即使它的变量与each_with_index

产生的变量绑定在一起
enum = [4, 1, 2, 0].to_enum(:count)
enum.each_with_index{|e,i| raise "--" if i==3; puts e; e == i}
4
1
2
RuntimeError: --
    from (irb):243:in `block in irb_binding'
    from (irb):243:in `count'
    from (irb):243:in `each_with_index'
    from (irb):243