块作为迭代器

如前所述,Rub y中块的主要用途之一是提供可以传递范围或项列表的迭代器。许多标准类(如 Integer 和 Array)都有方法可以提供块来迭代元素。例如:

  1. 3.times{ |i| puts( i ) }
  2. [1,2,3].each{|i| puts(i) }

当然,你可以创建自己的迭代器方法,以便为块提供一系列值。在 iterate1.rb 程序中,我定义了一个简单的 timesRepeat 方法以执行指定次数的块代码。这类似 Integer 类的 times 方法,除了它从索引 1 而不是索引 0 开始的事实(这里显示变量 i 是正为了证明这一事实):

iterate1.rb
  1. def timesRepeat( aNum )
  2. for i in 1..aNum do
  3. yield i
  4. end
  5. end

以下是如何调用此方法的示例:

  1. timesRepeat( 3 ){ |i| puts("[#{i}] hello world") }

我还创建了一个 timesRepeat2 方法来迭代数组:

  1. def timesRepeat2( aNum, anArray )
  2. anArray.each{ |anitem|
  3. yield( anitem )
  4. }
  5. end

这可以通过以下方式调用:

  1. timesRepeat2( 3, ["hello","good day","how do you do"] ){ |x| puts(x) }

事实上,如果一个对象本身包含它自己的迭代器方法,那么它将更好(面向对象的灵魂更真实)。我在下一个示例中实现了这一点。在这里,我创建了 MyArray,作为 Array 的子类:

iterate2.rb
  1. class MyArray < Array

在创建新的 MyArray 对象时,它使用数组初始化:

  1. def initialize( anArray )
  2. super( anArray )
  3. end

它向上依赖于它自己的(selfeach 方法,它由它的祖先 Array 提供,以迭代数组中的所有元素,并使用 Integer 的 times 方法执行此操作一定次数。这是完整的类定义:

  1. class MyArray < Array
  2. def initialize( anArray )
  3. super( anArray )
  4. end
  5. def timesRepeat( aNum )
  6. aNum.times{ # start block 1...
  7. | num |
  8. self.each{ # start block 2...
  9. | anitem |
  10. yield( "[#{num}] :: '#{anitem}'" )
  11. } # ...end block 2
  12. } # ...end block 1
  13. end
  14. end

请注意,由于我使用了两个迭代器(aNum.timesself.each),因此 timesRepeat 方法包含两个嵌套块。这是一个如何使用它的示例…

  1. numarr = MyArray.new( [1,2,3] )
  2. numarr.timesRepeat( 2 ){ |x| puts(x) }

这将输出以下内容:

  1. [0] :: '1'
  2. [0] :: '2'
  3. [0] :: '3'
  4. [1] :: '1'
  5. [1] :: '2'
  6. [1] :: '3'

iterate3.rb 中,我自己设置了为包含任意数量的子数组的数组定义迭代器的问题,其中每个子数组具有相同数量的项。换句话说,它像一个具有固定行数和固定列数的表或矩阵。例如,这里是一个具有三个“行”(rows,子数组)和四个“列”(columns,元素)的多维数组:

iterate3.rb
  1. multiarr =
  2. [ ['one','two','three','four'],
  3. [1, 2, 3, 4 ],
  4. [:a, :b, :c, :d ]
  5. ]

我已经尝试了三个替代版本。第一个版本受到限制,只能使用在预定义数量(这里是索引 [0] 和 [1])的”行数”(rows)中:

  1. multiarr[0].length.times{|i|
  2. puts(multiarr[0][i], multiarr[1][i])
  3. }

第二个版本通过迭代 multiarr 的每个元素(或’行’,row)然后通过获取行长度并使用 Integer 的 times 方法和该值迭代该行中的每个元素来绕过此限制:

  1. multiarr.each{ |arr|
  2. multiarr[0].length.times{|i|
  3. puts(arr[i])
  4. }
  5. }

第三个版本反转这些操作:外部块沿着行 0 的长度迭代,内部块获得每行中索引 i 的元素:

  1. multiarr[0].length.times{|i|
  2. multiarr.each{ |arr|
  3. puts(arr[i])
  4. }
  5. }

虽然版本 2 和版本 3 以类似的方式工作,但你会发现它们以不同的顺序迭代这些元素项目。运行该程序以验证。你可以尝试创建自己的 Array 子类并添加像这样的迭代器方法 - 一个按顺序迭代行的方法(如上面的版本 2)和一个按顺序遍历列的方法(如版本 3)。