You have already used the for loop. Here we are going to look at it in a bit more detail and figure out a few other ways in which we can use it.

A simple counter

We want to count from 1 to 10. The following will do just that.

1
2
3
4
5
6
7
8
9
package main

import "core:fmt"

main :: proc() {
    for i in 1..=10 {
        fmt.println(i)
    }
}

The 1..=10 is called a range and it means from 1 to 10, inclusive. If we wanted to exclude 10, we would use 1..<10 instead. Modify the program above to verify that actually is the case.

What if we wanted to count down from 10 to 1. Instead. Well, let’s try changing the range to 10..=1. Trying to run we get this:

lorenzo@orthanc:~/Temporary/odin$ odin run .
/home/lorenzo/Temporary/odin/main.odin(6:16) Error: Invalid interval range
	for i in 10..=1 {
	           ^

/home/lorenzo/Temporary/odin/main.odin(7:21) Error: Undeclared name: i
	fmt.println(i)
	            ^

So that doesn’t work. We have other possible cases as well. We might want to count up by 2s, or some other increment. Fortunately, we have another version of the for loop that allows us to do that.

When programming, you need to get used to being very exact. For instance, the difference between counting to 9 or to 10 can be significant, so make sure you understand exactly where a loop begins and ends.

Personally, I like to take out my notebook and unravel the loop manually.

C-style for loop

Odin supports a variant of the for loop that resembles the one in C. If we want to count from 1 to 10, it looks like this:

for i := 1; i <= 10; i += 1

This for statement of three parts. First is the initializer, were we say that we want to use a loop variable i that should initially be 1. Then we have the condition which states that the loop should continue as long as i is less than or equal to 10. Finally, we have the modifier part that states that at the end of each loop iteration, we increment i by 1.

For this simple case, we might as well use the range-based loop instead. But, if we want to count down or want to count by some number other than 1, then this loop will be useful.

Counting down

Here we count down from 10 to 1.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

import "core:fmt"

main :: proc() {
    for i := 10; i >= 1; i -= 1 {
        fmt.println(i)
    }

    fmt.println("... BLAST OFF!")
}

Compare the parts of the for statement witht the previous one. Here the counter is initialized to 10, we continue looping as long as the counter is is greater than or equal to 1 and we decrement the counter by 1 in each loop iteration.

You can easily end up with a program that enters an indefinite loop if your condition and modifier don’t match. TODO: continue here

If a program gets stuck in an indefinite loop, you can force the program to stop by pressing CONTROL and C at the same time on Unix, Linux or macOS or CONTROL and Z on Windows.

Modifiers other than 1

We might also want to skip some numbers. The following program counts from 0 to 100 by 5s.

1
2
3
4
5
6
7
8
9
package main

import "core:fmt"

main :: proc() {
    for i := 0; i <= 100; i += 5 {
        fmt.println(i)
    }
}

The fact that we might change the modifier statement, is one of the reasons why I generally avoid checking for equality in the condition statement, as in the following:

for i := 0 i != 10; i += 1

In this case, the loop works fine. It will count up until it reaches 10. However, what if we changed the modifier statement to i += 3, then i will never become exactly 10 (0, 3, 6, 9, 12, …). By changing the modifier to i < 10 instead, we ensure that as soon as i reaches 10 or greater, the loop ends.

You always need to analyze each particular situation, just to make sure that what you are doing makes sense in that context. It never hurts to do a quick check to see that what you are doing is what you meant to do. If you need, take out the pen and paper and work through the loop manually.

Modifier expressions do not have to use + or -

We’re not limited to using + or - with modifier expression. Maybe we needed a geometric progression instead. The following examples keeps doubling i until it reaches 1024.

1
2
3
4
5
6
7
8
9
package main

import "core:fmt"

main :: proc() {
    for i := 1; i <= 1024; i *= 2 {
	fmt.println(i)
    }
}}

Just make sure the modifier eventually gets the value of the loop variable. The following example alternates between positive and negative values until it reaches either -100 or +100.

1
2
3
4
5
6
7
8
9
package main

import "core:fmt"

main :: proc() {
    for i := 1; i >= -100 && i <= 100; i *= -3 {
	fmt.println(i)
    }
}

Scope of the counter variable

If you try to compile the following program you will get an error.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

import "core:fmt"

main :: proc() {
    for i in 1..=10 {
	    fmt.println("In the loop:", i)
    }

    fmt.println("After the loop:", i)
}

