Friday, September 28, 2007

Ruby's yield statement

Ruby provides a yield statement that eases the creation of iterators.

The first thing I noticed was that its behavior is different from the C# yield statement (which I knew from before).

Ruby's yield statement gives control to a user specified block from the method's body. A classic example is the Fibonacci Sequence:


class NumericSequences
def fibo(limit)
i = 1
yield 1
yield 1
a = 1
b = 1
while (i < limit)
t = a
a = a + b
b = t
yield a
i = i+1
end
end
...
end



The fibo method can be used by specifying a block that will be executed each time the control reaches a yield statement. For example:


irb(main):001:0> g = NumericSequences::new
=> #<NumericSequences:0xb7cd703c>
irb(main):002:0> g.fibo(10) {|x| print "Fibonacci number: #{x}\n"}
Fibonacci number: 1
Fibonacci number: 1
Fibonacci number: 2
Fibonacci number: 3
Fibonacci number: 5
Fibonacci number: 8
Fibonacci number: 13
Fibonacci number: 21
Fibonacci number: 34
Fibonacci number: 55
Fibonacci number: 89


The following example shows the sequence of numbers of a specific row of the Pascal's triangle :



def pascal_triangle_row(n)
for k in 0..n
yield(fact(n)/(fact(k)*fact(n-k)))
end
end



This method can be used like this:


irb(main):001:0> g = NumericSequences::new
=> #<NumericSequences:0xb7c9103c>
irb(main):002:0> (0..8).each {|n| g.pascal_triangle_row(n) {|r| print "#{r} "}; print "\n"}
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
=> 0..8


Methods using the yield statement can be combined. For example in order to create the sequence of Pascal's triangle read by rows we can write:


def pascal(limit_row)
i = 0
while (i <= limit_row)
pascal_triangle_row(i){|x| yield(x)}
i = i+1
end
end


And use it the same way:


irb(main):005:0> g.pascal(10) {|x| print "#{x} "}
1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 1 6 15 20 15 6 1 1 7 21 35 35 21 7 1 1 8 28 56 70 56 28 8 1 1 9 36 84 126 126 84 36 9 1 1 10 45 120 210 252 210 120 45 10 1 => nil