【问题标题】:Ruby enumerator chainingRuby 枚举器链接
【发布时间】:2018-01-10 05:16:20
【问题描述】:

在这个例子中,

[1, 2, 3].each_with_index.map{|i, j| i * j}
# => [0, 2, 6]

我的理解是,由于each_with_index 枚举器被链接到mapmap 的行为类似于each_with_index,通过在块内传递一个索引,并返回一个新数组。

为此,

[1, 2, 3].map.each_with_index{|i, j| i * j}
# => [0, 2, 6] 

我不知道该如何解释。

在这个例子中,

[1, 2, 3, 4].map.find {|i| i == 2}
# => 2

我期望输出为[2],假设map 链接到find,并且map 将返回一个新数组。

另外,我看到了这个:

[1, 2, 3, 4].find.each_with_object([]){|i, j| j.push(i)}
# => [1]

[1, 2, 3, 4].each_with_object([]).find{|i, j| i == 3}
# => [3, []]

您能告诉我如何解释和理解 Ruby 中的枚举数链吗?

【问题讨论】:

    标签: ruby list enumerator


    【解决方案1】:

    您可能会发现分解这些表达式并使用 IRB 或 PRY 来查看 Ruby 正在做什么很有用。让我们开始吧:

    [1,2,3].each_with_index.map { |i,j| i*j }
    

    enum1 = [1,2,3].each_with_index
      #=> #<Enumerator: [1, 2, 3]:each_with_index>
    

    我们可以使用Enumerable#to_a(或Enumerable#entries)将enum1 转换为一个数组,以查看它将传递给下一个枚举器(如果有的话,则传递给一个块):

    enum1.to_a
      #=> [[1, 0], [2, 1], [3, 2]]
    

    这并不奇怪。但是enum1 没有块。相反,我们将方法发送给它Enumerable#map:

    enum2 = enum1.map
      #=> #<Enumerator: #<Enumerator: [1, 2, 3]:each_with_index>:map>
    

    您可能会将其视为一种“复合”枚举器。这个枚举器确实有一个块,因此将其转换为数组将确认它将与enum1 相同的元素传递到块中:

    enum2.to_a
      #=> [[1, 0], [2, 1], [3, 2]]
    

    我们看到数组[1,0]enum2 传入块的第一个元素。 “消歧”应用于此数组以分配块变量值:

    i => 1
    j => 0
    

    也就是说,Ruby 正在设置:

    i,j = [1,0]
    

    我们现在可以通过向 each 发送带有块的方法来调用 enum2

    enum2.each { |i,j| i*j }
      #=> [0, 2, 6]
    

    接下来考虑:

    [1,2,3].map.each_with_index { |i,j| i*j }
    

    我们有:

    enum3 = [1,2,3].map
      #=> #<Enumerator: [1, 2, 3]:map>
    enum3.to_a
      #=> [1, 2, 3]
    enum4 = enum3.each_with_index
      #=> #<Enumerator: #<Enumerator: [1, 2, 3]:map>:each_with_index>
    enum4.to_a
      #=> [[1, 0], [2, 1], [3, 2]]
    enum4.each { |i,j| i*j }
      #=> [0, 2, 6]
    

    由于 enum2enum4 将相同的元素传递到块中,我们看到这只是做同一件事的两种方式。

    这是第三个等效链:

    [1,2,3].map.with_index { |i,j| i*j }
    

    我们有:

    enum3 = [1,2,3].map
      #=> #<Enumerator: [1, 2, 3]:map>
    enum3.to_a
      #=> [1, 2, 3]
    enum5 = enum3.with_index
      #=> #<Enumerator: #<Enumerator: [1, 2, 3]:map>:with_index>
    enum5.to_a
      #=> [[1, 0], [2, 1], [3, 2]]
    enum5.each { |i,j| i*j }
      #=> [0, 2, 6]
    

    为了更进一步,假设我们有:

    [1,2,3].select.with_index.with_object({}) { |(i,j),h| ... }
    

    我们有:

    enum6 = [1,2,3].select
      #=> #<Enumerator: [1, 2, 3]:select>
    enum6.to_a
      #=> [1, 2, 3]
    enum7 = enum6.with_index
      #=> #<Enumerator: #<Enumerator: [1, 2, 3]:select>:with_index>
    enum7.to_a
      #=> [[1, 0], [2, 1], [3, 2]]
    enum8 = enum7.with_object({})
      #=> #<Enumerator: #<Enumerator: #<Enumerator: [1, 2, 3]:
      #     select>:with_index>:with_object({})>
    enum8.to_a
      #=> [[[1, 0], {}], [[2, 1], {}], [[3, 2], {}]]
    

    enum8 传入块的第一个元素是数组:

    (i,j),h = [[1, 0], {}]
    

    然后应用消歧来为块变量赋值:

    i => 1
    j => 0
    h => {}
    

    请注意,enum8 显示在enum8.to_a 的三个元素中的每一个都传递了一个空哈希,但这当然只是因为 Ruby 不知道传入第一个元素后哈希会是什么样子。

    【讨论】:

    • 提出的很好
    【解决方案2】:

    您提到的方法是在 Enumerable 对象上定义的。这些方法的行为会根据您是否传递块而有所不同。

    • 当您不传递块时,它们通常会返回 Enumerator 对象,您可以将更多方法链接到该对象,例如 each_with_indexwith_indexmap 等。
    • 当您将块传递给这些方法时,它们会返回不同种类的对象,具体取决于该特定方法的意义。
      • 对于像find 这样的方法,它的目的是找到第一个满足条件的对象,将它包装在一个数组中并没有什么特别的意义,因此它只返回那个对象。
      • 对于像selectreject这样的方法,它们的目的是返回所有相关的对象,因此它们不能返回单个对象,并且必须将它们包装在一个数组中(即使相关对象恰好是一个单个对象)。

    【讨论】:

    • 我明白了这一点,但是你如何解释我上面所说的链 [1,2,3].each_with_index.map { |i,j| i*j }[1,2,3].map.each_with_index { |i,j| i*j } 返回完全相同的结果,我当时应该相信什么
    猜你喜欢
    • 1970-01-01
    • 2012-11-01
    • 1970-01-01
    • 2012-11-07
    • 2016-12-24
    • 2012-01-13
    • 2017-10-07
    • 2013-01-07
    • 2012-05-04
    相关资源
    最近更新 更多