The error is:

$ odin run .
/home/lorenzo/Temporary/odin/main.odin(10:36) Error: Undeclared name: i
	fmt.println("After the loop:", i)

This happens because the variable i is only available inside the loop body. Trying to access it during after the loop has ended will not work. Further, even predeclaring the variable i is not going to do what you might think.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import "core:fmt"

main :: proc() {
    i: int

    for i in 1..=10 {
	fmt.println("In the loop:", i)
    }

    fmt.println("After the loop:", i)
}

This program can compile and produces the following output.

$ odin run .
In the loop: 1
In the loop: 2
In the loop: 3
In the loop: 4
In the loop: 5
In the loop: 6
In the loop: 7
In the loop: 8
In the loop: 9
In the loop: 10
After the loop: 0

Why is i 0 after the loop and not 10? That is because of a little feature called variable shadowing. There are actually 2 different variables called i here. The first one is the on line 6 which, since we didn’t give a value excplicitly, is going to be 0. In the loop we create a temporary variable, also called i which “shadows” the primary i. (We will look at shadowing in more detail in another lesson.) Once the loop is over, the temporary i is destroyed and we are back to our original i, which is still 0.

You can confirm that the value printed after the loop is differnt from what is in the loop by initializing the outer i to something. Change line 6 to:

i: int = 478

and run the program again.

Using a C-style for loop we can make the counter variable available after the loop, but we will have to change the := to a = in the initializer to avoid creating a temporary counter variable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import "core:fmt"

main :: proc() {
    i: int

    for i = 1; i < 10; i += 2 {
	    fmt.println("In the loop:", i)
    }

    fmt.println("After the loop:", i)
}

Try running this program to confirm that the loop counter i truly is available after the loop has completed.

Think About It

  1. Why is it that the value is 11 instead of 9 after the loop
  2. What would the result be if you change the = to := in the initializer statement of the for loop?

Name of the counter variable

So far, I’ve been using the variable name i as the counter variable. This stands for iterator and its very common to call a loop counter that. However, it’s not by force. Loop iterators are just ordinary variables. You can call them whatever you want, as long as it doesn’t violate the rules for variable naming in Odin. (We will be looking at those later).

Here I’ve used count as the variable name, instead of i. As long as we make sure to change all the places where we saw i to count, we are fine.

1
2
3
4
5
6
7
8
9
package main

import "core:fmt"

main :: proc() {
    for i = count; count < 10; count += 2 {
	    fmt.println("In the loop:", count)
    }
}

An indefinite loop

There is a variable of the Odin for loop that never ends. It looks as follows:

1
2
3
4
5
6
7
8
9
package main

import "core:fmt"

main :: proc() {
    for {
	    fmt.println("I have no intention of stopping")
    }
}

If you run this program it will keep running until you forcefully stop it with CTRL+C. (CTRL+Z on Windows.) It may seem useless to have an indefinite loop, but in reality there are times when you don’t know ahead of time how many times you will need to loop. One example is a case where you are asking the user to input some information and you want to keep asking them until they have passed in valid information. How many times will that take? It depends on the user. They might type if correct in the next iteration or they might be toying around and it will take tens of iterations before they enter something valid.

We usually want some way to break out of an indefinite loop, which is what we shall turn our attention to next.

Breaking out of loop

The following program creates two variabls, i and j. It then enters a loop that increments each variable, i by 1 and j by 2. It continues until the product of these two numbers is divisible by both 3 and 5. When we reach that point, we use the keywoard break to forcefully break out of the loop and continue with the print statements below the loop.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

import "core:fmt"

main :: proc() {
    i: int = 1
    j: int = 2

    for {
	    if (i * j) % 3 == 0 && (i * j) % 5 == 0 {
	        break
	    }

	    i += 1
	    j += 2
    }

    fmt.println("Phew! I managed to break out of that loop")
    fmt.printfln("I broke out at i=%d and j=%d", i, j)
}

The break statement is not limited to use in indefinite loops. You can use it in any loop where you need to break out early.

Skipping to the next loop iteration

Related to break there is also a statement that allows you to skip one or more loop iterations. This is continue. Let’s imagine we want to only print out the odd numbers from 1 to 20. One way we could do this is by using a for loop and the continue statement.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import "core:fmt"

main :: proc() {
    for i in 1..=10 {
	    if i % 2 == 0 {
	        continue
	    }

	    fmt.println(i)
    }
}

