Ruby: Loops and Iterators

Loops are structures in Ruby which allow you to easily repeat a section of code a number of times. This functionality can always be accomplished through simply writing repetitive lines of code, however compacting everything into one loop makes changing a small feature about each repeated line very easy, and it abides to the core "Don't Repeat Yourself" principle of programming.

While and Until

The first two loops which we're going to learn about are the "while" and "until" loops. These repeat a section of code while a condition is true, or until a condition is true (as appropriate). They are created by writing the while or until keywords, the condition, the do keyword or a new line to separate the condition and the next section, the code to repeat, and then the end keyword to end the structure. Essentially they work much like 'if' statements, we have the main keyword, a condition, something to separate the condition and the rest of the code, the code we want to be a part of this structure, and then the end keyword to end the structure.

The "while" and "until" loops are essentially just inverted versions of each other. "While" repeats the code while the condition is true, and "until" loops while the condition is false (in other words, until the condition is true). The most basic "while" and "until" loops which just loop infinitely as they are given straight-up boolean conditions can be seen as follows:

1
2
3
4
5
6
7
8
while true #Infinite 'while' loop
	puts "Hello"
end
#This point in the script, after the loop, will never actually be reached

until false #Infinite 'until' loop
	puts "Hello"
end

In the case of the above infinite loops, we can break out of the loop in a certain situation by using the break keyword (often used in conjunction with 'if' statements to break out of infinite loops in certain conditions) - however it's much better practise in most situations to make use of the actual while conditions. While loops in general are intended to be used with true or false conditions, such as variables or method calls. We can, however, also use them to iterate (step) through a sequence of numbers by counting each repeat using a variable and then using this variable in the loop's condition. Take the following, for example, in which the code inside the loop is executed five times, counting from 1 to 5:

1
2
3
4
5
6
i = 1 #Variable for iteration called 'i' which starts at 1

until i > 5 #Until we've gone over 5
	puts i #Output the loop count
	i = i + 1 #Add one to the loop count
end #End the loop

Both keywords, while and until, can also be used as modifiers. Modifiers in Ruby mean that whatever expression the modifier is applied to will only be evaluated by the Ruby interpreter if the modifier says so. Since everything in Ruby is an expression, which means everything in Ruby has a value of some kind, modifiers can be used on pretty much everything in Ruby! The modifiers for "while" and "until" are simply represented by tagging their keywords plus a condition to an expression - take, for example, the following one line loop:

1
puts "Hello" while true #Works the same as: puts "Hello" until false

Similarly, there are also if and unless modifiers that can apply basic conditional functionality (as seen in 'if' statements) to expressions. We haven't actually talked about "unless" before, but essentially just like "until" is an invert of "while", "unless" is an invert of "if". Take, for example, the following (in which unless is used as a modifier):

1
2
3
puts "Enter your name below..."
name = gets.chomp
puts "Hello #{name}!" unless name == "Joe" #Don't greet users named "Joe"

Moving from this, these modifiers can also be applied to blocks. Blocks, in Ruby, are simply sections of grouped code which are treated as a single expression or value. This means that they can be passed to supporting methods, or in this case, can be used with modifiers.

Blocks are created in Ruby by either using curly brackets, or by using the do and end keywords. There is much debate about which form should be used where, but for most purposes it doesn't really matter. Our 'until' loop which counted from one to five earlier could be re-created using a block and the while modifier (just to mix things up in this case), as follows:

1
2
3
4
5
6
i = 1

do
	puts i
	i = i + 1
end while i <= 5

In the above case, using the modifier in this sense is simply another way to solve the "problem" of easily counting to five. This is a classic good use-case of loops as the numbers one to five could easily be outputted by simply using five puts calls, however with this method the number of times to loop can be easily changed, and certain mathematical operators or other things could be applied to every number the loop counts through.

For

The "for" loop in Ruby is a little bit different to the "while" and "until" loops. If you've programmed in any other languages before, the "for" loop in Ruby is also a bit different to the "for" loop in most other programming languages. The "for" loop is essentially for looping over the values in an array or hash. It's written via the for keyword, followed by a variable name (or multiple, we'll come back to this), followed by the in keyword, followed by an array or hash to loop through, followed by the do keyword or a new line, followed by the code to repeat, followed by the end keyword.

This probably sounds a little bit confusing, but essentially the variable name written before the in keyword will be used to store each entry in the array or hash specified after the in keyword, and this can be utilized inside the loop. Take, for example, the following, which outputs all the elements in the array:

1
2
3
4
5
employees = ['Joe', 'Charlie', 'Fred', 'Reeve']

for employee in employees
	puts employee
end

In this case "employee" is just the name we've given to a variable which is going to hold the relevant employee at each repeat of the loop - the loop will end up looping as many times as there are elements in the array.

If this type of functionality is used with a hash, it will loop through all the keys and all the values. This often isn't the intended functionality, and as such, two variables can be specified (separated by a comma) to the loop so that the keys and values can be treated separately. This functionality is demonstrated in the following code snippet:

1
2
3
4
5
mario = {:name => 'Mario', :job => 'plumber', :age => 50}

for key, value in mario
	puts "#{key}: #{value}"
end

The "for" loop can also be used to easily iterate through simple number sequences, just as we did with while and until earlier. The first variable in the loop structure will hold the number at each "step" of the loop, and for the "array" we can specify a pattern by using .. - for example 1..5 specifies the numbers one to five. This makes looping a certain number of times much easier than using "while" or "until", as can be seen in the following code snippet:

1
2
3
for i in 1..5
	puts i
end

Times and Each

The last two (or three) "loops" we're going to cover in this tutorial (we're not covering all of the possible methods of repeating code, there isn't enough time in the day!) aren't actually "loop structures" per-se, but are actually just methods which take blocks (as discussed earlier, sections of code denoted by curly brackets or "do/end"). These are called iterators.

The first method we're going to talk about, times, can be performed on any number object and simply loops the given block that number of times. The following, for example, outputs "Hi" five times:

1
5.times { puts "Hi" } #using do/end instead of {} would also work fine here

Sometimes blocks also have values available for them to use, and when this is the case they can be assigned to variables through the use of a comma separated list of variables surrounded in pipe symbols at the beginning of the block. In this case, the times method actually lets us see the value of the variable it's using to count each loop step, and so we can assign this to a variable and make use of it:

1
2
3
5.times do |i|
	puts i
end

The above will count from zero to four as the variable which counts the loop steps, which we're putting in 'i', in this case starts at zero and increases by one each time.

The final two methods we're going to talk about are each and each_index. Using each on an array or hash is essentially identical to using a for loop - the block specified repeats the same number of times as elements or keys and values in the array or hash, and the values for the array elements or hash key/values can be utilized as they are passed to the block. For example:

1
2
3
4
5
mario = {:name => 'Mario', :job => 'plumber', :age => 50}

mario.each do |key, value|
	puts "#{key}: #{value}"
end

Using this over for is literally just a matter of syntax and what you prefer to write, but that's one of the reasons that we all love Ruby - it's extremely flexible and there are a bunch of different ways to accomplish the same goal.

Finally the last method we're going to talk about, each_index, loops the same number of times as there are elements in an array, but instead of providing the values of each element directly, it provides the various index numbers. This can be useful for performance in situations where the value of each element shouldn't always be used, and something like array[i] can get the value at the given index if/when it does need to be used. In this case, we can just utilize each_index to count up the number of elements in an array:

1
2
3
4
5
employees = ['Joe', 'Charlie', 'Fred', 'Reeve']

employees.each_index do |i|
	puts i + 1 #Output 1 to 4
end