As mentioned earlier, one of the primary uses of blocks in Ruby is to provide iterators to which a range or list of items can be passed. Many standard classes such as Integer and Array have methods that can supply items over which a block can iterate. For example:
3.times{ |i| puts( i ) } [1,2,3].each{|i| puts(i) }
You can, of course, create your own iterator methods to provide a series of values to a block. In the iterate1.rb program, I have defined a simple timesRepeat
method that executes a block a specified number of times. This is similar to the times
method of the Integer class except it begins at index 1 rather than at index 0 (here the variable i
is displayed in order to demonstrate this):
iterate1.rb
def timesRepeat( aNum ) for i in 1..aNum do yield i end end
Here is an example of how this method might be called:
timesRepeat( 3 ){ |i| puts("[#{i}] hello world") }
This displays the following:
[1] hello world [2] hello world [3] hello world
I’ve also created a timesRepeat2
method to iterate over an array:
def timesRepeat2( aNum, anArray ) anArray.each{ |anitem| yield( anitem ) } end
This could be called in this manner:
timesRepeat2( 3, ["hello","good day","how do you do"] ){ |x| puts(x) }
This displays the following:
hello good day how do you do
Of course, it would be better (truer to the spirit of object orientation) if an object itself contained its own iterator method. I’ve implemented this in the next example. Here I have created MyArray, a subclass of Array:
class MyArray < Array
It is initialized with an array when a new MyArray object is created:
def initialize( anArray ) super( anArray ) end
It relies upon its own each
method (an object refers to itself as self
), which is provided by its ancestor, Array, to iterate over the items in the array, and it uses the times
method of Integer to do this a certain number of times. This is the complete class definition:
iterate2.rb
class MyArray < Array def initialize( anArray ) super( anArray ) end def timesRepeat( aNum ) aNum.times{ # start block 1... | num | self.each{ # start block 2... | anitem | yield( "[#{num}] :: '#{anitem}'" ) } # ...end block 2 } # ...end block 1 end end
Notice that, because I have used two iterators (aNum.times
and self.each
), the timesRepeat
method comprises two nested blocks. This is an example of how you might use this:
numarr = MyArray.new( [1,2,3] ) numarr.timesRepeat( 2 ){ |x| puts(x) }
This would output the following:
[0] :: '1' [0] :: '2' [0] :: '3' [1] :: '1' [1] :: '2' [1] :: '3'
In iterate3.rb, I have set myself the problem of defining an iterator for an array containing an arbitrary number of subarrays, in which each subarray has the same number of items. In other words, it will be like a table or matrix with a fixed number of rows and a fixed number of columns. Here, for example, is a multidimensional array with three “rows” (subarrays) and four “columns” (items):
iterate3.rb
multiarr = [ ['one','two','three','four'], [1, 2, 3, 4 ], [:a, :b, :c, :d ] ]
I’ve tried three alternative versions of this. The first version suffers from the limitation of only working with a predefined number (here 2 at indexes [0] and [1]) of “rows” so it won’t display the symbols in the third row:
multiarr[0].length.times{|i| puts(multiarr[0][i], multiarr[1][i]) }
The second version gets around this limitation by iterating over each element (or “row”) of multiarr
and then iterating along each item in that row by obtaining the row length and using the Integer’s times
method with that value. As a result, it displays the data from all three rows:
multiarr.each{ |arr| multiarr[0].length.times{|i| puts(arr[i]) } }
The third version reverses these operations: The outer block iterates along the length of row 0, and the inner block obtains the item at index i
in each row. Once again, this displays the data from all three rows:
multiarr[0].length.times{|i| multiarr.each{ |arr| puts(arr[i]) } }
However, although versions 2 and 3 work in a similar way, you will find that they iterate through the items in a different order. Version 2 iterates through each complete row one at a time. Version 3 iterates down the items in each column. Run the program to verify that. You could try creating your own subclass of Array and adding iterator methods like this—one method to iterate through the rows in sequence and one to iterate through the columns.