Think like a Computer Scientist
Jaime Silvela, 2005
I’ve always been fond of mathematics. For a couple of years I searched for good popularization books but generally ran to the same experience: these books would have lots of multicolor pictures of fractals, or deal with cool-sounding topics such as chaos, but would not reproduce the mindset of the mathematician, which is achieved after many exercises with arithmetic, calculus, geometry, logic or theorem proving.
Through some twists of fate I’ve ended up getting into Computer Science, and I see some of the same problems here. Books popularizing Computer Science generally talk about artificial intelligence, Turing tests, artificial life or neural networks. These topics, however, occupy a tiny group of computer scientists (and attract a great deal of crackpots from many disciplines) and do not represent the basic mindset of the programmer. This essay is my attempt to explain how Computer Scientists think.
So let’s begin with a problem. Imagine that you’re given a maze, in which there is a mouse and a piece of cheese, and you want the computer to find the shortest path the mouse can take to get to the cheese.
First of all, you need to remember that a computer is basically a calculator
with memory. How do you express a maze, a mouse, and cheese? Computer memory is
just a sequence of storage spaces. On each space you can put a number.
Everything can be represented as a number. For instance, in our maze, we could
walkable space with the number 0, a wall with the number 1,
and cheese with the number 2. Our previous drawing is now:
Computer memory is sequential, not bidimensional, so a more accurate
representation would put all these numbers on a single line. Usually, computer
memory is represented as a column of boxes, with the beginning at the top.
Let’s put the bidimensional arrangement of numbers into memory going row by
row, from left to right, from top to bottom. This is called
order. Also, we need to keep track of the mouse’s position. To do that, we
use a variable, whose value will be itself kept in memory. Actually, it
will most likely be stored in a register, which is a piece of fast
memory used for commonly accessed data. This variable will point to the memory
location of the mouse’s position. All memory positions on a computer are
identified by a number, the memory address. So, to sum things up, we’ll keep
the maze in memory, and we’ll keep the memory address of the mouse’s current
position in a register. Here’s the result:
Anyway, this takes us too far. It’s generally useful to think of data in the abstract rather than as a column of numbers. My purpose in showing this to you was to illustrate what programmers consciously or unconsciously take for granted: Anything can be represented on a computer. Everything can be expressed as a column of numbers.
So let’s continue. To make things interesting, our mouse doesn’t move except
when it’s given direct orders. It can recognize only one order: MOVE, which
can be up, down, left or right (like so: MOVE(up)).
Now you can start to move it. It would be easy, right?
However, at this point we start to run into problems. Do we move the mouse up or right? In this particular maze, it’s easy to see that the mouse needs to go right, but we could have substantially more complicated mazes, and we might not even know what the maze is going to be like. For instance, we could write a program that someone else will use on their maze. We need a general strategy.
If you were solving this maze yourself, you’d probably trace the routes with a
pen, and go back to the last
fork in the maze when you run into a dead
end. We can do better than that. Instead of following a route until we reach a
dead end, with a computer we can follow several paths at the same time. How do
we do that? With memory!
In the previous image we had arrived at a fork in the path, and we could go to
the square above or to the square to the right. Instead of choosing one of them,
we could store both squares as possible continuations. Let’s set
aside an area of memory to keep them. For reasons that will soon become
apparent, we’ll call it the
queue. Now comes the smart part: for each
square stored in the queue, we look at all the next possible continuations
reachable from it, and add them to the queue. To be precise, we add them at the
end of the queue (hence the name). Let’s see how this works in practice:
To reiterate: we place a square on the queue, and assign it to be the top of the queue. Then we put its neighboring squares at the end of the queue, and we advance the top to the next square in the queue. We place its neighbors at the end of the queue, advance the top to the next square in the queue, and so on and so forth until we come to the square that contains the cheese.
Kewl, I hear you say, but so what? Well, we’re in essence cloning the mouse
into several little mice who each explore a path. It can be demonstrated
mathematically that the first
cloned mouse to find the cheese will have
taken the least possible number of steps. Nifty, no? Just to make things a bit
more clear, we’re going to add an extra refinement. When we add squares to the
queue, we’re going to label them to indicate how many steps have been taken to
reach them. This is pretty easy: for instance, if the top of the queue has a
5, then when we add its neighbors we will label all of them with
Now let’s look at a full run until the mouse finds the cheese:
As a final step, the path we were looking for can be found by retracing the steps backwards from the cheese in descending order of the labels:
The program to be fed to the computer would be something like:
- Place the initial mouse square on a queue.
- Label it with "0" and make this the top of the queue.
- While the top of the queue is not a square containing cheese:
- Add to the end of the queue the possible continuations from the top square.
- Label them with one plus the label of the top of the queue.
- Advance the top of the queue to the next element in the queue.
- Retrace back from the square with cheese in order of descending labels
And that’s it for mice and cheese. What you have just learned is one of the
basic techniques of Computer Science, called
Breadth-First Search. If
you’ve gotten the hang of it with just one reading, pat yourself on the back.
Hell, pat me on the back too. It’s one of the stumbling blocks for many
beginners in CS, and it is at the base of many other techniques. Queues are in
widespread use in computer programs. Take the ability to
clone mice that
the queue gave us. Think of operating systems like Windows or Mac, which give
you the impression that the computer is doing several things at once, or that
there are several little cloned machines, one for email, one for Word, etc.
Well, queues are very important in simulating this multitasking ability. Just
for the record, computers (unless they have dual processors) can do only one
thing at a time, and that is simply to perform a calculation. Isn’t it
wonderful that this seemingly terrible restriction has been so well tackled?
To finish, I’d like to give you my favorite quote about Computer Science, by a famous Dutch computer scientist named Edsger Dijkstra: The question of whether computers can think is like the questions of whether submarines can swim.
If you’re interested, you can’t to better than getting a copy of Structure and Interpretation of Computer Programs, by Harold Abelson and Gerald Jay Sussman, MIT Press 1996