Python 2
Python 2
Algorithms
An algorithm is a clear set of steps to solve any problem in a particular class.
Algorithms that computers work on deal with numbers. Computers can only compute on numbers;
however, we can represent a variety of useful things (letters, words, images, videos, sound, etc.) as
numbers so that computers can compute on them. As a simple example of an algorithm that works
with numbers, we might consider the following algorithm (which takes one parameter N, a non-
negative integer):
For any non-negative integer N that I give you, you should be able to execute these steps. If you do
these steps for N = 2, you should come up with the sequence of numbers 0 4 12 10.
You must specify exactly what you want the computer to do. The computer does not "know what you
mean" when you write something vague, nor can it figure out an "etc." Instead, you must be able to
describe exactly what you want to do in a step-by-step fashion. Precisely describing the exact steps
to perform a specific task is somewhat tricky, as we are used to people implicitly understanding
details we omit. The computer will not do that for you (in any programming language).
The above figure shows how you should approach designing your algorithm.
If you plan well enough and translate it correctly, your code will just work the first time. If it does not
work the first time, you at least have a solid plan of what the code should be doing to guide your178
debugging.
If we wanted to write a program to compute x raised to the y power. To do Step 1, we would pick
particular values for x and y, and work them by hand. We might try x = 3 and y = 4, getting an
answer of 3^4 = 81.
1. Multiply 3 by 3
2. You get 9
3. Multiply 3 by 9
4. You get 27
5. Multiply 3 by 27
6. You get 81
7. 81 is your answer.
Step 3: Generalize Your Steps
In our Step 2 steps, we solve particular instances, but now we need to find the pattern that allows us
to solve the whole class. This generalization typically requires two activities. First, we must take
particular values that we used and replace them with mathematical expressions of the parameters.
Looking at our Step 2 steps for computing 3^4, we would see that we are always multiplying 3 by
something in each step. In the more general case, we will not always use 3—we are using 3
specifically because it is the value that we picked for x. We can generalize this slightly by replacing
this occurrence of 3 with x:
Multiply x by 3
You get 9
Multiply x by 9
You get 27
Multiply x by 27
You get 81
81 is your answer.
The second common way to generalize steps is to find repetition—the same step repeated over and
over. Often the number of times that the pattern repeats will depend on the parameters. We must
generalize how many times to do the steps, as well as what the steps are. Sometimes, we may find
steps which are almost repetitive, in which case we may need to adjust our steps to make them
exactly repetitive. In our 3^4 example, our multiplication steps are almost repetitive—both multiply
x by "something," but that "something" changes (3 then 9 then 27). Examining the steps in more
detail, we will see that the "something" we multiply is the answer from the previous step. We can
then give it a name (and an initial value) to make all of these steps the same:
Start with n = 3
n = Multiply x by n
n = Multiply x by n
n = Multiply x by n
n is your answer
Now, we have the same exact step repeated three times. We can now contemplate how many times
this step repeats as a function of x and/or y. We must be careful not to jump to the conclusion that it
repeats x times because x = 3—that is just a coincidence in this case. In this case, it repeats y - 1
times. The reason for this is that we need to multiply four 3s together, and we already have one in n
at the start, so we need y - 1 more. This would lead to the following generalized steps:
Start with n = 3
Count up from 1 to y-1 (inclusive), for each number you count,
n = multiply x by n
n is your answer
We need to make one more generalization of a specific value to a function of the parameters. We
start with n = 3; however, we would not always want to start with 3. In the general case, we would
want to start with n = x:
Start with n = x
Count up from 1 to y-1 (inclusive), for each number you count,
n = multiply x by n
n is your answer
Sometimes you may find it difficult to see the pattern, making it hard to generalize the steps. When
this happens, returning to Steps 1 and 2 may help. Doing more instances of the problem will provide
more information for you to consider, possibly giving you insight into the patterns of your algorithm.
This process is often referred to as writing 'pseudo-code', as you are working to design an algorithm
programmatically with no particular target language. Nearly all programmers make use of this
method to ensure their algorithm is correct before writing any actual code.
If y is 0 then
1 is your answer
Otherwise:
Start with n = x
Count up from 1 to y-1 (inclusive), for each number you count,
n = Multiply x by n
n is your answer
These steps check explicitly for the case that gave us a problem (y = 0), give the right answer for
that case, then perform the more general algorithm. For some problems, there may be corner cases
which require this sort of special attention. However, for this problem, we can do better. Note that if
you were unable to see the better solution and were to take the above approach, it is not wrong per
se, but it is not the best solution.
Instead, a better approach would be to realize that if we count no times, we need an answer of 1, so
we should start n at 1 instead of at x. In doing so, we need to count 1 more time (to y instead of to y
– 1)—to multiply by x one more time:
Start with n = 1
Count up from 1 to y (inclusive), for each number you count,
n = Multiply x by n
n is your answer
Whenever we detect problems with our algorithm in Step 4, we typically want to return to Steps 1
and 2 to get more information to generalize from. Sometimes, we may see the problem right away
(e.g., if we made a trivial arithmetic mistake, or if executing the problematic test case by hand gives
us insight into the correct generalization). If we see how to fix the problem, it is fine to fix it right away
without redoing Steps 1 and 2, but if you are stuck, you should redo those steps until you find a
solution. Whatever approach you take to fixing your algorithm, you should re-test it with all the test
cases you have already used, as well as some new ones.
Determining good test cases is an important skill that improves with practice. For testing in Step 4,
you will want to test with cases which at least produce a few different answers (e.g., if your algorithm
has a "yes" or "no" answer, you should test with parameters which produce both "yes" and "no").
You should also test any corner cases—cases where the behavior may be different from the more
general cases. Whenever you have conditional decisions (including limits on where to count), you
should test potential corner cases right around the boundaries of these conditions. For example, if
your algorithm makes a decision based on whether or not x < 3 , you might want to test with x = 2, x
= 3, and x = 4. You can limit your "pencil and paper" testing somewhat, since you will do more
testing on the actual code once you have written it.
Creating an Algorithm
Intro to a Pattern of Squares
For our second example, we will look at a pattern of squares drawn on a grid. You may wonder why
a programmer would be interested in drawing squares on a grid. Beyond this example serving us
well for analyzing patterns in general, computer graphics ultimately boil down to drawing colored
pixels on a 2D grid (the screen). In this particular example, we have an algorithm that is
parameterized over one integer N and produces a pattern of red and blue squares on a grid that
starts all white. The output of the algorithm for N = 0 to N = 5 is as follows:
To devise the algorithm, we should work through steps 1–4 (as we should with all problems).
The next two videos walk through these steps to illustrate how we could come up with this algorithm.
We note that there are many correct algorithms for this problem. Even if we restrict ourselves to the
ones we can come up with naturally (that is, as a result of working through steps 1–4, rather than
trying something bizarre), there are still many choices that are equivalent and correct. Which
algorithm you come up with would be determined by how you approach step 1.
The algorithm in the video works from left to right, filling in each column from bottom to top. If we had
worked step 1 by working from the top down, filling in each row from left to right, we might have
ended up with the following slightly different algorithm instead:
Count down from N to 0 (inclusive), call each number you count "y" and
Count down from 0 to y (inclusive), call each number you count "x" and
if (x + y is a multiple of 3)
then place a blue square at (x,y)
otherwise place a red square at (x,y)
Of course, those are not the only two ways. You could have worked across the rows from the bottom
up going right to left, and come up with a slightly different (but also equivalent) algorithm. Or even an
entirely different approach, such as filling in the entire "triangle" with red squares, then going back to
fill in the blue squares.
We emphasize this point because it is important for you to understand that there is always more than
one right answer to a programming problem. You might work a problem and come up with a correct
solution but find that it looks completely different from some other solution you know to be correct
(e.g., the ones provided for some of the problems in this book, or a teacher's solutions to an exam).
Understanding this possibility is important so that you will not incorrectly think that a right answer is
wrong because you have seen a different right answer. Not only is that experience frustrating, but it
hinders your learning.
In this video, we will develop an algorithm for a particular pattern of blue and red squares on a grid.
We're going to start with Step 1, doing an instance of the problem. In this instance, I picked N=3.
The first thing I'm going to do is work the problem by hand without really thinking through exactly
what I'm doing. That is, I'm going to do it right, but I'm not going to try to write down what I did. Once
I've done that, I'm going to do Step 2, where I write down exactly what I did in a step-by-step fashion.
The first thing I did was put a blue square at (0,0). Then, I put a red square at (0,1), another red
square at (0,2), a blue square at (0,3), a red square at (1,1), blue at (1,2), red at (1,3), red at (2,2),
red at (2,3), and finally a blue at (3,3). Now I'd like to generalize these steps. Step 3 of the
programming process, and if we look at the steps, we can see there's some repetition in counting
behavior, which is going to help us generalize these steps into an algorithm. These first four steps,
have x=0. Then we have a group of three steps for x=1, a group of two steps for x=2, and finally, a
single step for x=3. We're repeating somewhat similar steps as we count x going from 0-3, but
exactly what we do varies. We color the squares, blue, red, red, blue for x=0, or red, blue, red for
x=1. The color pattern is something we still have to figure out. Also, how many steps and the y-
coordinate of each varies? Let's look at the y-coordinates first, coming back to the colors in a minute.
If we look at this first group of steps, we see that the y-coordinates go from 0-3. If we look at the
second group of steps, we see that the y-coordinates go from 1-3, and in this third group, 2-3. It
looks like, in general we're counting from x to 3. If we ignore the colors for a moment, we can take
this group of steps and say, we count from 0 -3, calling the number that we count y, then put a
square of some color we'll figure out later at (0,y). We can write similar steps for each of these other
groups. Now we can use patterns in these steps to generalize our algorithm further. Each set of
steps is the same, except for the x-coordinate shown in bold, and these vary in a nice counting
pattern. That is, we can say count from 0-3, call each number that we count x, and for each of those
numbers we'll count from x to 3, and call that y. We'll put some color square at (x,y). But this is only
true for N=3. A clue that we have not finished generalizing is that the steps do not depend on N at
all. We know that the pattern will be different for different N, but this is not reflected in the algorithm
we wrote. We need to make it more general, how could we do this? We might just realize it, but if we
don't, we can go back and repeat steps 1 and 2. If the pattern is hard to figure out, we may have to
do steps 1 and 2 many times. For example, if we went back and performed Step 1 for N=1, we end
up with this pattern. When we do Step 2, we write down these steps. If we go through the
generalization process again, we see that we come up with these steps. Count from 0-1, call it x,
count from x to 1, call it y, then put a square of some color at (x,y), which is very similar to N=3,
except this number has changed. For any N, we count from 0 to N, based on the pattern from our
examples. Now we have a more generalized algorithm. Of course, we need to figure out what the
question marks are for the colors. To do that, we can go back to the colors from our N=3 example.
We had mostly red squares with some blue squares, so we should figure out the pattern for when a
square is blue. I've thrown away the red ones, so we can just look at blue for a pattern. This pattern
may be a bit subtle to see with only four pieces of information. You may have figured it out, but if not,
what could you do? Well, it might be easier to see with more information. Looking at N=5, we have
more blue squares to consider. Now you look at the pattern and see what you come up with. This is
mostly your ability to look at numbers and find patterns. If we add the x and y-coordinates together,
we see that they are 0, 3, 3, 6, 6, 6, and 9. The pattern here is we draw a blue square, if and only if
x+y is a multiple of 3. Now we can go back to our algorithm, and specify if x+y is a multiple of 3, we
put a blue square at (x,y). Otherwise, we put a red square at (x,y). This algorithm looks pretty good,
but we will want to do Step 4 and test it, shown in the next video.