Blocks as Iterators

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.47.166