Loops within loops

You will find yourself writing a for loop within the body of another for loop. This often has to do with things like rows and columns within a grid or some similar idea. The following program prints multiplication tables from 1x1 up to 12x12. The part with an outer and inner loop is highlighted.

 1main :: proc() {
 2    fmt.print("    |")
 3    for h in 1..=12 {
 4        fmt.printf("% 4d|", h)
 5    }
 6    fmt.println()
 7
 8    for d in 1..=13 {
 9        fmt.print("----+")
10    }
11    fmt.println()
12
13    for r in 1..=12 {
14        fmt.printf("% 4d|", r)
15
16        for c in 1..=12 {
17            fmt.printf("% 4d|", r*c)
18        }
19        fmt.println()
20    }
21}

The outer loop starts on line 13. It goes through the rows. Each row gets a header column and then we enter the inner loop on line 16. This prints out each individual column.

You are not limited to an outer and an inner loop. When you work in 3D space, you have x-, y- and z-coordinates and might from time to time need to work with 3 levels of for loops.

Try it Out

Try to understand exactly how the multiplacation tables program works. To help you out you can:

  1. temporarily comment out bits of code to see what happens
  2. modify the code and study the results

Pay close attention to where I am using print and where I am using println. Is this just done arbitrarily or is there a specific reason for why I sometimes use one and at other times the other?

Breaking and continuing with labels

Now that we have learned about outer and inner loops, let’s rewrite a program from earlier in this lesson, in order to understand something about break and continue.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import "core:fmt"

main :: proc() {
    i, j: int

    for i = 1; i < 1000; i += 1 {
	    for j = 2; j < 1000; j += 2 {
	        if (i * j) % 3 == 0 && (i * j) % 5 == 0 {
	            break
	        }
	    }
    }

    fmt.println("Phew! I managed to break out of that loop")
    fmt.printfln("I broke out at i=%d and j=%d", i, j)
}

Run this program and look at the output. Notice how the value of i has gone all the way up to 1000. Why could that be? It turns out to be very simple. The break statement by default only breaks out of the innermost loop. But we want it break out of the outer loop. To do this we will take advantage of a concept called a label.

Here is an updated version. The modified lines are as usual highlighted.

 1package main
 2
 3import "core:fmt"
 4
 5main :: proc() {
 6    i, j: int
 7
 8    outer: for i = 1; i < 1000; i += 1 {
 9	    for j = 2; j < 1000; j += 2 {
10	        if (i * j) % 3 == 0 && (i * j) % 5 == 0 {
11	            break outer
12	        }
13	    }
14    }
15
16    fmt.println("Phew! I managed to break out of that loop")
17    fmt.printfln("I broke out at i=%d and j=%d", i, j)
18}

On line 8 we create a label called “outer”. Then inside the inner loop at the point where we want to break out, we add the name of that label. That allows us to break out. Of course, the output is still different from the version we wrote earlier. Can you explain why that is the case?

Iterating over an array

Before this lesson, iterating over arrays was the only thing you had used for loops for. Even when it comes to array iterations, there are a few ways to do it and for the sake of completeness I want to quickly go over these.

Iterating over an array with a C-style loop

We can iterate over arrays using a C-style loop. In the loop body, we take advantage of the fact that we can access an array element by array[n] where n is the index number of the element we want. (Remember that arrays start at the index 0.)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

import "core:fmt"

main :: proc() {
    nums := []int{4, 8, 10, 11, -1, 7, 9}

    for i := 0; i < len(nums); i += 1 {
	    fmt.println(nums[i])
    }
}

First we create an array called nums and initialize it with some numbers. Then we enter a loop that counts from 0 to len(nums). the len procedure returns the length of of the array. Since we count from 0, the index number of the last item is going to be one less than the lenght of the array. That is why we use < in the condition.

The only array you have worked with so far is the one holding command-line arguments. As you can see, it is not hard to create other arrays as well. We are soon going to dig deeper into arrays as there is quite a bit to learn about them.

Iterating over an array with for..in

This is the type of for loop you have been using to iterate of the members of os.args. Let’s adapt the last program we wrote to use this loop instead.

package main

import "core:fmt"

main :: proc() {
    nums := []int{4, 8, 10, 11, -1, 7, 9}

    for n in nums {
	    fmt.println(n)
    }
}

