[Ruby-tut] Doing something... again and again and...
Author |
Message |
wtd
|
Posted: Thu Jul 01, 2004 9:01 pm Post subject: [Ruby-tut] Doing something... again and again and... |
|
|
Loops in Ruby are where things get really wild, and more than a little excting. Programmers have grown accustomed to doing a lot of work to get loops in other languages to behave exactly the way they want, with lots of extra variables introduced to get around inflexible syntax.
Ruby changes that by, for the most part, eschewing traditional looping constructs. Instead the block concept I demonstrated in my previous tutorial, "Method to my madness", allows for endless customization, and this makes for code that's easier to read and understand.
Let's take a simple example. Print out numbers from zero to nine to standard output. First I'll demonstrate in C++, and then in Ruby.
code: | #include <iostream>
int main()
{
for (int i = 0; i <= 9; i++)
{
std::cout << i << std::endl;
}
} |
code: | 0.upto 9 { |i| puts i } |
Even considering only the code for the loop in C++, and the lengthier printing code, the Ruby version is more concise and better demonstrates what's actually going on. Another example of this would be doing the same thing, but in increments.
code: | 0.step(9, 3) { |i| puts i } |
In each of these examples, the methods "upto" and "step" each yield the necessary values to the code block supplied. These are called "iterators", and Ruby is chuck full of them.
Perhaps the most useful iterator, though, is the "each" iterator. But first...
A bit on arrays
Arrays in Ruby are heterogenous collections. They can store any kind of object. As in many other languages, the first index is zero. The syntax to construct them is quite simple.
code: | my_array = ["hello", 42, 3.14159, "world"] |
Arrays are not the only thing that can define the "each" method, but they are probably among the most common in use. The each method simply yields each of the array's elements in order.
code: | my_array.each do |array_element|
puts array_element
end |
This is considerably more straightforward than using the equivalent "for" loop in most other languages. However, in using this method, you lose the index integer. That's easily enough remedied by using an iterator which also yields the index.
code: | my_array.each_with_index do |array_element, index|
puts "#{index}\t#{array_element}"
end |
Now, when I said that Ruby eschews the traditional looping constructs in favor of iterators, I didn't mean that it leaves them out of the language. "while" and it's opposite, "until", are both readily available.
code: | i = 0
while i <= 9
puts i
i += 1
end |
Or perhaps more descriptively:
code: | i = 0
until i > 9
puts i += 1
end |
Then there are the one line versions.
code: | i = 0
while i <= 9 do puts i; i += 1 end |
And just as if and unless can be taced onto the end of an expression, so too can while and until. Here begin...end is a single expression, but it encapsulates multiple expressions.
code: | i = 0
begin puts i; i += 1 end while i <= 9 |
Or:
code: | i = 0
begin puts i; i += 1 end until i > 9 |
These patterns could also be duplicated using Ruby's "loop" keyword, which, without intervention from "break", will continue forever.
code: | i = 0
loop do
break if i > 9
puts i
i += 1
end |
This replaces hacks such as:
As with other looping constructs, do and end can be replaced with curly brackets.
code: | i = 0
loop {
break if i > 9
puts i
i += 1
} |
However, preferred style is to use "do" and "end" whenever the loop spans multiple lines.
Ruby also has "for". It's essentially syntactic sugar for the "each" method. Anything which defines the "each" iterator can be used in a for loop. As an example, File objects have an each iterator, so the following two examples are effectively identical. The file is a function I recently wrote to replace strings in C++ std::string objects.
code: | File.open("str_replace.cpp").each do |line|
puts line
end |
code: | for line in File.open("str_replace.cpp")
puts line
end |
And the Tony version.
code: | for line in File.open("str_replace.cpp") do puts line end |
The only difference between these is that variables introduced in the for...in version remain in place after the loop ends.
code: | for x in [1,2,3]
y = x * 2
puts y
end
puts y |
The above is perfectly valid, since I created the "y" variable in the loop. However, in the following the "y" is local to the block and doesn't exist in the main program.
code: | [1,2,3].each do |x|
y = x * 2
puts y
end
puts y |
|
|
|
|
|
|
Sponsor Sponsor
|
|
|
ericfourfour
|
Posted: Sat Dec 16, 2006 3:27 pm Post subject: (No subject) |
|
|
I can't get the first example to work:
code: | 0.upto 9 { |i| puts i } |
I even copy pasted it directly.
I get this output:
code: | irb(main):008:0> 0.upto 9 { |i| puts i }
SyntaxError: compile error
(irb):8: parse error, unexpected '{', expecting $
0.upto 9 { |i| puts i }
^
from (irb):8 |
|
|
|
|
|
|
wtd
|
Posted: Sat Dec 16, 2006 3:40 pm Post subject: (No subject) |
|
|
That's actually a mistake on my part.
code: | 0.upto(9) { |i| puts i } |
|
|
|
|
|
|
|
|