Page 1 of 1

Block and Iterators in Ruby

#1 EdwinNameless  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 120
  • View blog
  • Posts: 710
  • Joined: 15-October 09

Posted 14 November 2009 - 10:24 AM

Introduction

Despite having been around for some time, Ruby has gained a major boost thanks to web development, and Rails in particular. And that's a good thing, because of all the languages I have worked with, Ruby is no doubt of the smartest (there are many many ways to achieve the same thing) and simplest ones. The object of this tutorial is to show one of most powerful features of the Ruby programming languages: blocks, and how they can be used in iterators. If you want to have a basic introduction to the Ruby programming language, please read Kingbradley6's tutorial.

Blocks

Blocks are one way of using closures in Ruby. What is a closure ? Wikipedia defines it as a first-class function with free variables that are bound in the lexical environment... It is a bit complicated, so let's clarify this. A closure is a function usually defined in another function and which "captures" the variables local to this enclosing function: the interest of this is that a closure can be executed at a later point with the environment of the enclosing function being preserved. It also means you can pass the closure around as a function parameter, which, as you'll see in the case of Ruby, is pure syntactic sugar! It might still be a bit clear as mud, but after a few examples, you will see closures in action, and understand their value.

So, let's see a few examples of blocks. Here is a very simple one:


  5.times{ puts "Hi!" }



The block starts with an opening curly brace, and finishes with a closing one. Here, the times function calls the block 5 times (funnily enough), and this block gets executed 5 times. It is somewhat similar to the following:

def printHello
  puts "Hi!"
end

for i in 1..5
  printHello
end



Here, we had to define an extra function, and make sure it is called 5 times. With the block, we just passed it as a parameter of the iterator times.

Iterator. That's it, I have used the word. Here, times iterate through integers between 1 and 5, and for each of these integers, called the block { puts "Hi!" }. And that's plainly what iterators are for: they iterate through a list of objects, characters in a string, elements in an array, values in a range, and for each of these objects, it performs an action. We will see more of them later.

Blocks can be written in 2 ways. We have already seen the first one, using the curly braces. It can also be written using the do ... end syntax:

  5.times do
	puts "Hi!"
  end




Iterators

Now that we've had an overview of how blocks are written, we will look further into iterators. Let's take an array:

greetings = [ "Hello", "Bonjour", "Hallo", "Hej" ]



To print its content, we will use a block as seen before. But how can I get "catch" the value of the current element in the iteration and print it? Here is how:

greetings.each{ |s| puts s }



The value between |...| is the value of the current element in the iteration. This value is "captured" by the closure, and can then be used within it -- here for printing it. Another way of doing this would have been:

def printGreeting(greeting)
  puts greeting
end

for i in 0..greetings.size-1
  printGreeting(greetings[i])
end



Which is obviously much longer. You also have to think of the indexes, and where to start and where to finish.

Here is another example; imagine you want to convert all the elements of the array to uppercase. To do this, you have to iterate through the values in the array, and for each element, call upcase. Let's see how this is done:

puts [ "ruby", "ada", "python", "haskell", "java" ].each{ |l| l.upcase! }



There is a lot going on in this one-liner. First, we define the array holding the languages. As everything in ruby is an object, we can call a method directly on this array, and we call each which, we saw, iterate through the elements of the array. We then pass a block to this iterator which takes each element, puts it into l (that's how we called it in |...|, but you can choose whatever name you want, as long as you use the same in the body of the block), and callb upcase! on it. puts prints out the uppercased elements.

Ruby provides lots of different iterators which really makes life easy. For example, map! replaces the elements in the array with the values returned by the block. Therefore, our previous example would become:

languages = [ "ruby", "ada", "python", "haskell", "java" ]
languages.map!{ |l| l.upcase }
puts languages



returns

RUBY
ADA
PYTHON
HASKELL
JAVA



The content of the array has changed! Another cool one is inject which can be used as follows:

puts (1..10).inject(0){ |sum, i| sum += i}



which returns 55, the sum all integers between 1 and 10. In this iterator, we pass a parameter, 0, which is the initial value of the first parameter of the block, sum. So when the iteration starts, sum is equal to 0. Each element is then stored in i, and the block adds the value of i to the current value of sum. Similarly, 10! can be written simply:

puts (1..10).inject(1){ |fact, i| fact *= i }



returning 3628800, the factorial of 10.

To retrieve the even numbers below 20, you could use select, which filters values:

puts (1..10).select{ |i| i%2 == 0 }



Another interesting feature of iterators is that they can be chained. Therefore, if you want to know the sum of all the even numbers below 10, you can write:

puts (1..10).select{ |i| i%2 == 0 }.inject(0){ |sum, n| sum+= n }
# returns 30





Other Iterators?

So far, we have seen iterators for arrays, but iterators can be found in lots of different types in Ruby. For example, strings have their own iterators.

a_long_string =<<LONG_TEXT
line 1
line 2
line 3
LONG_TEXT
a_long_string.each{ |l| puts l.reverse }



The each method breaks up the string into separata lines, and each item of the iteration is a line. Here, the block just prints the line in reverse. To iterate through characters within a string, use each_char:

"edwinnameless".each_char{ |c| print c.upcase }



prints all the characters in the string in uppercase. Once again, you can chain the iterators, and if you only want the vowels in a string:

vowels = ["a", "e", "i", "o", "u", "y"]
puts "edwinnameless".each_char.select{ |n| (vowels.index(n) != nil) }.join(",")
# prints e,i,a,e,e



join here is an iterator that concatenates the elements in an array, separating them with ",".

Solving a problem like Problem 16 of Project Euler becomes a bit of joke:

# Print the sum of all figures in 2^1000.
puts (2**1000).to_s.each_char.inject(0) { |sum,i| sum = sum + i.to_i }
# prints 1366




yield

We have seen that the blocks were using the |...| notation to define the parameter used in the block. But how is the iterator defined to pass this variable to the block? In other words, if you were to define your own iterator, how would you make sure blocks can retrieve a variable you are passing. This requires the yield statement.

Let's define an iterator:

def reversor(array)
  for i in 0..array.size-1
	yield array.at(i).reverse
  end
end
reversor([ "Hello", "Bonjour", "Hallo", "Hej" ]){ |i| puts i  }
#prints 
# olleH
# ruojnoB
# ollaH
# jeH



This iterator takes the elements of an array, and prints them reversed. The statement yield calls the block, passing a parameter to it: array.at(i).reverse (the ith item of the array, reversed). The block just prints the parameter.

A classic illustration of how yield works is by calling it twice:

def bis_repetita
  yield
  yield
end

bis_repetita{ puts "En gång till!" }
#prints
# En gång till!
# En gång till!



Conclusion

This tutorial focused mainly on blocks, but closures can also be written as Procs and lambdas in Ruby. We have seen how they come handy when working with iterators and after a bit of use, there is no doubt that iterators are probably amongst the coolest things in Ruby, but there also tons of other things that you really miss when switching to another language.

This post has been edited by EdwinNameless: 18 November 2009 - 09:55 AM


Is This A Good Question/Topic? 1
  • +

Page 1 of 1