Iterating over an array with for..in and an index

If we are using the for..in loop but also want an index, that is possible. The loop can take a second variable, which will hold the index.

 1package main
 2
 3import "core:fmt"
 4
 5main :: proc() {
 6    nums := []int{4, 8, 10, 11, -1, 7, 9}
 7
 8    for n, i in nums {
 9	    fmt.printfln("%d. %d", i+1, n)
10    }
11}

Iterating over an array in reverse

Sometimes we want to iterate over an array in reverse order. That is such a common thing that there is an easy solutiion provided by Odin:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

import "core:fmt"

main :: proc() {
    nums := []int{4, 8, 10, 11, -1, 7, 9}

    #reverse for n in nums {
	    fmt.println(n)
    }
}

As always, try the program out to make sure it really prints out the array in reverse order. And notice that it doesn’t change the array itself.

If you use #reverse with a loop that also includes an index (for n, i in nums), be aware of the fact that the index is also reversed. This may or may not be what you want. If you want the array reversed but not the indexes, you will have to use a different method for that.

Becoming profient in a programming language also means learning all the little warts, or unexpected features and knowing how to work around them.

Iterating over a string

Look at the follwing program. Compile it and run it. Study the output.

1
2
3
4
5
6
7
8
9
package main

import "core:fmt"

main :: proc() {
    for r in "Welcome to Odin" {
	    fmt.println(r)
    }
}

Yes, you can iterate over the runes of a string. Let’s say we wanted to replace any instance of the letter “e” with an asterisk. We could do this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import "core:fmt"

main :: proc() {
    for r in "Welcome to Odin" {
	    if r == 'e' {
	        fmt.print('*')
	        continue
	    }

	    fmt.print(r)
    }

    fmt.println()
}

I could of course have used an if..else construct instead, but since we have learned about continue in this lesson, I thought it would be appropriate to use it here.

Flow Control

Loops are control flow statements. They alter the order in which statements are executed. Remember that by default all statements are executed from top to bottom and when the program has executed the last statement, it ends? We loops we keep repeating the same block of code over and over again until a condition is hit.

Summary

In this lesson we have looked at much of what there is to know about loops. There are still a few things to learn, but they will have to wait until we have learned a few other concepts (like pointers). Loops are things that you will use all the time, so it’s a good idea to get really familiar with them.

In the next lesson we are going to look at asking the user to type something and capturing it into our program.

Exercises

Exercise 1

Write a program that counts from 10 to 30, printing each number

Exercise 2

Write a program that counts by 10s from 0 to 200, printing each number.

Exercise 3

Write a program that counts down by 5s from 100 to 5, printing each number.

Exercise 4

Write a program that counts from 1 to 100. Print all numbers, except those that are divisible by 7.

Exercise 5

Write a program that counts from 1 to 100. If a number is divisible by 3, print “fizz”. It is is divisible by 5, print “buzz”. If it divisible by both 3 and 5 print “fizz buzz”. Otherwise print the number.

Exercise 6

Run the following program and look at the output.

package main

import "core:fmt"

main :: proc() {
    for i in 1..=30 {
	    if i % 3 != 0 {
	        continue
	    }

	    fmt.println(i)
    }
}

Modify the program so that the fmt.print stamenent is above the if statement. Run the program again. Does the modified program print the same results as the original one, or is the output different. Explain why.

Exercise 7

Write a program that takes the following array and prints it out in reverse order, one element per line.

weekdays := []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}

Exercise 8

Write a program that creates an array of the names of the month names. Then it should print out this list, with each month preceded by its ordinal number (January should be 1, not 0). Also, if the iterator gets to your birth month, print ‘***’ before and after then month. I was born in April so my program would output:

 1. January
 2. February
 3. March
 4. *** April ***
 5. May
 6. June
 7. July
 8. August
 9. September
10. October
11. November
12. December

Exercise 9

Write a program that takes 2 command-line arguments. They should both be numbers and the second one needs to be greater than the first one. Otherwise print an error message (to stderr) and exit the program.

Then iterate through the numbers from the first argument to the last, printing each number. The following is example output.

$ ./exercise-9 3 7
3
4
5
6
7

Exercise 10

Modify the program in exercise 9 to take an optional 3rd command-line argument. This one should be a boolean argument and its default value shall be false. This argument affects the output as follows:

  1. if the argument is false, print out only odd numbers
  2. if the argument is even print only even numbers