Jump to content
oichidan

Tutorials on Iterators

Recommended Posts

Um... Hello.

 

This is my first post. I hope it can be of some help in any way.

 

This time, we'll be discussing about iterators in RGSS3. I noticed there hasn't been plenty of tutorials about this, despite the usefulness of iterators in the ruby language. So here goes. I'll try to share whatever I understood of the concept. Feel free to comment, correct, add some more information, that everyone who reads this topic may find it useful.

 

Tutorial Level: Moderate

 

-----

 

Iterators (Ruby, RGSS3)

Suppose we have a collection of apples in a basket of apples. We want to cut each apple half. What we do is that we take each apple and cut them one by one, manually, right? Well, there are a plenty more of ways to accomplish our goal, but the point is that I stress on the word each. We do something to each thing in a group of things. We cut each apple.

 

That is iteration, in its simplest form. Quoting Wikipedia:

 

In computer programming, an iterator is an object that enables a programmer to traverse a container, particularly lists. Various types of iterators are often provided via a container's interface.

 

It basically enables us to do something to each member of a collection or object. Simply put, we take each item from a group of items and do something to it.

 

Iterations in Ruby

Visual Basic .NET uses the For Each ... Next loop to do their iteration. As for C#, they use the for...in or foreach loop.

For Each Apple in ABasketOfApples
   Cut(Apple, Half)
Next
foreach apple in aBoxOfApples
    cut(apple, half);
next

Ruby, in the other hand, uses a different iteration syntax. If our illustration above is to be written in ruby, it'd go like one of the following:

# 1. Using the do...end block
# Multi-Line Syntax
Apples.each do |apple|
  Cut(apple, half)
end

# Single-Line Syntax
Apples.each do |apple| Cut(apple, half) end

