Last week we learned about conditionals and the
syntax of if
and else
statements, along
with elif
, and we used this to respnd to
user-triggered events. Broadly, this could be divided into three
topics:
We looked at the syntax for three kinds of blocks:
if conditional: # instructions here
if conditional: # instructions here else: # instructions here
if conditional: # instructions here elif conditional: # instructions here else: # instructions here
The conditional is comprised of an expression that evaluaates to a Boolean value. These allow us to ask questions about the status of our program. Things like: "Is the mouse being pressed?" "Is the mouse on the right side of the screen??" "Is the size of this shape greater than 200 pixels?"
Inside the code blocks (the indented lines
after a colon) go any arbitrary Python / Processing
statements and commands, which get executed if the
conditions are met (if they evaluate to True
).
Boolean expressions are variables that evaluate
to True
and False
(like mousePressed
or keyPressed
)
together with boolean operators that allow
us to combine these True
and False
values to make more detailed conditions.
In Python, the boolean operators are:
and
meaning both parts must
be True
or
meaning at least one part must
be True
not
meaning the thing that follows must be False
In addition to these boolean operators, we can also ask questions about numbers, using comparison operators:
less than (<
) meaning the value on the left
must be less than the value on the right
greater than (>
) meaning the value on the left
must be greater than the value on the right
equal to (==
) meaning both values must be
equal. Remember: this is
different from the assignment operator, which only uses one
equal sign (=
). The assignment operator is
making a statement: "This variable now equals this value";
while the equality comparison asks a question: "Is the
variable on the left equal to the variable on the right?" It
is easy to mix them up, so be careful and don't forget!!!
less than or equal to (<=
). This is
a shortcut. It is very useful, but can always be written
another way. For example: x <= 10
could also
be written as x < 10 or x == 10
, or if
you're working with whole numbers, as x <
11
.
greater than or equal to (>=
). Same as
above.
We also looked at several new special Processing variables:
mousePressed
: a Boolean variable
that is True
whenever the mouse is being
pressed
keyPressed
: a Boolean variable
similar to the above that is True
whenever any
key is being pressed.
key
: a variable that contains
a string (text in single or double
quotes, 'a'
or "s"
) of whichever
character the user has most recently pressed. You can
check this value by specifying a string
literal with quotes and using
the comparison operator for equality:
if key == 'a':
And lastly, we looked at two new kinds of blocks for event handling:
def mousePressed(): # code instructions
and
def keyPressed(): # code instructions
Unlike the special variables mousePressed
and keyPressed
, these blocks respond
to every mouse or key press. That means that each
user action will trigger the code in these blocks exactly
once, and it is not depending on the
current frameRate
of your program.
If any of that remains confusing, please review the class notes from last week or reach out for help.
Today we will learn how to create repetition with our code. This is one of the most powerful concepts within software. So far this semester we have learned:
how to create and use variables, to create a media object that can dynamically change,
how to create interactive programs, that use dynamic variables to chnage in response to user input,
and how to use conditionals to check for different situations within our code and render or respond to them differently.
And all of these things are essential components of software.
But repetition is another key concept that is at the core of what software does.
The kind of repetition that we are going to learn today allows you to write a program that does something hundreds, thousands, millions, or billions of times. Whenever you hear people talking about the power of software to proces "big data", or to search through billions of web pages, they are referencing the ability of software to implement some kind of repetition. It lets you write a piece code, and then repeat that an inhuman number of times.
To add repitition in a computer program, we use loops.
You have already seen one kind of repetition in your
sketches. Remember from week 3 that all the code inside
the draw()
block gets repeated many times, once
per frame, depending on the frame rate of your sketch:
def draw(): # This block of code runs many times
This is definitely a kind of repetition. Remember the movie
metaphor: without setup()
and draw()
your code creates a static image
like a photograph. But with these, your code is like a film:
a sequence of frames played quickly, giving the illusion of
motion. Every time draw()
runs, it outputs one
frame.
But the repetition that we are talking about today is for drawing multiples within one frame. So it is a way to go from images like the left, to images like the right:
So how does it work? First some pseudocode:
while some question is True: Run some commands over and over.
The question is a Boolean expression, which, as we talked about last week, is one or more Boolean variables combined with boolean operatorsor comparison operators asking questions about the values of variables. And the commands in the block are any arbitrary commands.
What this does is execute all the commands, in order, over
and over again, as long as the conditional is
True
— i.e. "while" it is true, hence the
name.
The actual syntax for this new structure is the following:
while conditional: # run some commands
That's still a little abstract, so let's work through a specific example.
Let's start with the following code:
size(600,600) background(255) fill(100, 100, 250) rect(200,200, 200,20)
Now say we want to add two more rectangles. We could copy/paste and change some values like this:
size(600,600) background(255) fill(100, 100, 250) rect(200,100, 200,20) rect(200,200, 200,20) rect(200,300, 200,20)
That's fine in this case, but what if we now wanted to add 10 or
100 or 1000 more. And what if we then wanted to change the width
for each one. This could get tedious really quickly. So let's
see how we could do it with a while
loop.
size(600,600) background(255) fill(100, 100, 250) i = 100 while i <= 300: rect(200,i, 200,20) i = i + 100
Mentally step through that code and make note of each value
of i
. Notice that the above loop is
functionally equivalent to the three
separate rect()
statements above.
This introduces some new vocabulary.
The variable assignment: i = 100
. This
defines what we will call the looping
variable. This is the variable that will be
used to control the number of repetitions of the loop.
The boolean operator: i <=
300
. This works just like an if
statement, so we can call this
the conditional or
the check, as in "the check to
continue".
The last line (i = i + 1
) modifies
the looping variable, and usually
increases the value, so we can call this
or increment.
(Or decrement, if it were being
decreased.)
Another term from computer science for looping is iteration, a fancy but commonly used term in computer programming that simply means repeating in this way. Looping is also referred to as iterating, and one might describe repetition like this as an iterative style of programming. (There are other styles, such as recursive, but we won't talk about those.)
Some notes on this new syntax:
Unlike with other variables, I highlyrecommend
that you always create your looping variables
right before your while
loop, even if you
are inside another block, such as
draw()
or an if
statement. This will give the variable local
scope and will make things a little easier to
keep track of.
I recommend always putting
your increment as the last line in
the while
loop block. This will also help
you keep track of things and make it easy to check that
you are properly incrementing your looping
variable.
In the past I have said to always use informative
variable names. And you can do that here, but it is
common practice to simply use short, one-letter names
for looping variables. Common choices
are i
, j
,
or n
. So feel free to do that. Of course,
if you have many loops and you want to help keep track
of them, using more informative names
for looping variables might help,
like personNumber
.
println()
can be very useful here. Try putting
a println()
inside a loop to see how the value
of the looping variable changes. This works
well in
static mode, because the loop will run
once, but can be very hard to read in interactive
mode, beacuse the loop will run every frame, many
times per second.
Don't forget to increment your looping variable!!!!! This happens so frequently. So I'm telling you now: do not forget!!
What happens if you forget the increment? Try removing it
(or commenting it out) in the above example, running your
code, and seeing what happens. Probably nothing will show up
on the screen, and you might even have trouble exiting the
program. If you do, go to the PDE and click Stop
instead of simply trying to click on the "x" to close the
window. This is because your while
loop never
exits! It never completes, so your program hangs, running in
this loop forever. This is called an infinite
loop and should always be avoided (for
now). Make sure that you are incrementing your
looping variable, and that eventually the conditional check
will be met.
For example, the following code example would never terminate:
i = 100 while i <= 300: i = i - 50 rect(200,i, 200,20) i = i + 50
Do you see why? Step through the loop a few times in your
mind to think about what happens to the values
of i
.
We could call this the "two steps forward, two steps back" loop. That might seem like a silly thing to do, and that infinte loop could be easily caught and fixed. But sometimes you may encounter cases that are harder to catch.
Getting back to our original example. Now we have a flexible way of adding more repetitions. If we want to add more rectangles, there are several ways.
# This makes more rectangles because # they are spaced more closely together size(600,600) background(255) fill(100, 100, 250) i = 100 while i <= 300: rect(200,i, 200,20) i = i + 50
# This makes more rectangles because # they extend further down in the window size(600,600) background(255) fill(100, 100, 250) i = 100 while i <= 600: rect(200,i, 200,20) i = i + 100
Experiment with this and see what you get.
So, that saves us some typing. But there are even more
interesting things about loops. For example, what if you
wanted to modify what you were doing inside the
loop each time you did it. Let's move fill()
from outside the loop block to inside, and modify its value
with the looping variable:
size(600,600)
background(255)
i = 100
while i <= 250:
fill(i,100,200)
rect(200,i, 200,20)
i = i + 50
Mentally stepping through this loop, we see that the
parameter to fill()
change each time the loop
runs. The values will be:
100,100,200
blue
150,100,200
more red
200,100,200
even more red
250,100,200
purplish
giving us a nice gradient.
We can also use the looping variable to modify other things
about the shapes we're drawing. For example, the width
of rect()
:
size(600,600)
background(255)
i = 100
while i <= 250:
fill(i,100,200)
rect(200,i, i,20)
i = i + 50
So we can use loops so that the commands we are running repeatedly can change a little each time.
But we can even do more. Using loops, we can do things
a dynamic number of times. For example, let's put
our sketch in active mode and use a dynamic
variable in the while
loop conditional:
def setup(): size(600,600) def draw(): background(255) i = 100 while i <= mouseY: fill(i,100,250,100) rect(200,i, 200,20) i = i + 50
Try mentally stepping through this loop. When this code runs, the number of repetitions is determined dynamically, when the sketch runs, based on user input. This is exciting because so far the number of things that you've been drawing has always been fixed in advance.
Even when you had lots of dynamism in your compositions, the number of things that you were drawing were always in a sense hard-coded. Now, the number of things themselves can vary.
Another new possibility here is to combine loops in interesting ways.
Let's say that instead of simply drawing a rectangle in a loop, you wanted to draw many things. Let's go back to static mode and start with this example:
size(600,600) background(255) i = 100 while i <= 250: fill(i,100,200) rect(200,i, 25,25) rect(300,i, 25,25) rect(400,i, 25,25) i = i + 50
Now inside the loop we're drawing three squraes with each iteration. Note the highlighted values. They are changing, increasing in an incremental pattern.
When you see values incrementing like that, think back to the original example today and think how you could write that instead.
We can replace those three rect()
statements
with a loop — even though we are already inside a
loop.
size(600,600) background(255) i = 100 while i <= 250: fill(i,100,200) j = 200 while j <= 400: rect(j,i, 25,25) j = j + 100 i = i + 50
Mentally step through one iteration of the i
loop, and see how it is equivalent to one iteration of the
previous code snippet.
Now, just like with the example that started off this lesson, now we can ask: what if we now wanted to add 10 or 100 or 1000 more squares? And what if we then wanted to change the width for each one?
What we have here is called a nested loop and is a very powerful idea. In pseudocode, you could think of it like:
Repeat many times, Each time through, repeat many times Run some commands each time
In this example, we've adding an entirely new inner
loop here, with a new looping variable j
, but
it is inside the original loop.
The outer "i loop" repeats four times (for i
equal to 100, 150, 200, and 250). And each time it
repeats, the inner "j loop" repeats three times
(for j
equal to 200, 300, and 400). So how many
rectangles will there be total? 4 x 3 = 12.
Nested iteration multiplies the number of repetitions. Think of this kind of repetition as working in two dimensions. Instead of repeating in a linear fashion, it can be used to create a grid.
Note that each row of squares is the same color. Why? Where
is the fill()
statement? It is inside the "i
loop" but outside of the inner "j loop". In other words, for
each iteration of the outer loop, the color is changed, then
the inner loop repeats three times without changing the
color. (Then the outer loop repeats again.)
Better loop organization
Writing loops in the above way is fine, but there is a slightly different way of working out the math of loops that is in many ways more powerful and easier to keep track of. Compare the following two examples:
size(600,600) background(255) i = 100 while i <= 400: fill(i,100,200) rect(200,i, 25,25) i = i + 100
size(600,600) background(255) i = 1 while i <= 4: fill( i*100, 100,200) rect(200, i*100, 25,25) i = i + 1
Stepping through both examples, it should be clear that both
are functionally equivalent. Both result in the
following fill()
commands:
fill(100,100,200)
fill(200,100,200)
fill(300,100,200)
fill(400,100,200)
And corresponding rect()
commands for each.
But there are several advantages to the version on the right side.
If I were to ask you how many times each loop repeats, you could step through the left side and do the math, but you could also quickly glance at the right side and probably figure it out much more quickly. Of course, the math inside the loop is a little more complicated, but it is more obvious and apparent how many iterations are taking place.
You should feel free for now to work with whichever is more clear to you. But next week when we start to look at arrays, it will be essential to use the right side style. So it is good to start thinking about it now. And there are certain kinds of functionality that are much easier to implement with the right side style, as we'll see next.
I've mentioned that you are able to put any arbitrary
commands inside the while
block. Let's see an
example of doing something inside a loop that is a little
more complicated than merely drawing squares. Let's start
with some pseudocode:
Repeat 10 times Each time through, Alternate the fill color between red and blue Draw a square
Wait, "alternate"?? Let's try to get more specific:
Repeat 10 times Each time through, If we're on an even numbered repetition, set the fill to red If we're on an odd numbered repetition, set the fill to blue Draw a square
OK, we're getting into something that we can work with in Processing syntax.
We can use our looping variable somehow. But how do you determine if a number is even or odd?
Even means that it is divisible by two, and odd means that it is not. So we could divide the looping variable by two and somehow check if there is a remainder or not. You could say something like:
if 2 * (i/2) == i:
But yikes, that is confusing.
It turns out that this is such a common thing to do in
programming that there is a special operator just for
this: %
(SHIFT-5 on USA keyboards), referred to
as modulo. This operator returns the remainder when
the left number is divided by the right. Some examples:
10 % 2 # Answer: 0
21 % 2 # Answer: 1
25 % 5 # Answer: 0
28 % 5 # Answer: 3
Now we have all the pieces to implement this.
size(600,600) background(255) i = 1 while i <= 4: if i % 2 == 0: fill( 255,0,0 ) if i % 2 == 1: fill( 0,0,255 ) rect(200, i*100, 25,25) i = i + 1
This would be a great occasion to use else
, so
let's re-write it that way:
size(600,600)
background(255)
i = 1
while i <= 4:
if i % 2 == 0:
fill( 255,0,0 )
else:
fill( 0,0,255 )
rect(200, i*100, 25,25)
i = i + 1
Read this as: "If i
is even, then set the fill
color to red, otherwise set it to blue."
What if we want to alternate colors in a grid? Thinking of a grid — i.e. repetition in two dimensions, horizontal and vertical — should immediately trigger thinking in terms of a nested loop: repeating, and for each iteration, repeating again.
For this, we can put together everything from this lesson into the following example:
size(600, 600) background(255) i = 1 while i <= 4: j = 1 while j <= 4: if (i + j) % 2 == 0: fill(255, 0, 0) else: fill(0, 0, 255) rect( j*100, i*100, 25,25) j = j + 1 i = i + 1
Here is an example that puts that in interactive
mode and adds some keyboard interaction for fun. Try
pressing 'z'
and 'x'
to change the
number of iterations, and 'n'
and 'm'
to change the size of the squares:
squareCount = 4 squareSize = 25 def setup(): size(600, 600) def draw(): background(255) i = 1 while i <= squareCount: j = 1 while j <= squareCount: if (i + j) % 2 == 0: fill( 255, 0, 0, 100 ) else: fill( 0, 0, 255, 100 ) rect( j*100, i*100, squareSize,squareSize) j = j + 1 i = i + 1 def keyPressed(): global squareCount, squareSize if ( key == 'z' ): squareCount = squareCount - 1 if ( key == 'x' ): squareCount = squareCount + 1 if ( key == 'n' ): squareSize = squareSize - 5 if ( key == 'm' ): squareSize = squareSize + 5
for
loops
All the above discussion is about while
loops.
There is another kind of syntax for creating loops that
looks a little different but achieves the same
behavior: for
loops. These two different
constructs are very similar but not precisely equivalent.
In general, programmers tend to use for
loops
more commonly than while
loops, but I find that
the syntax of while
more clearly illustrates
the principles of looping in a way that is more easily
readable by new programmers. Feel free to use whichever loop
style you like and whichever is more clear to you.
The following two examples show how to translate between one and the other:
i = 0 while i < 10: println("i = " + str(i)) rect(i,i,5,5) i = i + 1
and
for i in range(10): println("i = " + str(i)) rect(i,i,5,5)
Both examples contain: a variable declaration and
a boolean
expression. But the
variable increment
works differently in each case. In the while
loops that we have been working on in this lesson, the
increment is clearly discernible on its own line. In
the for
loop case, the increment happens kind
of implicitly, because the Python range()
command generates a list of numbers, and the
variable i
is automatically set to each number
in an iterative way.
What is a list, you may ask? We'll talk about this next week.
I will say that one advantage of the for
loop
is that it's nearly impossible to forget to include
your variable increment, making it less likely to
accidentally produce an infinite loop.