# 2. Using the curly brackets
# Multi-Line Syntax
Apples.each {|apple|
  Cut(apple, half)
end

# Single-Line Syntax
Apples.each {|apple| Cut(apple, half)}

Well, so what's the difference? Before diving further, I'd like to quote something from the RPG Maker VX Ace help file.

 

Iterators are methods used for abstracting control structures (especially loops). If you call a method that is followed by a code segment enclosed by do ... end or by { ... } (called a block), the method can evaluate the block from within. Methods that call these kinds of blocks are called iterators. Block calls from iterators use yield. The value passed to yield is assigned to the variable between the two pipes ( | ).

{ ... } binds more strongly than a do ... end block.

 

To put ease in understanding, understand our goal. We want to do something, to perform some operations to a single item in a collection of items. That's where iterators come to the play. Ruby uses the do...end block or by { ... } (curly brackets). To define what arguments are passed into the iteration block, we use the yield operator.

 

Yet, we are not going to talk about defining iterators, for now. We'll talk about how to do it.

 

Performing an Iteration

Every collection (includes arrays and everything that implements the Enumerable module) has an each method. Call this method, followed by a do...end or curly brackets and define the local variable within pipe brackets. Then evaluate expressions, call commands or methods, basically do some programming stuff inside the block. An example would best illustrate:

# Let's define an array of objects
Numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Call the each method and perform the iteration
# 1. Using the do...end block, multi-line
Numbers.each do |i|
  if i % 2 == 0 then
    puts i + " is an even number!"
  else
    puts i + " is an odd number!"
  end
end

# 2. Using the do...end block, single-line
Numbers.each do |i| puts (i % 2 == 0) ? i + " is an even number!" : i + " is an odd number!"

# 3. Using the curly brackets, multi-line
Numbers.each { |i|
  if i % 2 == 0 then
    puts i + " is an even number!"
  else
    puts i + " is an odd number!"
  end
}

# 4. Using the curly brackets, single-line
Numbers.each { |i| puts (i % 2 == 0) ? i + " is an even number!" : i + "is an odd number!" }

=begin
The output on the above code (in the game output console window) would be:
1 is an odd number!2 is an even number!3 is an odd number!4 is an even number!5 is an odd number!6 is an even number! ... until 10
Why until 10, you ask? Since we define the above array of numbers, from 1 - 10.
Yes, I know. I forgot adding the newline.
=end

=begin
Just in case you're wondering about the percentage sign (%). It is an operator to perform modulus division.
Example is provided for 10 divided by 4.

   _2_____        This is returned by 10 / 4
4 / 11
     8
  --------
     3            This is returned by 10 % 4

So basically it returns the remainder of the division. If a number % 2 == 0, then the number is an even number.
=end

Where can I perform this iteration?

You can do iteration on any object that implements the each method. Later on, if you're defining classes and methods, you can define a method that can be evaluated by iteration blocks by passing the arguments to be iterated using the yield operator.

# These are examples of objects that can be iterated
ABoxOfApples = [Apple1, Apple2, Apple3, Apple4]

ARangeOfNumbers = 1..10    # Remember, since this is a range of numbers, it -is- an array of numbers.

AString = "This is a string."    # Yes, a string -can- be iterated. You can perform something on each
                                 # character of the string.

# These are examples of objects that cannot be iterated
ANumber = 3
ABoolean = false
ANil = nil

Are there other methods I can use to iterate?

Yes. There are actually plenty more methods defined in the ruby standard library, and more in RGSS library. By now I assume that we've grasped the fundamentals of iteration in Ruby. We basically call a method (that encapsulates iteration), use the do...end block or curly brackets and perform actions on each members passed on by the method. each basically passes each member of the array/collection for us to iterate.

 

The following are some scenarios we often encounter in developing an RPG game with RGSS3 (VX Ace). We won't cover some complicated things for now, though.

 

Can I use indexes in iteration instead of items?

Yes. The Array class defines the each_index method. Instead of passing the items for us to iterate (as do the each method), each_index passes the indexes of its members for us to iterate.

# Well, since we now know that do...end and { } basically does the same thing,
# I will only provide one example for one scenario, either using do...end or { }.

str = "This is a string."
str.each_index { |x| # Remember, each member of the array is represented by a variable in pipe brackets. Tired of 'i' already?
  print "Character #" + x.to_s + " is " + str[x] + "\n"
}

=begin
Output of the above code would be:

Character #0 is T
Character #1 is h
Character #2 is i
Character #3 is s
Character #4 is 
Character #5 is i
... and so on until the last character
Character #16 is .
=end

Can I conditionally delete members of an array using iteration?

 

Sure do. Arrays define the delete_if and reject! methods that evaluates an iteration block. Both methods basically deletes a particular member if, after we evaluate it, the evaluation results true. The difference is in their return values, but we won't fuss it for now.

# Objective: Delete all odd numbers in the array of numbers between 1 and 100

# 1. Define the numbers
num = 1 ... 100

# 2. Convert the definition above to an array object.
# Well, we can actually use the each method on the above, since number ranges are instances of the
# Range class, but since we're going to use the delete_if and reject! methods defined in the Array class,
# we'll convert it into an array.
num_a = num.to_a

# 3. Perform the iteration.
# Remember that odd numbers are numbers whose division by 2 remains 1.
# After the iteration, assign the results to a new variable new_num.
new_num = num_a.delete_if { |number|
  if number % 2 == 1 then 
    true
  else
    false
  end
}

=begin
Explanation
The above code evaluates each member of the array of numbers (performing an iteration on them).
Each member is defined into the 'number' local variable. If the number, divided by 2, remains 1
(that is, the modulus division returns 1), then it is an odd number and we return 'true' to signal
the delete_if method that the said 'number' is to be deleted.
Otherwise, we return 'false' to prevent the method from deleting the number.

After performing the iteration, we now have an array in memory of all even numbers from 1 to 100. We
immediately store them into a new variable, new_num.
=end

# Want to simplify the code above?

simplified = num_a.delete_if { |number| number % 2 == 1 }

# Yes. The above one line code basically does the same thing.

What are other methods I can use for iteration?

This will be a long list. I hope you're prepared for more confusion and frustration.

=begin
If you want to search for methods that can be iterated within the game help files, look at the definition.
Here's an excerpt from the Array page.

-
delete_at(pos) 
delete_if {|x| ... } 
reject! {|x| ... } 
-

delete_at doesn't evaluate iterations, while delete_if and reject! evaluates iteration. Notice how the
help file includes an iteration block {|x| ...} to indicate this.
=end

# METHODS THAT CAN EVALUATE ITERATION
# The following methods can be called from all instances of classes that implements the Enumerable
# module. This includes Array, Range, Hash, IO (reading each line of a file), String...
# Basically, if the class has an each function, then the class implements this module.

# =================================================================
# 1. all? {|item| ...}
# This method will return 'true' (boolean) if, after evaluating each of 'item', all of them returns true.

print [1,2,3].all? {|number| number > 0} # this will print "true" on the screen (without quotes).
print [1,2,3].all? {|number| number > 1} # this will print "false" on the screen (without quotes).

=begin
Explanation
On the first line, the conditional check is number > 0. The array of numbers is 1, 2 and 3.
1 > 0 returns true. 2 > 0 returns true. 3 > 0 returns true. All of them returns true, so the all?
function returns true.

On the second line, it will return false since there will be one number that evaluates to false.
Guess what the number is?
=end
# =================================================================

# =================================================================
# 2. any? {|item| ...}
# This method will return 'true' if any of the evaluation performed within the iteration block evaluates
# to 'true'. Otherwise, it will return 'false'.

numbers = [1, 2, 3, 4, 5]

print numbers.any? {|num| num < 2} # prints 'true' on the screen, without the quotes.
print numbers.any? {|num| num < 6} # prints 'false' on the screen.

=begin
Explanation
On the first line of code, we evaluate all members within the 'numbers' array. The condition is num < 2.
1 < 2 is true. 2 < 2 is false. 3 < 2 is false. 4 < 2 is false. 5 < 2 is false.
Since one of them is true, then the any? method returns true.

I guess you can figure out why the second line of code will print 'false' on the screen.
=end
# =================================================================

# =================================================================
# 3. collect {|item| ...}
# Instead of performing something based on evaluation results, collect will gather all the evaluation
# results and return an array of them.

numbers = [1, 2, 3, 4]
results = numbers.collect do |num| num >= 2 end
results.each do |result| print result end

=begin
Explanation
If we perform an iterator by calling 'each' on the numbers array, and we print the local variable,
it will output 1, 2, 3 and 4 on the screen. The collect function evaluates each member of the 'numbers' array.
All of the results are then gathered on a single array and stored to the results variable.
1 >= 2 is true, 2 >= 2 is true, 3 >= 2 is false, 4 >= 2 is false.
So the results array will contain [true, true, false, false].
Guess what will be printed on the screen by the third line?
=end
# =================================================================

# =================================================================
# 4. find {|item| ... }
# This method returns the first occurrence that matches the evaluation within the iteration block.
# If there are no matches, the method returns nil.

numbers = [5, 6, 7, 8]
found = numbers.find {|number| number % 2 == 0 }
print found

=begin
Explanation
The find method evaluates the evaluation inside the iteration block. Our condition is % 2 == 0.
5 % 2 is 1, so it is false. 6 % 2 is 0, so it is true.
When found a member that, after evaluation, the evaluation returns true, the method will stop iterating
through members and return that first finding. In our case, it is 6.
=end
# =================================================================

# =================================================================
# 5. find_all {|item| ...} or select {|item| ... }
# Returns an array of all members who, after evaluated inside the iterator block, the evaluation returns
# true. Basically, if find will return the first match, find_all or select will return -all- matches.
# =================================================================

There are actually more functions, such as sorting members, returning the smallest or largest based on two values, comparing the iteration block for each item and returns the largest or smallest...but we won't cover that for now. It's rather complicated, and it deserves a single topic.

 

So, how was this article? Did you find it useful? Did you find it too complicated? Did you find it helpful? Did you find it confusing?

This topic is open for further discussions. Questions are always welcome, too, and I'll try my best to answer, but please don't expect a fast reply.

 

Thank you! :)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×