You are on page 1of 445

CS125 : Introduction to Computer Science

Lecture Notes
Spring 2006

2006,
c 2005, 2004, 2003, 2002, 2001, Jason Zych
ii
Part 1 : Programming Basics 1
Lecture 1 : Computer Science and Software Design . . . . . . . . . . . . . . . . . . . . . . 2
Lecture 2 : Architecture and Program Development . . . . . . . . . . . . . . . . . . . . . 9
Lecture 3 : Types, Variables, and Expressions . . . . . . . . . . . . . . . . . . . . . . . . . 20
Lecture 4 : Type Checking, Input/Output, and Programming Style . . . . . . . . . . . . . 31
Lecture 5 : Boolean Expressions, Simple Conditionals, and Statements . . . . . . . . . . . 48
Lecture 6 : Compound Statements, Scope, and Advanced Conditionals . . . . . . . . . . . 56
Lecture 7 : Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Lecture 8 : One-Dimensional Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Lecture 9 : Multi-Dimensional Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Lecture 10 : Processing Data Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101

Part 2 : Programming Methodologies 109


Lecture 11 : Procedural Composition and Abstraction . . . . . . . . . . . . . . . . . . . . 110
Lecture 12 : Method Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
Lecture 13 : Reference Variables and Objects . . . . . . . . . . . . . . . . . . . . . . . . . 142
Lecture 14 : Objects and Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
Lecture 15 : Data Composition and Abstraction: Classes and Instance Variables . . . . . 170
Lecture 16 : Classes, Reference Variables, and null . . . . . . . . . . . . . . . . . . . . . 176
Lecture 17 : Instance Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
Lecture 18 : static versus non-static, and Constructors . . . . . . . . . . . . . . . . . . 210
Lecture 19 : Access Permissions and Encapsulation . . . . . . . . . . . . . . . . . . . . . . 217
Lecture 20 : Copying and Mutability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223

Part 3 : Algorithm Design and Recursion 231


Lecture 21 : Introduction to Algorithm Design and Recursion . . . . . . . . . . . . . . . . 232
Lecture 22 : Subproblems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
Lecture 23 : Picture Recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
Lecture 24 : Recursive Counting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
Lecture 25 : Subarrays – Recursion on Data Collections . . . . . . . . . . . . . . . . . . . 292
Lecture 26 : Searching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
Lecture 27 : Sorting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
Lecture 28 : Tail Recursion and Loop Conversion . . . . . . . . . . . . . . . . . . . . . . . 334
Lecture 29 : Accumulator Recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354
Lecture 30 : More Accumulator Recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . 366

Part 4 : Algorithm Analysis 379


Lectures 31 and 32 : Introduction to Algorithm Analysis . . . . . . . . . . . . . . . . . . . 380
Lecture 33 : Selection Sort Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386
Lecture 34 : Insertion Sort Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392

iii
iv

Lecture 35 : Exponentiation and Searching . . . . . . . . . . . . . . . . . . . . . . . . . . 393


Lectures 36 and 37 : Mergesort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402
Lectures 38 and 39 : Quicksort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 410
Lecture 40 : Advanced Sorting Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . 437
Part 1 : Programming Basics

The study of programming begins with some of the very basic tools needed by any program. Any
computer program is simply a set of instructions that manipulates data, so we need some way to
store data and some way of manipulating it. To begin with, our data values will be very simple
– integers and floating point numbers, for example — and so we want some way to store simple
values like that, and simple operations to manipulate those values (such as arithmetic operations).
We of course want to be able to input data from the user, and print data to the screen as well,
so we will also need to learn how to do those things.
But in addition to simple data storage and arithmetic manipulation, we will also find that to
write meaningful programs, we need a few more tools as well. Our programs will need some way of
making choices (for example, should a program report that a student has passed, or report that a
student has failed), repeating work (if you want to calculate the homework average for one student,
you might want to repeat that same calculation for 299 other students as well), and storing large
amounts of data in a way that makes it easy to manipulate (for example, if you have a roster of
the names of 300 students in a class, you might want to simply say “print all the names on the
roster”, rather than individually saying to print the first name, print the second name, print the
third name, and so on). Once you can store and manipulate data, and can use the above sorts of
tools as well, you will be on your way to writing very interesting and useful programs.
However, prior to learning how to do any programming, it is useful to understand what computer
science is, and where programming fits into that definition. It is also useful to have a vague idea
of how the actual circuitry of a computer stores and manipulates your data, and how the process
of developing a program converts code that you understand into code the computer understands.
So, we will start with those discussions, and armed with that as background, we will then begin to
learn how to write programs in Java.

1
2

Lecture 1 : Computer Science and Software Design


An introductory discussion

Imagine you walk into a room and there are 100 small boxes laid out in a single-file row on the
floor. The lids of the boxes are closed. You are told that either exactly one of the boxes contains
a penny, or else all the boxes are empty. Your task is to determine which box, if any, holds the
penny.
Now, suppose that there isn’t a penny inside any of the 100 boxes. However, you don’t know
that yet; you’ve only just now walked into the room, and you are asked to figure out which box – if
any – holds a penny. How many of the boxes would you have to open before you could be certain
there was no penny in any box? For example, if you open just one box, and it was empty, could
you then be certain there was no penny in any of the 100 boxes? If not, how many boxes would
you have to open before you could be certain that none of the 100 boxes held a penny? (Assume
you can only tell if a penny is in a box, by opening the box and looking inside – i.e. you don’t have
metal detectors or X-ray machines or any such thing.)
Well, since you have not been given any extra information about the boxes, it will be necessary
to look inside every box to see whether or not that box holds a penny. If you only check half the
boxes, the penny could be in the boxes you didn’t check. If you check all but one of the boxes, the
penny could be in that last box you didn’t check. You can only say for certain that no box contains
a penny if you’ve checked every box.
Certainly, if you knew something extra about the boxes, that might help. For example, perhaps
half the boxes are green, and half are red. If you were told that the penny – if there is one – is
in a green box, then you would not need to check the red boxes, since you’ve been told the penny
would not be in a red box. However, you would need to look at every box, since you at least need
to determine if a box is green or red before deciding whether or not you need to bother opening it
to look inside. On the other hand, if you are told that if there is a penny, it would only be in one
of the three boxes closest to the door, then in that case, not only would you not need to look in
most of the boxes, but you wouldn’t even need to look at most of the boxes – you could just look
inside the three boxes closest to the door, and ignore every other box in the room.
But if you are given no such additional information – if it’s possible for the penny to be in any
of the boxes – then you would need to check all of the boxes in order to be sure that there was no
penny in any box.
Now, imagine a second scenario. What if I printed out the name of every single student at the
University of Illinois, in a list that is not alphabetized, and gave that printout to you? Imagine
that I then ask you to search that list to see if there is any student named “Abraham Lincoln” on
the list. Perhaps there is such a student on the list, and perhaps there is not. However, if you only
look at the first page of names, find no one named “Abraham Lincoln”, and then quit, you can’t
be sure there is no such student at the University – maybe there’s a student with that name on one
of the other pages you didn’t check. If you check all the pages except the last one, or if you check
every name on every page except for the last name on the last page, you face a similar problem. As
long as there are names you haven’t looked at yet, one of those names could have been “Abraham
Lincoln” and you’d have no way of knowing. You can only be sure there is no student with that
name if you’ve checked every name.
Now, certainly, if the list is ordered in some manner, that would help. If the names are sorted
alphabetically by last name, for example, you’d only need to check a certain part of the list –
namely, where “Lincoln” would occur in alphabetical order. But if there is no order to the list for
you to take advantage of, then you’d be forced to check every name if you wanted to be sure that
3

no student named “Abraham Lincoln” existed.


Note that the problem of searching boxes for a penny, and the problem of searching a list of
names for a student with a particular name, are very similar problems. In both problems, you have
a number of similar items, in no particular order, and you are searching the collection of items for
one which matches a description exactly. In the first case, you are looking at boxes one by one
to find the box that matches the description “there’s a penny inside”; in the second case, you are
looking at names one by one to find the name that is “Abraham Lincoln”. And in both problems,
if the only information you have is the description you are trying to match, you need to examine
all the items before you can be certain that no item matches the description. However, it is also
true that in both problems, if you had additional information that you could use to rule out some
items without examining them in detail, then it might have saved you some work.
This is computer science!!
Now, that might sound strange, since we have not even discussed computers yet. However, what
we are doing here is conveying an important principle: computer science is not about computers.

Computer Science

Computer Science is the study of computation. (The discipline really should have been called
“Computation Science” from the start, but “Computer Science” got chosen way back when, and
now we’re stuck with that name, even though it’s a little bit misleading.) The study of computation
encompasses a great deal of material. Among the more theoretical aspects of this material is the
study of algorithms. An algorithm is a step-by-step procedure (with a finite number of steps)
that, when followed from beginning to end, solves a particular problem – i.e., performs a specific
computation. Note that these steps should be well-defined steps that can be easily and correctly
followed every time the algorithm is run; for example, you can’t say “Step 5: meditate until the
answer pops into your head” and count that as a legitimate step.
Note that we have basically come up with an algorithm for solving each of our earlier problems,
even though we didn’t state the algorithm in any sort of formal way. Let’s do that now, for the
box-and-penny problem:
4

To determine which of a finite number of boxes, if any, holds a penny:

step 1) Start at the first box in the collection of boxes; call this
your ‘‘current box’’; go to step 2.
step 2) Open the ‘‘current box’’ to see if it holds a penny; if it
does, go to step 6; otherwise go to step 3.
step 3) If your ‘‘current box’’ is the last box in your collection of
boxes, go to step 5, otherwise, go to step 4.
step 4) Consider the next box after your ‘‘current box’’ to be the new
‘‘current box’’, and go back to step 2.
step 5) It is confirmed that no box in the collection of boxes has
a penny inside it, since we have now checked every box
in the collection. Stop.
step 6) You have found the box that contains a penny. Stop.

Note that as you run through this algorithm, step 4 takes you back to step 2. So, if the box does
NOT hold a penny in step 2 (thus leading you to step 3), and if that is NOT your last box in step
3 (thus leading you to step 4), you end up at step 2 again, and might run through the series of
steps 2, 3, and 4 over and over again. You only stop running steps 2, 3, and 4 repeatedly, when
you either find a box with a penny in step 2, or you run out of boxes in step 3. And that should
make sense, given our problem – we only stop looking for a penny, when we’ve found the penny, or
run out of boxes to look at.
Note that this algorithm is very similar to the one we might have used to find “Abraham
Lincoln” on our list of unalphabetized names:

To determine which of a finite number of student names, if any,


is ‘‘Abraham Lincoln’’:

step 1) Start at the first name in the list of names; call this
your ‘‘current name’’; go to step 2.
step 2) Examine the ‘‘current name’’ to see if it is ‘‘Abraham Lincoln’’;
if it is, go to step 6; otherwise go to step 3.
step 3) If your ‘‘current name’’ is the last name in your list of
names, go to step 5, otherwise, go to step 4.
step 4) Consider the next name after your ‘‘current name’’ to be the new
‘‘current name’’, and go back to step 2.
step 5) It is confirmed that no name in the list of names is
‘‘Abraham Lincoln’’, since we have now checked every name
in the list. Stop.
step 6) You have found the name that is ‘‘Abraham Lincoln’’. Stop.

In fact, you could generalize this, and have a procedure that work no matter what collection you
were searching and no matter what particular value you were searching for:
5

To determine which of a finite number of values in a collection C,


matches some description D:
step 1) Start at the first value in the collection; call this your
‘‘current value’’; go to step 2.
step 2) Examine the ‘‘current value’’ to see if it matches your
description; if it does, go to step 6; otherwise go to step 3.
step 3) If your ‘‘current value’’ is the last value in your collection,
go to step 5, otherwise, go to step 4.
step 4) Consider the next value after your ‘‘current value’’ to be the new
‘‘current value’’, and go back to step 2.
step 5) It is confirmed that no value in the collection matches the
description, since we have now checked every value in the
collection. Stop.
step 6) You have found the value that matches your description. Stop.
The above is an algorithm known as “linear search” – and the purpose of the algorithm is to
search a collection of items, one by one, from the first item to the last item, trying to find one that
matches a particular description. As we have seen, it can be applied to searching boxes for a penny,
or searching a list of names for a particular name. It can be applied to many other kinds of specific
problems as well – any time we have a collection and need to search it for a particular item.
We will explore linear search and other kinds of searching, later in the semester. For now,
we simply wanted to present it as one example of an algorithm – a finite, step-by-step procedure
designed to solve a particular problem.

We’ll talk about computers, too

Now, when we said “Computer science is not about computers”, perhaps that was a little bit
inaccurate. Usually, when we discuss algorithms, our intention is to eventually have computers, not
people, run through the algorithms step by step. So, the design of computers, and the programming
of computers, are also aspects of computer science. In fact, you will begin your study of the
programming of computers, in this very course; later CS courses will introduce you to the design
of computers as well.
The important idea behind the earlier discussion, though, was to point out that we can discuss
what work needs to be done, without discussing how that work needs to be done. For example,
based on our earlier discussion, we know that if you are searching through one hundred different
integers, for one that equals 47, then – assuming that none of the integers are equal to 47, even if
you don’t know that yet – you would have to look at all one hundred integers to be certain that
none of them are equal to 47. This is an inherent property of the problem. Even if you search
through the collection of integers using a computer, the computer program you write will still have
to look through all one hundred different integers. If you buy a computer tomorrow that is twice
as fast, that doesn’t mean you suddenly only need to look at fifty integers – because looking at
every single integer is an inherent property of the computation itself, and has nothing at all to
do with the speed of the computer or person doing that computation. If in the future, you buy a
computer 100 times as fast as the one you use today, that computer will still need to look at all one
hundred integers. A faster computer can only perform each individual step of an algorithm faster;
it cannot eliminate any of the steps of the algorithm, since every one of those steps still needs to
6

be performed in order for the algorithm to be run correctly. If your fast computer only looks at
the first ninety-nine integers, the last integer could have been 47 and neither you or the program
would have any way of knowing for sure – except by continuing onward and inspecting that final
integer out of the one hundred integers.
There are many other sorts of advanced theoretical issues that can be discussed as well – and you
will discuss many of them in later courses! CS125 is meant to introduce you to some introductory
theoretical issues, as well as giving you some beginning instruction in how to program computers
and design software. The important thing to realize, here, is that – though programming is part of
computer science – a proper exposure to computer science involves more than just programming,
and though we will not touch on every aspect of computer science in this course, we will indeed be
doing more than just teaching you how to program in the Java language. We will be touching on
some more general computer science issues as well – among them, algorithm design and analysis,
and some program design techniques. All of these things are part of the discipline of computer
science.

Conceptual Tools

In the coming lectures, you will learn about various computational ideas, and the particular Java
syntax that you use to express those ideas in a Java program. Other programming languages might
express those ideas with different syntax than Java uses, just like different spoken languages would
have different words for the concept of “water” or “computer”. However, just as the idea of “water”
is the same whether the word you use to represent the idea is the English word “water”, or the
Spanish word for that concept, or the Japanese word for that concept, likewise, the computational
ideas we will learn will will be the same, regardless of what language we happen to be using to
express those ideas. In this course, we will use Java to express our ideas, and if this is the first
time you’ve done any programming, it might be easy to think we are learning Java ideas instead of
general programming ideas. That is not the case, though. We are learning general programming
ideas which you will find in many different programming languages, and the particular symbols
which represent those ideas in Java, won’t necessarily be the same as the particular symbols that
represent those ideas in other languages.
However, all programs basically consist of (1) obtaining data and (2) running instructions that
manipulate that data. In that respect, there are three kinds of conceptual ideas we can introduce
right now, which you will make heavy use of as you design programs throughout the semester:

1. primitives – these are the lowest-level data values and procedures provided by a programming
language. Primitives are not things you need to define, since they are built right into the
language; similarly, they can’t really be broken down into smaller parts without losing their
meaning. (The concept is similar to that of an “atom” in chemistry or physics class.)

• When talking about data, the primitives are the built-in data values that your program
can use without any further definition. For example, you can type numbers such as 5
or -2 or 38.6 directly into your Java program; the language is designed to understand
numerical values like that automatically. Similarly, characters such as a lowercase ‘h’ or
the dollar sign ‘$’, are understood by the language.
• When talking about procedures, the primitives are small, basic operations such as simple
arithmetic. Adding two numbers, or multiplying two numbers, would be considered
7

primitive operations – they are built right into the language and you can use them
without any further definition.

2. composition – this is the idea of taking smaller ideas, and putting them together to build a
larger idea which is effectively just the collection of the smaller ideas

• When we talk about data composition, we mean building larger pieces of information
by collecting together smaller pieces of information. For example, when you take your
name, address, social security number, GPA, and the names of the classes you took and
the grades you got in those classes, and put all that information together, you have
your personal record in the student database. In this case, the chunk of data known
as a “student record”, is in reality composed of many smaller pieces of data, such as
your name, GPA, and so on. The student record is a composition of other, smaller data
values, which together form a larger, more complex piece of data.
• When we talk about procedural composition, we mean building larger procedures out of
smaller ones. For example, it is possible to add two numbers together, and it is possible
to divide a number by 3. So let’s apply that first procedure (addition) twice, and then
apply the second procedure (division by 3) after that. Given three numbers:
(a) add the first two numbers together
(b) add the sum from the previous step, to the third number
(c) divide the sum from the previous step, by 3
Now, in that case, we’ve applied some small, primitive procedures – addition and division.
However, the result of the last step will be the average of the three numbers we were
given. So, what we have really done above is to create a procedure for finding the average
of three numbers. By combining some primitive mathematical operations – addition and
division – we were able to produce a procedure that is slightly more complex. This more
complex procedure is a composition of other, smaller procedures that together form a
more larger, more complex procedure.

3. abstraction – this is the idea of focusing on the “big picture” of an idea, and ignoring the
details that implement that big picture. The advantage to doing this is that worrying about
those details often bogs us down in a lot of little issues that we really don’t need to worry
about. When you speak into a telephone, you might know that the phone is encoding sound
waves as electrical signals, but you don’t really worry too much about that when you speak
into a phone, and certainly you don’t perform that encoding on your own! You simply trust
that the phone will perform the encoding properly, forget about it, and just pick up the phone
and speak. You worry about the overall purpose of the phone, rather than the details of how
that purpose is achieved. Certainly, someone or something has to handle those details, but
that someone or something doesn’t have to be you.
8

We make use of this idea when writing computer programs, as well:

• When we talk about data abstraction, we are referring to the idea of viewing a data
composition in terms of what the overall collection of data is trying to represent, rather
than focusing on the details of what pieces of data make up the composition. For
example, we might have procedures that “print a student record” or “copy a student
record”; in those cases, we don’t need to worry about sending a lot of little pieces of
data to those procedures; we simply send one piece of data – a student record – and
don’t need to worry that a student record is “really” lots of smaller pieces of data if you
look at it closely.
• When we talk about procedural abstraction, we mean viewing a composition of smaller
procedures in terms of what the overall goal is, rather than viewing it in terms of the
smaller procedures that make up the larger one. For example, once we have written a
procedure to take the average of three values, we can make use of it whenever we have
three values we want the average of. We can send the three values to our procedure, and
get the average back, and we don’t have to worry about the details of how the average
is calculated.

All these ideas fit together to aid us in program design. Quite often, we will often compose a
larger piece of data out of smaller pieces of data, and then focus from then on, on the larger data
concept rather than worrying about what smaller pieces of data form the larger one. Sometimes,
those smaller pieces of data that form the larger piece of data, will be primitives, and sometimes
they will instead be other data compositions we already created earlier. Ultimately, though, if you
break any piece of data down into small enough detail, you will find that it is composed of the
same limited set of data primitives that the language (and processor) supports. We just choose to
focus on the “big picture” when we can, instead of always peeking into a data composition to see
the smaller pieces of data from which it is made.
Similarly, quite often, we will compose a larger procedure out of smaller ones, and then focus
from then on, on the larger procedure and what the overall task is that it accomplishes, rather than
worrying about all the smaller little procedures that form it. Sometimes, the smaller procedures
we use to form the larger procedure, will be primitives, and sometimes they will instead be other
procedural compositions we already created earlier. Ultimately, though, if you break any procedure
down into small enough detail, you will find that it is composed of the same limited set of procedural
primitives that the language (and processor) supports. We just choose to focus on the “big picture”
when we can, instead of always peeking into a procedural composition to see the smaller procedures
from which it is made.
Over the first half of CS125, you will learn about many of the primitives available to you in
the Java programming language, and you will also learn about the syntax available in Java for
dealing with data composition and abstraction, and with procedural composition and abstraction.
In addition, you will see the concept of abstraction in use in other ways as well.
9

Lecture 2 : Architecture and Program Development


Data Encoding

The idea of data encoding is to take information in one form, and translate that information
into an entirely different form, with the intention being that we can translate back to the first form
eventually. We presumably want to translate information to the second form because the second
form is easier to deal with in some way – otherwise, we would just keep the information in the first
form to begin with!
Talking on the phone is a good example of this. Imagine you are in Champaign-Urbana and
you are talking on the phone to someone in California. If you did not have a phone, and just stood
in the middle of the Quad and yelled, the person in California is not going to hear you, no matter
how loud you yell. They are just too far away; the sound generated by your voice can be carried
through the air to the people standing near you, but that sound cannot be carried by the air all
the way to California.
Having a telephone, however, changes this. When you speak, the telephone you are holding
encodes the sounds of your voice into electrical signals. And though sound cannot easily be trans-
ported long distances, electrical signals can be transported long distances, though wires. Of course,
this idea is useless unless your friend in California also has a working phone, since your friend
cannot understand electrical signals! But, assuming there is a phone on the other end of the line to
decode the electrical signals back into sounds, then your friend can hear your voice in California.
The concept here was that sounds were not convenient for sending across long distances, so we
encoded the sounds (our voices) into a form that was more convenient to us – electrical signals – and
then decoded the information back to the first form – sound waves – once the transport was done. If
we think of vocal sounds as information, then when our goal is creating or hearing that information
with our own body, then sound waves are the more useful form, and thus we want the information
in “sound wave” form. But when our goal is transporting the information long distances quickly,
then electrical signals are the more useful form, and thus we want the information in “electrical
signal” form. We can freely convert between the “sound wave” form and the “electrical signal”
form (using the phone hardware), depending on which form of the information is more convenient
at the time.

Encoding data as bits

The concept of data encoding is of tremendous importance in the design of a computer. The
reason is that a computer is nothing more than a big piece of electronic circuitry – effectively, a pile
of electrical switches hooked together by wires. Each of the electrical switches is called a transistor,
and each of them has only two settings or positions – “on”, and “off”. Likewise, any of the wires
in the computer either has electrical current flowing through it at that particular moment, or else
it doesn’t have electrical current flowing through it at that particular moment. Since most of the
time, we don’t want to have to think about the particular circuitry layout on a computer chip, and
what position switches are in and where current is flowing, we instead represent the idea of a single
transistor or the flow of electricity on a single wire, with the idea of a bit.
A bit is a single digit that is always either 1 or 0. We implement a bit in hardware with a
transistor set to either “on” or “off”, respectively, or with an electrical wire that either does (in the
case of a 1), or doesn’t (in the case of a 0), have electrical current flowing through it. But, thinking
10

in terms of transistors and wires is more detail than we really care about in many situations, so
commonly, when thinking about having a collection of transistors, or a row of wires, we will instead
view it as a collection of bits. A bit sequence or bit string is then a collection of bits. For example,
the following:

1111100010111001

is a bit string that is 16 bits long. In the actual computer hardware, it would be represented by 16
transistors in a row, set as follows:

on on on on on off off off on off on on on off off on

or by a row of wires, set up as follows (with CUR meaning there is current on that wire, and NCUR
meaning there is no current on that wire):

CUR CUR CUR CUR CUR NCUR NCUR NCUR CUR NCUR CUR CUR CUR NCUR NCUR CUR

When dealing with computers, everything gets encoded using bit strings. The example on the
left below shows how we might encode a set of 4 integers using 2 bits. If we want to represent the
number 3 in that case, we can do it using two bits, both set to 1 (or in other words, two transistors,
both set to “on”, or two wires, both with current). The example on the right below shows how we
might encode 8 integers using 3 bits. The more bits we have, the larger the set of integers we can
encode; given a bit string length of N , we have 2N different bit strings of that length, and so we
can encode 2N different values (N is 2 in the example on the left below, and 3 in the example on
the right below).

integer bit string encoding integer bit string encoding


------- ------------------- ------- -------------------
0 <--> 00 0 <--> 000
1 <--> 01 1 <--> 001
2 <--> 10 2 <--> 010
3 <--> 11 3 <--> 011
4 <--> 100
5 <--> 101
6 <--> 110
7 <--> 111

Basic computer architecture

Computer architecture is much more complicated than we are describing here, but the simplified
view of things we are presenting is good enough for our purposes. You’ll learn more details in other
courses.
The two components of the computer that we are concerned with understanding are the proces-
sor and memory. The processor is where the circuitry for performing additions, subtractions, and
so on, resides.
11

_____________________________________
| |
| processor (lots of circuitry |
| here that we are not drawing in) |
|___________________________________|

We need the ability to send data into the processor, and receive data out of it (for example, if
you want to add 2 and 3, you need a way to send the encoded values of 2 and 3 into the processor,
plus you need a way to receive the result, the encoded value of 5). In addition, we need to be able
to tell the processor what operation it should be doing (for example, if we tell the processor to add
2 and 3, we get a different result than if we tell the processor to multiply 2 and 3). Therefore, this
is a somewhat-more-accurate model for a processor:

many wires to many wires to


encode one encode a second
input value input value

|| | || |
||...| ||...|
_||___|__________________||___|______
| |
many wires ---| processor (lots of circuitry |
to encode ---| here that we are not drawing in) |
an ...| |
operation ---|___________________________________|
|| |
||...|
|| |

many wires to
encode an
output value

That’s basically all a processor is – a bunch of wires carrrying input, a bunch of wires carrying
output, and circuitry between the input and output wires which manipulates the input signals in
the desired way, to produce the desired output signals. The interesting thing about processors is
how you design that circuitry – how it is that you can wire transistors together to perform addition,
subtraction, etc. on encoded data values. Unfortunately, how to design a processor is beyond the
scope of this course, although you will learn the beginning ideas in CS 231 and CS 232 (or ECE
290 and ECE 291).
The processor cannot store information, though – it can only process the information it is given.
Where does that information come from, that we ultimately give to the processor? Well, that’s
where memory comes in. You can imagine memory as a shelving unit. Imagine there is a room
with some shelving in it, and each each shelf is numbered, starting with zero at the top:
12

___________________________
0 | top shelf
|__________________________ <--- top shelf
1 | second shelf from top
|__________________________
2 | third shelf from top
|__________________________
3 | fourth shelf from top
|__________________________
4 | fifth shelf from top
|__________________________
5 | sixth shelf from top
|__________________________
6 | seventh shelf from top
|__________________________

I could tell you to do things such as “take the item from shelf 3 and move it to shelf 6”, or
“take the item from shelf 4 and throw it away”. Anytime I want you to do something to one of the
items on a shelf, I first tell you what shelf to go to by telling you the shelf number. It would not do
you any good for me to say, “take the item from the shelf and throw it away”, since you wouldn’t
know if I was talking about some item on shelf 0, or shelf 6, or shelf 4, or some other shelf.
You can think of computer memory as if it were a shelving unit, except the “items” we store
on these “shelves” are bit strings – one per “shelf”. The “shelves” are often called memory cells or
memory locations, and the numerical labels on these memory locations are usually called memory
addresses. The addresses of our memory locations start at 0, just as the shelves in our above
example did. (When talking about memory, we’ll use the convention of putting a letter a in front
of the number, just to remind ourselves that it’s an address. In reality, the address is represented
as a bit string in the machine, just as the rest of our data is.)
___________________________
a0 | row of 8 switches
|__________________________
a1 | row of 8 switches
|__________________________
a2 | row of 8 switches
|__________________________
a3 | row of 8 switches
|__________________________
| .
| .
| .
|__________________________
a4094 | row of 8 switches
|__________________________
a4095 | row of 8 switches
|__________________________

In our example above, we have 4096 memory locations (i.e., 4096 numbered shelves), with
13

addresses 0 through 4095. Each memory location holds a row of 8 switches – i.e., it holds one
8-bit-long bit string. The purpose of memory – much like the purpose of a shelving unit – is to be
a storage unit where data can be stored until it is directly needed. At that time, it can be read or
written by the processor. That is, memory basically supports two operations:
1. given an address, obtain the 8-bit bit string stored at that given address, and send it back to
whatever part of the hardware requested that information
2. given an address and an 8-bit bit string, write the given 8-bit bit string into the memory cell
at that given address – meaning that the 8-bit bit string is now stored at that address for as
long as we need it to be
It may be that some data value we are trying to store takes up more than 8 bits. For example,
we might decide to encode 232 different integers as 32-bit bit strings. If we did that, we could not
fit our 32-bit bit string into one memory cell, since each memory cell only holds 8 bits. So, in
such cases, we break our larger bit string into 8-bit pieces and store them in consecutive memory
cells. For example, if your piece of information needed 32 bits to represent, and the storage of
this information started at the memory cell with address a104, then you need 4 memory cells to
store the information, since 32 bits will take up four 8-bit cells. And, since the information storage
starts at a104, your information not only takes up the memory cell at a104, but will also take up
the memory cells at a105, a106, and a107 as well. Those four consecutive memory cells, located
at addresses a104, a105, a106, and a107, would together hold the 32 bits that our bit string
takes up. In general, that is how we store larger chunks of information – we break it up into many
consecutive 8-bit memory cells, which collectively store our larger bit string.
So, the image below is the model of a computer that we will deal with in this class. The assorted
input signals to the processor come (mostly) from the memory itself, and the output signals get
written back into the memory.
address bit string
____________________________
| a0 | row of 8 switches |
|------|-------------------|
_____________________________________ | a1 | row of 8 switches |
| |-----|------|-------------------|
| |-----| a2 | row of 8 switches |
| processor (lots of circuitry |-----|------|-------------------|
| here that we are not drawing in) |-----| . | . |
|___________________________________| | . | . |
| . | . |
|------|-------------------|
|a4095 | row of 8 switches |
|______|___________________|

The stored-program computer

Since bit strings are all a computer understands, we need to make sure the following two things
are true in order for computers to be able to accomplish anything:
14

1. all our data is encoded as bit strings and stored in memory

2. all our instructions to the processor, are encoded as bit strings and stored in memory

That is, not only is our data stored in bit string form, but our instructions to the computer –
our “computer programs” – are also stored in bit string form. That is the essential idea behind a
stored-program computer – that the same hardware that is used to store our data, could also be
used to store our programs instead, because our programs are encoded into bits just as our data is.
So there are two key concepts here, one related to instructions, and one related to data. The
first important idea is that whatever task we want a computer to do, we need to tell it to do that
task, by supplying it with some huge sequence of bits that encodes our instructions to the machine.
That means that writing a program is a matter of coming up with some proper series of instructions
that the processor should run through, and then encoding those instructions into a form such that
the program can be stored in the computer’s memory. Our large sequence of bits, specifying our
program, is broken into 8-bit pieces and stored in consecutive memory cells.
The second important idea is that any given data value is also stored as a sequence of bits. If
our data value is only 8 bits long, it can be stored in one memory cell (since each cell can hold
up to 8 bits). Larger bit strings get broken up into 8-bit pieces and stored in consecutive memory
cells, just like larger bit strings representing programs, were broken up into 8-bit pieces and stored
in consecutive memory cells.
15

Developing a Java program

The following is one of the simplest possible Java programs – all it does is print one line of text
to the screen.

public class HelloWorld


{
public static void main(String[] args)
{
System.out.println("Hello World!");
}
}

The program above is just composed of text characters, just like those you might type into
a word processor if you were writing a term paper. However, one important difference between
writing a computer program and writing a term paper, is that the text of the computer program
must be exact. If you write a term paper, and accidentally type “the” as “teh”, the paper will still
be readable – a human reading the paper can figure out that that is just a typo, and can continue
reading your paper. However, if you have a typo in a computer program, it renders the program
completely incorrect. For example, in the following text, we have forgotten the ‘c’ in “static”:

public class HelloWorld


{
public stati void main(String[] args)
{
System.out.println("Hello World!");
}
}

The program above is not even an actual Java program! The loss of one letter not only means
it’s not the same program as the first one, but in addition, in this case it renders the program not
even legitimate at all. (Not every typo will have that effect, but many will.) Preciseness is very
important when writing computer programs.
Developing a program is a four-step cycle:

1. edit (either write a new program, or change an existing program)

2. compile (encode program in bit string from)

3. run program through tests to see how well it works

4. debug – find errors in the test results and deduce their cause

We will look at each of those four steps in detail now.


16

Step 1 of writing a program is to type the program into a file using a text editor.
A text editor is a program that is similar to a word processor, in that you are allowed to type and
edit text, but provides the sort of features important for program development, rather than the
sort of features important for writing term papers. In particular, text editors generally:

• provide some of the same features as word processors, such as allowing the entering and editing
of text, support for cut-and-paste and search-and-replace, the ability to save and open files,
etc.

• provide some features very useful for programming, such as automatically indenting in com-
plex ways, color-coding certain syntax features, checking to make sure you have a close-
parenthesis for every open parenthesis, etc.

• save the program as plain text – that is to say, the file a text editor saves contains nothing
but the text you typed in, and thus should be conveniently viewable using a different text
editor. This is in contrast to using a word processor, where generally if you save a file in a
word processor, there’s all sorts of fancy extra formatting added to the file, and so either you
have to use that particular word processor to view your file later, or else someone needs to
do a lot of work to enable some other piece of software to read your file.

Some examples of text editors are pico, emacs, xemacs, vi, and vim on Unix, TextEdit and
BBEdit on Mac OS X, and NotePad and TextPad on Windows. In addition, some of those editors
are actually cross-platform; Xemacs, for example, is available on all three platforms. A good text
editor can save you a great deal of time in developing your programs, so it is worth your while to
become proficient in the use of a good text editor! Xemacs is a text editor that we’ll give you some
beginning guidance in how to use, but you are free to both learn more about Xemacs, or to learn a
different text editor instead. At any rate, learning how to use a text editor well is one of the many
situations where your courses will point you in the right direction, but most of the work will be left
up to you. If you want to get through all your computer science courses using only the minimum
amount of information we give you about text editors during the first few weeks of this course, you
can – but you’ll find that spending a bit more time now and again learning more features of your
chosen text editor can save you a great deal of time in the future.
Once you type in your program text and save the file, that file is known as a source file or source
code file, and the text inside that file is known as source code. In Java, we will name our source
code files with the .java suffix, so our files will have names such as Foo.java or Spacing.java
or Test.java. Our first program above would go into a file named HelloWorld.java, because the
name on the first line after public class is HelloWorld. (We’ll talk about the issue of file names
in the next lecture packet.)

Step 2 of writing any program is to compile the source code for the program. That is,
the next step is to encode your source code into bit string form so that the machine can understand
it. The process of encoding your source code into bit string form is known as compiling and the
software that does this encoding for you is called a compiler. This step is necessary because the
source code is meaningless to the actual hardware. As we discussed before, your computer hardware
is nothing but a collection of transistors and wires, and so you need to convert your information
(the program you’ve written) into a form that the hardware can make sense of – namely, bit strings.
In Java, the encoded file has a .class suffix and otherwise has the same name as the source
code file. For example, if you sent the file HelloWorld.java as input to the Java compiler, the
17

output would be a file named HelloWorld.class, and it is this file that would contain the encoded
version of your program.
The compiler’s job is actually more than just encoding your program. This is because the
compiler can only encode actual Java programs. If you sent the compiler the following

SDFSDF SWEasasdf 9745972348963qq anczdciero283 2je9 23 83e 123 q

then the compiler can’t produce an encoded version of that, since it isn’t a Java program to begin
with – it’s just a bunch of random characters. There are particular rules about what is and what
isn’t a Java program – our first “hello world” example earlier is indeed a Java program, and the
random garbage above is clearly NOT a legal program...but the second “hello world” example, the
one with the typo, isn’t a legal program either, although it is closer to being a legal program than
the above nonsense is.
What we are getting at here is that programming languages are different than natural languages.
Natural languages are languages that people speak – languages such as English, Spanish, Japanese,
and so on. We as people understand natural languages, so you might think they’d be ideal for writ-
ing programs in, but unfortunately, natural languages are not precise enough – the same sentence
can have multiple meanings depending on context, the tone of voice it is spoken in, etc. Whereas
when we specify a set of instructions to the computer, we don’t want the computer to assume a
different meaning than we intended. Therefore, we have very exact specifications for programming
languages, so that it is very clear what is a legal program in that language and what isn’t a legal
program, as well as exactly what each legal program should do when it is run.
You can imagine a spectrum of possibilities:

<--------------------------------------------------------->
| | |
natural high-level bit strings
languages languages

The far right end of the spectrum is where we have instructions encoded as bit strings – the only
language the machine actually understands (for this reason, the language of bit strings that have
meaning to the machine is also known as machine language). The far left end of the spectrum is the
collection of languages we speak, which are very understandable to us but not precise enough for
usage as programming languages. Close to natural languages, but not quite so far to the left, are the
languages in which most modern program development is done, and these programming languages
are called high-level languages because of how much their syntax resembles natural languages instead
of bit strings. These languages are closer to our level of speech, than bit strings would be, and
that makes it much easier to develop programs in a high level language than it would be to develop
programs by writing out bit string after bit string from scratch.
So, getting back to the compiler, if you send a file of legal source code to the compiler, the
compiler can produce the encoded version of that source code...but if you send a file of source code
that is NOT a legal program for that language, then the compiler cannot produce any encoded
version of your program, since you haven’t actually given the compiler a real program to begin
with. However, what the compiler will do for you in this case, is to tell you what areas of your
program violate the syntax rules of the language – that is, the compiler will tell you what lines
had errors, and what it thinks those errors are. (For example, if the earlier “hello world” program
with the one typo was sent in as input to the Java compiler, the Java compiler might tell us that
there is an error on line 3, and furthermore, would tell you that the word “stati” on that line is
not something the compiler can make any sense of.)
18

These kinds of errors – where a program is written that does not conform to the specification of
the language – are called syntax errors, and the way you fix them is by changing your source code
so that what you have written does conform to the specification of the language. The compiler’s
job is to tell you what syntax errors you have in your source code, and, if you don’t have any syntax
errors, to produce an encoded version of your source code.

Step 3 of program development is to run your program. At this point, you have an
encoded version of your source code – that is, you’ve translated your source code so that it is in
a form the machine can understand. However, there are many different kinds of machines! Some
of these machines are similar to each other – for example, a computer built around the newest
Pentium processor (which is the processor most commonly used in Windows machines), probably
isn’t all that different from a computer built around last year’s Pentium processor, and so the
encoding for the two machines is probably the same, or at least, very very similar. On the other
hand, the difference between a computer built around a Pentium processor, and a computer built
around a PowerPC processor (which is the processor used in Macintosh machines), is somewhat
more significant. Because those processors are so different from each other, the encoding you use
for one machine is different than the encoding you use for the other machine.
This is a big part of why software you can buy at the store for a Windows machine, probably
doesn’t run on a Macintosh, and vice-versa. If I were to write a story in English, and translate it
to Spanish, someone who only understands Japanese isn’t going to understand my original story,
nor will this person understand the Spanish translation. Now, I could go back to my original story
and create a second translation if I wanted – a translation to Japanese – but if I’ve only done the
one translation, to Spanish, then anyone who can’t understand the original English version and
who also can’t understand the Spanish translation, cannot read the story. Similarly. you can have
the compiler translate your source code to run on a Pentium processor, but a PowerPC processor
will not be able to make sense of the Pentium translation. You can run a second translation,
and translate your source code to the PowerPC machine code, if you want, but if all you’ve done
is the Pentium translation, then a PowerPC processor cannot run your program, since it doesn’t
understand the original source code, and it doesn’t understand the encoding for a Pentium.
There’s another option, as well – it is possible you could make up a machine – imagine a machine
that nobody has built yet – and then translate your source code into the machine code for that
machine. In that case, the machine code you end up with, would not run on a Pentium, nor would
it run on a PowerPC. It is designed to run on this other processor which you dreamed up but
which no one has built yet. Why would you do this? Well, the difficult part about performing
an encoding, is going from the high level language, to some machine language. Once you have a
machine language version of a program, translating that to a different machine language, is relatively
easy by comparison. So, you could do the hard work of translating your code for a machine – your
imaginary machine, or virtual machine – and then put the code out on the internet. People who
want your program, could then download it, and would only have a small amount of work to have to
do – they’d need to translate your encoding, to the encoding for their own processor. For example,
if your computer had a Pentium processor, you could download my encoding for an imaginary
machine, and then further translate that for your Pentium machine. Since the translation from one
machine code to another is not hard, your task is not hard.
This is the Java model. To run a Java program, you will have to run a separate program –
the Java virtual machine – which is a piece of software that (among other things) translates the
encoded .class file into a form that can run on the actual machine you are on. The advantage to
19

this process is that it makes the .class files somewhat portable – they can be run on any computer,
as long as that computer has virtual machine software installed. If you write a program in Java, you
don’t need to release one version for the Pentium processor, and another version for the PowerPC
processor. You can simply provide the .class files, and they should run on any processor that has
a virtual machine available. The downside is that running your program can take a bit longer, since
the virtual machine is performing the additional translation as your program runs. If speed is your
top priority, therefore, Java might end up not being the best language to use...but if portability is
a higher priority, and you can afford to have things slow down a little, then the Java model might
be a useful one for you. (This entire discussion has been simplified a bit, but that’s the basic idea.)

Step 4 of program development is to debug your program (if necessary) and then
return to step 1.
Once you have a program and have translated it to machine language, you then can run the
program, as we just discussed. However, the machine might not do what you want. Certainly, the
machine will do what your program tells it to do, but if you’ve specified the wrong sequence of
instructions in your program, then of course the “wrong” things will be done. These are known
as logic errors – when the actual program you’ve written doesn’t actually do what you intended
the program to do, and so you therefore have to change the program to one that does do what you
want it to do. That is, you need to figure out where you made an error in your chosen sequence of
instructions, and then correct that error. This is generally not easy to do – it’s the main difficulty
in program development. When software you use crashes, or makes some other sort of mistake, it’s
because the programmer of that software made some logic errors that were not found and fixed
before the program was made publicly available for people to use.
The debugging cycle – the cycle for fixing logic errors – is basically the same as the cycle for
fixing syntax errors. That is, you make what you think are the correct changes to your program to
get it to work correctly, and then you re-translate using the compiler and try to run the program
again. You repeat this cycle until the program works correctly – or at least, until you believe it
works correctly. Beginning programmers often take a haphazard approach to this cycle. They
change any random thing in the hopes that it will work, and then try again. Don’t do this!. You
want to reason through your errors. Look carefully at your program code (the high-level language
instructions you have written). Look carefully at the output your program is producing. And try
and understand why the program is producing that incorrect output instead of the output you want
it to produce. Once you’ve figured out why the program is doing what it is doing, you will then
know what needs to be done to fix the program.
20

Lecture 3 : Types, Variables, and Expressions


The “program skeleton”

During the last lecture, we saw this simple Java program:

public class HelloWorld


{
public static void main(String[] args)
{
System.out.println("Hello World!");
}
}

We’re not going to go into any detail right now on what most of the above code means. It’s
not important (yet). So don’t worry about trying to understand that code. However, you do want
to have everything but the line System.out.println("Hello World!"); in every program you
write. That is, you want to make sure you have the following “program skeleton” when you write
a program:

public class ProgramName


{
public static void main(String[] args)
{
(the stuff you learn these next
few weeks would go here)
}
}

Basically, you can copy those six lines – four of them are curly braces – into your file, and then
in between the inner set of curly braces, you can add the stuff we talk during the next few lectures.
Until we start talking about methods in lecture 11, the only thing in the “program skeleton” above
that will ever change is the ProgramName. The rest of it stays exactly the same, and all you will
do is write different code within those inner curly braces.
21

Variables

A variable is a name which refers to a value. You’ve seen this concept in math class before, where
you would be given equations such as y = 3x + 5 and then various values could be substituted for
x or y. It’s the same sort of idea in computer programs, except that in addition to one-character
names like x or y, our variable names can also be longer names such as totalProfits, exam1,
exam2, or numStudents.
One useful way to think of a variable is to imagine it as a box with a label. If we have a variable
named exam1, then think of it like a box labelled “exam1”:

_________________
| |
exam1-----| |
|_______________|

and then various values could be stored in that box, such as an integer:

_________________
| |
exam1-----| 86 |
|_______________|

or a floating-point value:

_________________
| |
exam1-----| 80.3 |
|_______________|

or an individual character:

_________________
| |
exam1-----| B |
|_______________|

or an entire word:

_________________
| |
exam1-----| passed |
|_______________|

A variable in a computer program can only hold one value at a time, but sometimes that variable
is very simple (such as a single character) and sometimes it is more complex (such as a word, made
up of many characters).
22

Types

One problem with the concept of variables is that the variable might hold a value that doesn’t
make sense, given how we are trying to use that variable. For example, if you were using the
equation y = 3x + 5 in math class, and you were told that x was the word “hello”, that would
not make any sense! You are expecting x and y to stand for numbers, not words. If x is the word
“hello”, well, how on earth do you multiply “hello” by 3 and then add 5 to it? The idea is silly.
So, to prevent this sort of problem from happening, some computer languages require the
programmer to associate a type with each variable. A type is the “kind” of data that variable is
allowed to hold. The programmer will have to decide in advance, “this variable will hold only
integers” or “this variable will hold only floating-point numbers” or “this variable will hold only
words”. The compiler is then responsible for making you stick to that decision. For example, if
you decided a variable would hold only integers, and then tried to store a word in that variable, the
compiler would complain that your variable type and your value type don’t match (this is sometimes
referred to as a “type mismatch”). Your program, therefore, would not correctly compile until you
fixed this problem.
Java is such a language. In Java, every variable has a particular type, and you must decide
what that type will be before you can use the variable in the rest of your program. So, before we
go any further, we need to learn what types of data we can have our variables store. The types
you are about to learn are specific to Java, but other languages have their own types that might
be similar or identical, or might be different.
There are 8 essential types in all...called the 8 “primitive” types. (Later in the semester, we
will design our own, more complicated types, but those types will use the eight primitive types as
building blocks.)

Integral types

• Four types: byte, short, int, and long

• These four types all refer to integers, but have different size restrictions.

• A byte takes up 8 bits of memory, and as a result can be one of exactly 256 different values.
(28 = 256.) The actual range is -128 through 127.

• A short takes up 16 bits, and thus can be one of exactly 65536 different values. (216 = 65536.)
The actual range is -32768 through 32767.

• Likewise, int (by far the most common) takes up 32 bits, and long is 64 bits. And the larger
the type gets, the larger the range of values it represents

• Trade-off here! – If you want a larger range of values, you must sacrifice by using more
memory. If you want to save memory, you must sacrifice by allowing yourself a smaller range
of values.

• But in this course, we will basically stick with int.


23

The concept of a “literal” for a type

• We’ll use the word literal to refer to the symbols you type directly into your program’s code
in order to indicate certain values.

• If you were to type 2 + 3 into your program’s code, then the 2 and 3 you typed into your
program are literals in this case.

• Integers you type directly into your program’s code are literals of type int, rather than short,
or byte, or long. That is one reason we tend to use int in this class rather than the other
three integral types – it makes your life easier for now.

• Examples include 2, -56, 3451, -957, and so on.

Floating-point types

• Two types: float and double

• These two types both refer to floating-point values, but of different size limits, such as the
four different integral types had different size limits.

• A float uses up 32 bits of memory; a double uses up 64 bits of memory.

• Again, just as with integers, the larger the type gets, the larger the range of values it repre-
sents. Floating-point values, however, can have a larger range in two different ways:

– They can have a larger magnitude (i.e. size of value).


– They can have greater precision (i.e. number of significant digits).

Values of type double can have both greater magnitude and greater precision than values of
type float. So, we have the same same trade-off here as we did with integers! – a gain in
value range results in using up more memory, and saving memory results in having a smaller
value range available.

• Floating-point literals that you type into your program’s code – symbols such as 98.6,
3.14159,
-88420.22491, and 0.000004556 – are literals of type double.

• In this course, we will basically stick with double, partly because the fact that literals are
automatically of type double makes that type easier to deal with than float would be.

• One note of caution about floating point numbers – any numbers you get as a result of
calculations are generally slighly imprecise, since computers need to round calculations just
as you do. (For example, the square root of two is an irrational number and thus has an
infinite number of digits; you need to round off somewhere.) As a result, often a floating-
point result could be different than you expect in, say, the 12th significant digit. For that
reason, you should NEVER compare two floating point numbers directly to see if they are
equal; instead, you should be concerned with whether they are within, say, 0.00000000004 of
each other (or within some other distance of each other). The syntax you learn in lecture
packets 5 and 6 can help you with this.
24

char

• The values of type char are single characters.

• Literals of type char are single characters that appear within single quotes. For example:
’A’, ’c’, ’$’, ’)’.

• A value of type char takes up 16 bits. That gives us the ability to store one of 65536 different
characters. Why would need the ability to represent so many different characters, when your
keyboard only has a hundred or so characters?

• The answer to that is: Java’s character type is designed for international use – it can hold
characters of many different languages, using a “international character to 16-bit bit string”
translation standard known as Unicode.

• But, don’t worry about Unicode right now. Just worry about putting single characters in
single quotes as above.

• Warning!! When you are dealing with character literals in a program, don’t forget the single
quotes! You want to type ’c’, not c, to indicate the literal character. If you leave off the
single quotes it means something different (which we will explain shortly).

boolean

• There are only two values of the boolean type. The literals representing those values are
true and false.

• Useful in situations where there are only two possibile results (ex: a certain part of the
program either has completed its work or has not completed its work; a certain student either
graduates or doesn’t.)

• Needs exactly one bit of space – because we can simply say that the bit equalling 1 (i.e. the
switch being on) inside the machine is equivalent to the boolean value true, and the bit being
0 (i.e. the switch being off) is equivalent to the boolean value false.
25

Summary of Type Information

size
type in bits values
----- -------- ---------
boolean 1 true or false

char 16 ’\u0000’ to ’\uFFFF’

byte 8 -128 to 127


i.e. -(2^7) to (2^7) -1

short 16 -32768 to 32767


i.e. -(2^15) to (2^15) - 1

int 32 -2^31 to (2^31) - 1

long 64 -2^(63) to (2^63) - 1

float 32 -3.40292347E38 to
3.40292347E38

double 64 -1.79769313486231570E308 to
1.79769313486231570E308

Values in the machine

As we have previously discussed, all our data is really stored as bit strings in the computer’s
memory cells. In addition, we mentioned that a value that needs more than one cell will use
consecutive cells. For example, a value of type int is 32 bits, which means it needs four 8-bit cells
to hold all 32 bits. If the first 8 of those bits are stored at, say, address a48, then the next 8 bits
are at address a49, the next 8 bits are at address a50, and the last 8 bits are in address a51.
Variables are abstractions of this idea – they allow us to think of our value as an item in a
“labelled box”, rather than thinking of it as a bit string spread across one or more consecutive
memory cells. For that reason, thinking of how data is stored in the machine is not generally
something you’ll have to do – you can simply use the abstraction provided by variables, and let the
environment (compiler, operating system, etc.) handle for you the particulars of where a variable’s
value should be stored in memory.
26

Variable Declaration Statements

• In Java, each variable can only hold one particular type of value, so before you can use a
variable, you must announce which type the variable is supposed to hold.
• The statement we use to do this is known as a variable declaration statement.
• Format is: typeName variableName;
• Some examples:

int x;

char selectionLetter;

double temperature;

boolean isCompletedYet;

• Now x can only hold int values, selectionLetter can only hold char values, and so on.
• Note that we put single quotes around a characater literal – such as ’x’ – to distinguish it
from a variable with that character as a name (such as x above).

Variable Assignment Statements

• Writing a value to a variable is known as assignment.


• Format of the statement is: variableName = value;
• Some examples, using the variables declared on the previous slide:

x = 2;
selectionLetter = ’c’;
temperature = 98.6;
isCompletedYet = false;

• The very first assignment to a variable is known as initialization because it is the initial
assignment.
• For example:

int myVal;
myVal = 7; <--- this assigment is
an initializaton
myVal = 3; <--- the 7 is replaced by 3,
this assignment is NOT an
initialization

• You can declare and initalize on the same line. For example:

int exam1 = 93;


27

Operators – (some of) the “verbs” of a computer language

Operators are symbols that indicate some kind of action is to be performed. These are usually
very simple actions, such as:

• + : addition

• - : subtraction

• * : multiplication

• / : division

• % : modulus

• ++ : increment

• -- : decrement

• = : assignment

• () : parenthesis

One symbol can mean different things in different contexts. This context depends on the type
of the operands. For example, a + b is compiled to a different collection of machine language
instructions depending on whether a and b are floating-point types, or integer types. In fact, a and
b could be different types...and the language needs to have a rule about what happens in that case
as well.
Operators have precedence – certain operators get evaluated before other operators. This is
similar to algebra, where multiplication and division were performed left to right first, and once all
the multiplications and divisions were completed, then addition and subtraction were performed,
again from left to right. As in mathematics, the parenthesis have the highest precedence, so if you
have an addition and a multiplication, and the addition is in parenthesis, then the addition comes
first, even though the multiplication would be done first if the parenthesis were not there. For
example, the expression 5 * (2 + 3) equals 25, even though the expression 5 * 2 + 3 equals 13
and the expression 3 + 2 * 5 equals 13.
The ++ operator performs a very common operation – it adds 1 to the value of a variable. For
example, if the variable i held the value 5, then the expression i++ would raise the value of i to 6.
Effectively, it performs an addition and an assignment together – adding one to the value inside i,
and then storing that new sum back into i. Similarly, the -- operator subtracts 1 from the value
of a variable.
We will refer collectively to the addition, subtraction, multiplication, division, modulus, incre-
ment, and decrement operators (all the operators in the above list except the assignment operator
and the parenthesis) as arithmetic operators, because they help us perform arithmetic. We will be
exploring more operators in lecture packet 5, and any Java reference book probably has a full list
of the operators and their precedence rules. Also, our course web page has a link to a website with
that information.
28

Expressions – the “phrases” of a computer language.

An expression is a code segment that can be evaluated to produce a single value.


Expressions are not always complete units of work – in the cases where they are not, they are
used in code segments that are complete units of work – and those code segments are generally
trying to use the value that the expression evaluates to.

Expression examples:

• 6

• 2 + 2

• exam1

• slope * x + intercept

• 5280 * numberOfMiles

• total/numScores

• a + b - c + d - e

The language itself defines many kinds of expressions in terms of other expressions. Meaning,
if we make a list of what sorts of things constitute an expression, we could end up with a list much
like the following:

• a literal

• a variable

• (some arithmetic expression) + (some other arithmetic expression)

• (some arithmetic expression) - (some other arithmetic expression)

• (some arithmetic expression) * (some other arithmetic expression)

• (some arithmetic expression) / (some other arithmetic expression)

• ...and so on...

In the list above, “arithmetic expression” refers to any expression that evaluates to a numerical type
– i.e. to an int or double (or any of the other four numerical types that we won’t use in CS125).
For example, since 5 is a literal of type int, and so is 7, both count as expressions, since as the
above list indicates, literals are expressions (they evaluate to single values). However, that means
5 + 7 is also an expression, since you have two arithmetic expressions (5 being the first one, and 7
being the second one) separated by a + operator. And since 3 is also a literal of type int and thus
also an expression, then since 5 + 7 is an arithmetic expression, and 3 is an arithmetic expresssion,
5 + 7 - 3 is also an expression. And so on – you can use the fact that some expressions on the
list above are defined in terms of other, smaller expressions, to create large expressions of aribtrary
complexity. And each of them reduces to a single value – that’s what makes them expressions!
29

A computer programming language (such as Java) doesn’t need to have a rule for every single
expression you might come up with. It only needs to provide the building blocks to let you create
your own expressions, and primitives to start those rules off. In our list of possible expressions, the
literals and variables are primitives, since those are very simple expressions that can’t be broken
down further. And then, a rule such as

(some arithmetic expression) + (some other arithmetic expression)

allows us to create a composition – we can create a larger expression out of smaller pieces. Then,
that composition is itself an expression, and can be included in still-larger compositions. Every
larger expression we create in this manner, is composed of smaller expressions (which are possibly
primitive expressions – variables or literals) – and yet is itself still an expression, and thus is
something that can be reduced to a single value.

Statements – the “sentences” of a computer language

A statement is a code segment that produces a complete unit of work. (I know that is a rather
vague definition; as we learn more Java syntax, the concept will become clearer.)
We have already seen variable declaration statements and assignment statements. We said ear-
lier that an assignment statement will have a value on its right-hand side; that value is actually an
expression – and thus we could describe the syntax of an assignment statement as,
variableName = expr;, where expr is some expression that evaluates to a single value of the ap-
propriate type. That value then gets stored in the variable on the left-hand side of the assignment
statement.

Statement examples:

• c = 2 + 2;

• x = 5;

• y = slope * x + intercept;

• int b;

• numberOfFeet = 5280 * numberOfMiles;

We will be looking at many other kinds of statements besides declaration and assignment state-
ments. Statements are the building blocks of a computer program - you list them one after the
other, and they are run one by one until the program has completed.
30

So, we now know what types are, and we can declare variables of particular types and assign
values to those variables, and we know how such statements should fit inside a Java program.
So, as the final example for this packet, note the program below, in which we have taken some
variable declarations and variable assignments and expressions involving the arithmetic operators,
and placed them within a new program skeleton. Granted, the program below doesn’t do too much
that is interesting, but it will compile and it will run (producing no output whatsoever). In the
next lecture packet, we will give you the abilty to input values from the user into your program,
to use in your calculations, and we will give you the ability to print values (such as the results of
your calculations) to the screen.

public class Example


{
public static void main(String[] args)
{
int x, y, z;
char selectionLetter;
double temperature = 98.6;

x = 2;
selectionLetter = ’c’;

x = x + 3;
y = x * 2;
z = (x + y)/2;
boolean isCompletedYet;
isCompletedYet = false;

x = 5;
x = 0;
int w = (2 * x) + (3 * y) + (y * z * 4);
isCompletedYet = true;
temperature = 44.5 + temperature;
}
}
31

Lecture 4 : Type Checking, Input/Output, and Programming Style


Type Checking

The various operations in your program (up to now, you’ve only learned about arithmetic
operations, so you can imagine we are only talking about numerical calculations for now, but the
same idea applies to other operations you will learn later) are performed by the machine when
your program runs, NOT by the compiler when your program is compiled. Part of the reason for
this is that, often, those operations would involve values inputted by the user, and the compiler
would therefore not know those input values since the user would not enter values as input until the
program is being run. However, as we mentioned earlier, the compiler does generate the machine
code that will tell the processor to perform those calculations. In addition to that task, the compiler
makes sure that the way in which we use our variables and values is legal – in part, by performing
type checking, confirming that variable types match value types whenever they are supposed to.
We can do this type checking ourselves by replacing our variables, values, and expressions
with their types, thus obtaining what we will call type expressions (as seen in the explanations
that accompany the code in the next five examples). We can double-check that we are using
types correctly by working through these type expressions to see what type of values our code
expressions produce. The compiler does essentially the same thing when type-checking your code
during compilation, so the next five examples below serve two purposes – one is to explain how you
could reason through your own code to see what it does, and the second is to explain a bit about
how the compiler does its job.

Example 1 – integer calculations

public class Example1


{
public static void main(String[] args)
{
int weightedQuantity;
int exam1, exam2;

exam1 = 60;
exam2 = 70;
weightedQuantity = 20 * exam1 + 80 * exam2;

// weighedQuantity is 6800 when program runs


}
}

• 20 * exam1 is int * int which is an int

• 80 * exam2 is int * int which is an int

• The addition is int + int which is an int

• The assignment is int = int, which is okay.


32

Example 2 - a slight problem with division

public class Example2


{
public static void main(String[] args)
{
int sum, average;
int exam1, exam2;

exam1 = 75;
exam2 = 90;
sum = exam1 + exam2;
average = sum/2;

// When program runs, average is 82, not 82.5


}
}

• exam1 + exam2 is int + int which is an int

• The assignment to sum is int = int, which is okay.

• The division is int/int which is an int – result is truncated!!

• The assignment is int = int, which is okay. Result: 82


33

Example 3 - change average to double

public class Example3


{
public static void main(String[] args)
{
int sum;
double average;
int exam1, exam2;

exam1 = 75;
exam2 = 90;
sum = exam1 + exam2;
average = sum/2;

// When program runs, average is 82.0, not 82.5.


}
}

• exam1 + exam2 is int + int which is an int

• The assignment to sum is int = int, which is okay.

• The division is int/int which is an int – result is truncated!!

• The assignment is double = int, which is okay. The int is automatically converted to a
double...which is why we get 82.0 and not 82.
34

Example 4 – fix #1: change sum to double

public class Example4


{
public static void main(String[] args)
{
double sum;
double average;
int exam1, exam2;

exam1 = 75;
exam2 = 90;
sum = exam1 + exam2;
average = sum/2;

// average is 82.5 when program runs


}
}

• exam1 + exam2 is int + int which is an int

• The assignment to sum is double = int, which is okay. sum now holds not 165, but rather,
165.0.

• The division is double/int which is a double – finally, result is not truncated!!

• The assignment is double = double, which is okay. The result: 82.5


35

Example 5 – fix #2: change int literal to double literal

public class Example5


{
public static void main(String[] args)
{
int sum;
double average;
int exam1, exam2;

exam1 = 75;
exam2 = 90;
sum = exam1 + exam2;
average = sum/2.0;

// average is 82.5 when program runs


}
}

• exam1 + exam2 is int + int which is an int

• The assignment to sum is int = int, which is okay.

• The division is int/double which is a double – again, result is not truncated!!

• The assignment is double = double, which is okay. The result: 82.5

The rule is that, if both operands of an addition, subtraction, multiplication, or division, are of
type int, then the result will be of type int – and as we have seen, for division, that means that
the fractional portion is truncated. However, if one or both of the operands is a double instead of
an int, then the result will be a double (and thus division would not be truncated).
36

Outputting text using System.out.println

Recall our HelloWorld program:

public class HelloWorld


{
public static void main(String[] args)
{
System.out.println("Hello World!");
}
}

• The code "Hello World!" in the code above is a literal of type String – the first non-
primitive type we have encountered. We won’t talk about variables of type String for a
while yet, but we will use literals of type String. Any text that appears in between double
quotes is a literal of type String.

• The general form is System.out.println(expr); where expr is some expression. The ex-
pression is evaluated, and the result is then printed to the screen.

• If you have only double-quoted text inside the parenthesis, then since that is a String literal,
the expression in parenthesis is of type String.

Output of variables

In the previous example, the item we were sending to the screen was literal text. However, it is
also possible to print out the values of our variables.

public class Example6


{
public static void main(String[] args)
{
int exam1, exam2, sum;

exam1 = 60;
exam2 = 70;
sum = exam1 + exam2;

System.out.println(sum);
}
}

In the code above, the expression in parenthesis is of type int. The use of System.out.println
activates some pre-written code already present in the virtual machine. There are many different
pieces of pre-written code – one to print a String, one to print an int, one to print a double, and
so on. The compiler picks the right one based on the type of the expression in the parenthesis.
37

String concatenation

• Concatenation is the merging of two Strings into one by placing them one after the other.
The type of the expression is String + String --> String

• One example: "Levia" + "than" evaluates to the String value "Leviathan"

• If one of the operands is a variable of a primitive type instead of a String, that operand’s
value is automatically converted to a String, just as in our earlier type expression examples,
the value of an int variable got automatically converted to a double value if we used an
assignment statement to write an int value into a double variable.

• For example, in the expression:

System.out.println("Sum is: " + 20);

we have a String + int operation, and that means that the + is a concatentation, and that
means the value of the int should be converted to a String automatically and then the
concatenation done.

• "Sum is: " + 20 -> "Sum is: " + "20" -> "Sum is: 20"

• So, again, the meaning of an operator depends on the type of the operand(s). The operator +
in an int + int expression has a different meaning than the operator + in a String + int
expression.

An example using concatenation

public class Example7


{
public static void main(String[] args)
{
int exam1, exam2, sum;

exam1 = 60;
exam2 = 70;
sum = exam1 + exam2;

System.out.println(exam1);
System.out.println(exam2);
System.out.println();
System.out.println("Sum is " + sum);
}
}

• When there is nothing at all in the parenthesis, all that is printed is a blank line.

• In the last line, the type of the expression in parenthesis is String, and that expression
evaluates to the String value Sum is 130 (which is the String value that gets printed to
the screen).
38

A bit more messing with types

Consider the following code:

public class Example8


{
public static void main(String[] args)
{
int v1 = 5;
int v2 = 7;
System.out.println("Sum is " + v1 + v2);
System.out.println(v1 + v2 + " is the sum");
System.out.println("Sum is " + (v1 + v2));
}
}

The + operator is read left-to-right, so in the first two output statements above, the leftmost
+ is done first. In the third output statement, the parenthesis take higher precedence and so the
rightmost + is done first. Most operators are read left-to-right. A few, such as assignment, are
read right-to-left, as with the statement a = b = c = d = 2;, where d is assigned the value 2 first,
then c is assigned the value of d, and so on. A few other operators can’t be used multiple times in
a row to begin with. But most operators are read left-to-right.
So, the first output line:

String + int + int "Sum is " + 5 + 7


------------
|
|
\./
String + int "Sum is 5" + 7
--------------
|
|
\./
String "Sum is 57"

And the second output line:

int + int + String 5 + 7 + " is the sum"


---------
|
|
\./
int + String 12 + " is the sum"
--------------
|
|
\./
String "12 is the sum"
39

And the third output line:

String + (int + int) "Sum is " + (5 + 7)


------------
|
|
\./
String + int "Sum is " + 12
------------------
|
|
\./
String "Sum is 12"

One last note...

Everything that is true for System.out.println(...); is also true for System.out.print(...);


except that the latter doesn’t start a new line after the printing, and the former does.

System.out.println("Hello, ");
System.out.println("world!");
will print: Hello,
world!
^

and the next thing to be printed goes where the ^ is. (The ^ is not actually printed, it’s just an
indicator in our two examples on this page.)

System.out.print("Hello, ");
System.out.print("world!");
will print: Hello, world!^

and the next thing to be printed goes where the ^ is.


This means that the statement System.out.print(); is meaningless. With System.out.println(),
there is at least a new line started even if no other text is printed. But if you don’t start a new
line and you don’t print anything, then you did no work whatsoever. So there’s no reason to have
a System.out.print(); command.
40

Input using Keyboard.java

So, now that we have seen how to output data to the screen, we also need to see how to input
data from the keyboard. Java, oddly enough, does not have nice, easy-to-use facilities for reading
input from the keyboard in a command-line application (at least as of this writing; there are rumors
that such useful tools are being added to the language in a future revision). The only tools Java
provides for reading input from the keyboard, are very low-level tools that a beginning student
would not understand how to use. So the authors of one of your recommended textbooks have
written a Java file, Keyboard.java, that builds the kind of nice facilities we need out of the more
primitive facilities that Java provides us.
In order to use the input tools we describe below, you will need to put the
Keyboard.java file in the same directory as your MP!!! Forgetting to do this is the single
most common mistake made by students on the first MP. The compiler can’t use the Keyboard.java
file if the file isn’t there! Your first MP will direct you to where to obtain a copy of this file, as
part of an output-testing tutorial, so you’ll know where to get a copy of the file when you need it.
If you happen to be curious, you can take a look at the Keyboard.java source code. But, you
don’t ever really have to do this. You can just type into your own code the kinds of expressions
we’ll discuss below and that will be enough to make input from the keyboard work for you. (As
we learn more about Java in the first half of the course, the source code of the Keyboard.java file
might start to make a little bit more sense to you than it would right now.)
The Keyboard.java file provides us with five tools for our use:

• Keyboard.readInt()

• Keyboard.readChar()

• Keyboard.readDouble()

• Keyboard.readString()

• Keyboard.endOfFile()

We won’t be dealing with the last two right now – we’ll only be using Keyboard.readInt(),
Keyboard.readChar(), and Keyboard.readDouble(). Each of those three expressions evaluates
to a particular type, namely, int, char, and double, respectively.

• Keyboard.readInt() – makes your program pause until the user types in an integer and hits
return; then, the value of the expression is that integer that the user typed in.

• Keyboard.readChar() – makes your program pause until the user types in an character and
hits return; then, the value of the expression is that character that the user typed in.

• Keyboard.readDouble() – makes your program pause until the user types in an floating-
point value and hits return; then, the value of the expression is that floating-point value that
the user typed in.
41

Input example

Read in an integer and print it out again.

public class Example9


{
public static void main(String[] args)
{
int myValue;
System.out.println("Enter new value:");
myValue = Keyboard.readInt();
System.out.println("Your value was: "
+ myValue);
}
}

• Keyboard.readInt(), like any other expression, is on the right of the assignment operator.

• The first output line is called a prompt; you should always print a prompt before you ask for
input, in order to let the user know what they are supposed to input. Otherwise the program
seems to just halt suddenly.

• Note that the last line is broken into two lines so as not to run off the end of the slide.
Likewise you can use that technique to avoid running past 70 or 80 characters on a line in
your code. Just don’t put a split in the middle of a double-quoted String literal; if you start
a value with double-quotes, the closing double-quotes must be on the same line.

• When you are writing your first MP, try running the following two lines:

javac Keyboard.java
java Keyboard

The first line will work fine, and will produce a file Keyboard.class which – as we described
earlier – will contain the machine code the compiler produced from the source code in the
Keyboard.java file. However, the second line will NOT work – you will get an error message
telling you in some way that your file (Keyboard.class) is missing information about main().
Remember the word main that was part of our “program skeleton”? Well, it turns out that
every program you ever run in Java, needs a main somewhere. If your file has no main, then
your file is not a program you can run. And, indeed, Keyboard.java does not contain the
code for a program you can run. What it contains are additional utilities which can be used by
a program such as the ones you are going to write. That “program skeleton” we talked about,
is what the virtual machine looks for when you start the virtual machine to run your program,
and that is why files like Keyboard.java, which do not have that “program skeleton”, can
be used by other programs, but are not complete programs in and of themselves.
42

Programming Style

While certainly it is most important to write a program that actually works, it is also important
to write your code using good programming style. Doing this makes your code more readable and
understandable, which is very important.
Imagine if our “Hello World” program was stored in a file named Foo.java, and was written
as follows:

public class Foo { public


static void main(String[] x)
{ System.out.println("Hello World!"); }}

That is legal! The compiler can understand this! But you cannot.
Think of the text of a program as being a bunch of characters one after another in a pipe.
The compiler reads these characters in one by one, and all spaces, blank lines, and tab characters
(collectively known as “whitespace”) are discarded by the compiler. Likewise, as we have stated,
all the names you use get traded in by the compiler for actual memory locations.
So, you can use all the whitespace you want, and make names as long as you want. Those
things only serve to help you (and others) read your program.

Style component #1: Indentation

Indenting your source code properly is very important, and becomes even more important as
your programs get larger. There are various “indentation standards” which vary slightly, but the
general rule of thumb they are all based on is that stuff inside curly braces should be moved to the
right a few spaces (three to five is a good choice).

Wrong:

public class HelloWorld


{
public static void main(String[] args)
{
System.out.println("Hello World!");
}
}

The first thing you need to do is realize that it would be easier to tell what code was inside the
class if that code was indented three spaces. (This will be even more important when we starting
adding other methods besides main to a class.)
43

Better, but still not ideal:

public class HelloWorld


{
public static void main(String[] args)
{
System.out.println("Hello World!");
}
}

The second thing to realize is that, likewise, it will be easier to read the code inside main if that
code is indented three spaces from the curly braces that begin and end main. Again, the more code
you have, the more important this becomes.
Correct:

public class HelloWorld


{
public static void main(String[] args)
{
System.out.println("Hello World!");
}
}

Now, it is very easy to visually pick out with one glance where the class begins and ends, what
code is inside it, and where main begins and ends, and what code is inside it.
A slight variation on this is that some people like to put the open brace on the same line as the
Java syntax tool it is starting:
Also correct:

public class HelloWorld {


public static void main(String[] args) {
System.out.println("Hello World!");
}
}

Since the word public lines up with the closing brace, you still have the same kind of visual
structure. It’s a bit harder to see it, since the code is crushed together a bit more, but on the other
hand, you can fit more code in the viewing window at once. Some people think this tradeoff is
worth it and others (myself among them) don’t. You can make your own choice.
That is what was meant by “indentation standards” – there are slightly different ways of doing
things. To give another example, some people indent three spaces, some prefer five. You should
indent at least three but you don’t want to indent so many spaces that your code runs off the end
of the page all the time.
You can use whichever variation you prefer – just be consistent and keep your line lengths at
about 80 characters or so.
44

Style component #2: Descriptive class, method, and variable names

As we have already discussed, it is a lot easier to quickly figure out what a given chunk of code
is doing if the variables in that code have been given names that describe what data they hold.

Unclear:

c = a + b;

Much better:

totalScore = exam1 + exam2;

With relatively few exceptions, using single letters for variable names tends to be a bad idea.
Even a short 4- or 5-character abbreviation of a longer word is more descriptive than a or G or q.
Likewise, as you start to write other methods besides main, and as you start to write other classes,
you should choose descriptive names for those methods and classes as well. Any names you choose
should in some way describe the purpose of what is being named.

Style component #3: Commenting

Indentation makes it easy to read your code; descriptive variable names make it easy to tell
what those variables are for. However, not every collection of statements has an immediately-
decipherable purpose even if you know what each of the variables holds. In addition, it often helps
to explain the general purpose of a particular file, or the individual major divisions of code within
that file.

So, for the purposes of documentation, Java provides syntax for commenting our code. Com-
ments are basically just text descriptions of whatever it is you want to describe or explain – the
purpose of the file, a quick summary of the intent behind a particular section of code, or whatever
else. We use a special Java syntax to notate these remarks as comments; as a result, the compiler
ignores them just as it ignores extra whitespace. Thus, we can add as many comments as we want
without affecting the resultant machine code at all. The comments are there for the code reader’s
benefit only.
45

One syntax for commenting

Most of the time, the commenting syntax you will want to use is the use of the “double slash”:
//. You can place that two-character symbol anywhere in your code, and anything from that point
to the end of the line of text will be ignored by the compiler.

Example:

// Class: HelloWorld
// - in control of greeting generations
// - Written August 1999 by Jason Zych
public class HelloWorld
{

// Method: main
// - controls main execution of program
public static void main(String[] args)
{
// line prints out a greeting
System.out.println("Hello World!");
}

Another commenting syntax

If you want to quickly comment out a large chunk of text or code, you can also surround the
area as follows:

/* <---- slash-asterisk to open comment

whatever you want goes in here

*/ <---- asterisk-slash to close comment


46

Example 2:

/*
Class: HelloWorld
- in control of greeting generations
- Written August 1999 by Jason Zych
*/
public class HelloWorld
{
public static void main(String[] args)
{
System.out.println("Hello World!");
}
}

Example mixing the two kinds of comments:

/*
Class: HelloWorld
- in control of greeting generations
- Written August 1999 by Jason Zych
*/
public class HelloWorld
{

// Method: main
// - controls main execution of program
public static void main(String[] args)
{
// line prints out a greeting
System.out.println("Hello World!");
}

}
47

New programmers sometimes take this to an extreme and place a comment on every single line,
even the lines whose purpose is obvious. For example:

// adds 1 to the number of CDs


numCDs = numCDs + 1;

Comments like this are generally a bad idea. If you put comments even at lines where the purpose
of the code is clear, then the comments start to clutter up the code. Usually, the purpose of a
comment is just to remark on a line whose purpose isn’t immediately obvious, or to remark on the
general purpose of an entire section of code. With experience, you will gain a sense of the right
balance between “not enough commenting” and “too much commenting”.
Remember – if you choose descriptive variable names, then often your code becomes mostly
“self-documenting” – i.e. fewer comments are necessary because, due to your choice of variable
names, the code’s purpose is mostly clear at first glance. Any time a variable name helps to
document the variable’s purpose, that’s another comment you might not have to bother writing.
So, when you write your MPs – and when you write other code as well – it will be important
to keep in mind the elements of good coding style:

1. Indentation

2. Descriptive variable names

3. Comments where needed

Part of your grade will depend on writing your code in a good style.
48

Lecture 5 : Boolean Expressions, Simple Conditionals, and State-


ments
Boolean Expressions

We have previously discussed the idea of an expression, and the idea that every expression
evaluates to a single value of a particular type. Up to this point, most of the expressions we have
seen have evaluated to type int or type double. There are many possible expressions you might
write, however, and not all of them evaluate to values of type int or double. For example, the
following is an expression – specifically, it’s a literal – of type boolean:

false

And, the following is another expression of type boolean (which again happens to be a literal
of the boolean type):

true

Finally, if we perform the following declaration and initialization:

boolean exampleVariable;
exampleVariable = false;

then after the above code is run, the following is also an expression of type boolean:

exampleVariable

We call those expressions boolean expressions because they are expressions that evaluate to a
value of type boolean, rather than a value of some other type.

Boolean expressions of greater complexity

Up to this point, the only boolean expressions we have been able to put into our programs are
literals of type boolean, and variables of type boolean, as in the examples we just saw. We had
the arithmetic operators to help us create more complex arithmetic expressions, but none of those
operators helped to produce boolean values – all of those operators helped perform arithmetic and,
as a result, the expressions we wrote that used those operators, evaluated to numerical values.
To form boolean expressions that are more complicated than literals or single variable names,
we will need operators that help produce boolean values, rather than the arithmetic operators,
which help produce numerical values. These operators that produce boolean values fall into two
groups:

• relational operators – two operands can be any type, but the resultant expression evaluates
to a boolean value

• logical operators – the operands need to be of type boolean, and the resultant expression
evaluates to a boolean value

We will examine both categories of operators.


49

Relational Operators

The relational operators are operators that perform comparisons; we are trying to see how two
values relate to each other. Specifically, we question whether or not two values are related to
each other in a particular way. If they are indeed related to each other in that way, the expression
evaluates to true, and if the two values are not related to each other in that way, then the expression
evaluates to false.
The relational operators are:

• < (less than)

• > (greater than)

• <= (less than or equal to)

• >= (greater than or equal to)

• == (are equal)

• != (are not equal)

These, like the arithmetic operators, are binary operators, meaning they have two operands.

Do not confuse = and == when you write your code!!! This is a very common mistake. The single
equals sign means assignment; it is an action. The double equals sign means “compare for equality”;
it asks a question, “are the two operands equal, or not?”.
Using these operators, we can create boolean expressions that perform comparisons for us. For
example:

• heightOfPerson > 6.3 (evaluates to true when the variable heightOfPerson of type double
holds a value greater than 6.3, and evaluates to false otherwise)

• examScore <= 91 (evaluates to true when the variable examScore of type int holds a value
less than or equal to 91, and evaluates to false otherwise)

• grade != ’A’ (evaluates to true when the variable grade of type char holds a character
other than the capital letter ’A’, and evaluates to false otherwise)

• statusFlag == true (evaluates to true when the variable statusFlag of type boolean holds
the value true, and evaluates to false otherwise)

• time1 < time2 (assuming time1 and time2 are both of type int, evaluates to true when the
value stored in time1 is less than the value stored in time2, and evaluates to false otherwise

In general, the two operands of a relational operator tend to be of the same type. However,
since both integers and floating-point values are numbers, it is possible to compare them to each
other using the relational operators (for example, 5 <= 6.1 or 23.00002 == 23).
50

Logical Operators

• As we stated earlier, relational operators have assorted types as operands, and produce values
of type boolean.

• The logical operators also produce values of type boolean; however, unlike relational opera-
tors, logical operators also must have operands of type boolean.

• Logical operators are designed to create complex boolean expressions out of simple boolean
expressions.

• The following four operators are the logical operators:

|| (or)
&& (and)
! (not)
^ (exclusive or, also called xor)

• The concepts these operators implement are very common all over computer science.

X or Y -- true if either X is true,


or Y is true, or both
X and Y -- true only if both X and Y are true
not X -- returns the opposite of X
X xor Y -- true only when X is true and Y is
false, or when Y is true and X is false;
false if X and Y are the same

Using these operators, we can create more complex boolean expressions out of simpler boolean
expressions such as boolean literals or boolean variables. For example:
• true && false (evaluates to false, since it is NOT the case that both operands are true

• true || false (evaluates to true, since AT LEAST ONE operand is true)

• true ^ false (evaluates to true, since EXACTLY ONE operand is true)

• !false (evaluates to true, which is the opposite of the operand)


If we had three variables, val1, val2, and val3, each of type boolean, and if the first two variables
held the value true and the third held the value false, then:
• val1 && val2 evaluates to true

• val2 && val3 evaluates to false

• val1 || val3 evaluates to true

• val1 ^ val2 evaluates to false

• val1 ^ val3 evaluates to true

• !val3 evaluates to true


51

Using relational and logical operators together

It’s important to keep in mind the difference between the relational and logical operators:

• the relational operators produce boolean values, but the operands themselves do NOT have
to be boolean values. For example, 5 < 6.1 is a perfectly legal boolean expression; the
operands are not boolean values even though the result is

• the logical operators not only produce boolean values, but must have boolean values as
operands as well

As a result, often we have many small boolean expressions, each using a relational operator to
generate a boolean value, and then all the small boolean expressions are merged into one large
boolean expression by using the logical operators. For example, the expression:

(x >= 1) && (x <= 100) && (x % 2 == 0)

evaluates to true whenever x is an even integer between 1 and 100, inclusive. As another example,
the following expression:

(x == 5) || ((x > 10) && (x < 0))

evaluates to true only when x is 5, since the second operand of the logical OR above can never
evaluate to true.
52

The short-circuiting behavior of logical AND and logical OR

The && and || operators are short-circuiting; that is, they don’t evaluate the second operand
of the operator if the answer to the overall expression is already known after evaluating the first
operand.

Example:

(x != 0) && (y/x < 70.2)

In the boolean expression above, the first thing to be evaluated is the truth or falsehood of the left
side of the && operator. Assume x is zero; if so, this expression we are trying to evaluate – namely,
x != 0 – evaluates to false. And in that case, we know what the result of the entire expression
will be! Since an AND expression is only true if both operands are true, and since we know the
value of the first operand is false, we know the entire AND expression must evaluate to false
regardless of the value of the second operand. Again, that is assuming that x is zero.
On the other hand, if x held a non-zero value, then the first expression – the x != 0 expression
– would evaluate to true. In that case, the AND expression could still evaluate to true or to
false, depending on the result of the evaluation of the second operand. So, because of that, we
must evaluate the second operand (y/x > 70.2) to know for sure what the result of the AND
expression is.
This feature might be helpful for two reasons. First of all, any work that we don’t have to do,
is time we save – so if the machine can avoid having to evaluate the second operand, then that
means your program runs a little bit faster since there is less work to do. Also, in the expression
above, we don’t want to evaluate y/x if x is zero, since the subsequent division-by-zero operation
would crash our program. But since the short-circuit property of the && operator prevents that
second expression from being evaluated when x is zero, our program is safe! So in addition to
saving time, the short-circuiting feature can be used as we used it in the example above, to keep
us from evaluting a particular expression such as y/x if it would be dangerous to do so.
Likewise, for the OR operator, if the first operand evaluates to true, the second operand won’t
be evaluated, since the first operand being true means the entire expression must be true regardless
of the value of the second operand.
Note that XOR has no short-circuiting ability since you always need to know the values of
both operands in order to evaluate the XOR, and NOT has no short-circuiting ability since there
is only one operand to begin with.
53

Basing results on comparisons – the conditional

Up to this point, we’ve only listed instructions one after the other. But, just listing instructions is
not enough!

What we do might depend on certain conditions being true.

ex.: Read a student score, print out whether each has passed (better than 60) or failed (60 or
worse).

Read score, call it examScore


If examScore is greater than 60,
print out that the student has passed
Otherwise examScore must be <= 60.
In this case, print out that
the student has failed.

In this case, we don’t always print “passed” and don’t always print “failed”. In fact, each time
we read an exam score, we perform exactly one of those – we will always print either “passed” or
“failed”, but never both. How can we make this work?
What we need is a new kind of language statement – the conditional, which will run code
“conditionally”.

The if statement

Today we are introducing a new kind of statement – the if statement. The form for this
statement is as follows:

if (condition)
statement;

• condition must be a boolean expression


• statement is a statement of some kind
• The statement is only executed if the condition evaluates to true. If the condition
evaluates to false instead, then, the statement is skipped over, and the next line of code
that executes is whatever is after the if statement.

Example:
if (grade > 60)
System.out.println("Passed!");
System.out.println("Done!");
If student’s grade is greater than 60, then both lines get printed. If student’s grade is not greater
than 60, than only "Done!" gets printed. The line that prints "Done! has nothing to do with the
if statement, so it gets run regardless of whether the condition is true or false. (Note that we
indent the statement that is run conditionally by the if statement – that is the proper style.)
54

Statements

Up to this point, we have used the term “statement” to refer to any one of the following:
• a variable declaration statement

• an assignment statement

• a System.out.println statement

• a System.out.print statement
Furthermore, if you want to get technical, the Keyboard.readInt(), Keyboard.readChar(), and
Keyboard.readDouble() expressions are also statements. Those statements evaluate to values, so
they are also expressions, as we discussed before. But we can put them in the “statement” category,
too, since they accomplish work on their own, whether we do something with the returned value
or not.
So, it’s all fine and good to make a list of things we call “statements”, but what is a statement?
Well, one vague way to define the term is to say that a statement is one complete unit of work. For
example, the expressions 5 - 3 * 2 or "Sum is : " + 120 evaluate to single values, but what do
we do with those values? It is not until we put the values inside statements – perhaps by writing the
value to a variable (via an assignment statement) or perhaps by printing the value to the screen (via
a System.out.println statement) – that we’ve made meaningful use of the expression’s value. The
expression, on its own, simply calculates the value and then ignores it. The fact that an expression
is not automatically a statement, is why the following will compile:
public class Example1 {
public static void main(String[] args) {
System.out.println(2 + 3 + " is the sum.");
}
}
but the following two examples will not compile:
public class Example2 {
public static void main(String[] args) {
2 + 3 + " is the sum."
}
}

public class Example2 {


public static void main(String[] args) {
2 + 3 + " is the sum."; // <-- adding a ; doesn’t make a difference
}
}
You can’t just toss 2 + 3 + " is the sum." into your program; you need to do something
with the result of that expression. (If you run Keyboard.readInt(), you obtain a piece of data
from the user, so in that case, you have indeed done something, even if you don’t use the value
you’ve inputted – you’ve taken in an input value from the user so that that input value won’t be
re-entered the next time your program wants input.)
55

However, we can come up with a more precise definition of “statement” than “one complete
unit of work”. Instead, our definition of “statement” will be: a statement is anything we refer to
as a statement, i.e. anything that is on our list of things we call statements. We started our list at
the top of the previous page; all those statements accomplished different sorts of things, but they
were all on the list, so they were all statements.
This widely-ranging definition of “statement” is a good thing! It means that in any code
construct where we say a “statement” should go, we can substitute anything we want, as long as
it is called a “statement”. For example, we have said this is a statement:

if (grade > 60)


System.out.println("Passed!");

So, if it is a statement, then why not put it inside an if statement, since any statement can go
inside an if statement? That is, the grammar of an if-statement was:

if (condition)
statement;

so if the statement is the conditional we listed above, and the condition is daysInAttendance > 200,
then we get the following:

if (daysInAttendance > 200)


if (grade > 60)
System.out.println("Passed!");

That is legal code! Since the grammar of a conditional had a statement within it, we can put any
statement we want there – even another conditional! In a sense, it is as if the simple statements,
such as assignments, declarations, and print statements, are primitives – and then statements such
as the if statement, that can contain other statements within itself, are the means by which we
create compositions. This is not so different from expressions, and the way literals and variables
were primitive expressions, while more complex expressions were built from simpler expressions.
And just as we made use of those expression rules to build expressions that were as complex as we
needed, likewise we can make use of statements-that-hold-other-statements, to create statements
of whatever complexity we need.
We will explore this idea further in the next lecture.
56

Lecture 6 : Compound Statements, Scope, and Advanced Condi-


tionals
Multiple statements based on the same condition

Given what we have seen so far, if we wanted three different statements to run based on the
truth or falsehood of the same condition, we would need three different conditionals:

if (x > 5)
a = a + 2;

if (x > 5)
b = b + 3;

if (x > 5)
c = c + 1;

In the above code, if x is indeed greater than 5, then all three assignment statements will be
run, and if x is NOT greater than 5, then none of the assignment statements will be run.
However, we are being wasteful in two ways here. First of all, we had to write the same con-
dition three different times. If we had some means of collecting those three assignment statements
together, we might be able to do something like this:

if (x > 5)
DO ALL OF THESE:
a = a + 2;
b = b + 3;
c = c + 1;
OK NOW YOU ARE DONE

and thus only write out the condition once. If there were 1000 statements that should all run
depending on whether or not x was greater than 5, rather than just three statements that depend
on that condition like in the above example, then we’d save even more work by being able to group
all those statements into one conditional statement under one copy of the condition, instead of
typing the same condition 1000 times (once in each of 1000 different conditional statements).
In addition, if we’ve listed the condition three times in our code, then it is being evaluated three
times as well. By limiting the number of times we list the same condition, we limit the number of
times it has to be evaluated – thus reducing the work our program is requiring the processor to do.
For both of these reasons, we would like a way to group statements together, as we did above
with our “DO ALL OF THESE/OK NOW YOU ARE DONE” remarks. We would like a way to
make a conditional statement, conditionally run many statements, rather than just one.
To accomplish this, we will introduce another kind of statement – the compound statement.
57

Compound Statements

The compound statement is created by taking any collection of other statements, listing them
one after the other in the order you want, and then enclosing the entire collection of statements
within a set of curly braces. For example:

{
int a;
a = 2;
System.out.println(a);
}

We can consider the above five lines of code to be one statement, since even though there are
three smaller statements (on the second, third, and fourth lines of the example above), they are
all included within a pair of curly braces. In places where we need a single statement, the above
qualifies – it is considered only one statement. The fact that it happens to be composed of many
smaller statements is irrelevant, because the curly braces have the effect of gathering all the smaller
statements together into one logical unit.
To run this single statement, you just run each of the smaller statements within it, one after the
other, in sequence. So, in most situations, there is no difference at all between writing a program
with the above code in it, and writing three separate statements on their own:

int a;
a = 2;
System.out.println(a);

since in both cases, we will run the declaration, the assignment, and the print statement, one after
the other, in sequence.
There are only two situations where converting a series of individual statements into a single,
compound statement, is a helpful thing to do. One of these situations involves scope, and we’ll
talk about that later in this packet. The other situation is the one we were just talking about at
the start of this packet – by treating a number of statements as one complete unit, we can then
include that one complete unit as the one statement inside a conditional, and thus enable a single
conditional statement to conditionally execute many smaller statements together, instead of only
one smaller statement. (We’ll see an example in just a moment.)
Once again, we are seeing here that a good definition of “statement” is, “Anything that we call a
statement, is a statement”. It’s a good definition because there really isn’t any similarity between
the different things that we call a statement. Each one basically represents a single “chunk” of
work, but even that is a vague definition. So rather than trying to define “statement”, we just
make a list of everything that is called a statement. What we have found already, though, is that
having a list of different things that we call a statement is very useful, since it means whenever we
need a statement, we can use anything appropriate from that list. Now, we can add the “compound
statement” to that list of things that we can call “a statement”, and thus we can use a “compound
statement” anywhere we might need a statement in our program.
58

Conditionals with Compound Statements

Just as the compound statement had other statements nested within it, likewise the if-statement
has another statement nested within it. And, as with the compund statement, we can choose any
statement from our “list of statements” to go in that position. For example, if we chose our example
compound statement from the previous page, to be the statement nested within an if-statement,
then we might get the following:

if (daysInAttendance > 200)


{
int a;
a = 2;
System.out.println(a);
}

In the above example, if the condition is true, all three lines inside the curly braces are run, and if
the condition is false, none of the three lines inside the curly braces are run.
Similarly, to solve the problem we introduced at the start of this packet, we simply need to
enclose each of the three assignment statements, together inside a compound statement:

if (x > 5)
{
a = a + 2;
b = b + 3;
c = c + 1;
}

In both cases, if the condition is true, all the smaller statements within the compound statement
are run, and if the condition is false, none of the smaller statements within the compound statement
are run.
By using this technique, we can make a single condition affect whether or not an enormous
amount of code is run. For example, if there were 1000 smaller statements within a compound
statement, and we then put that compound statement within a conditional statement, then those
1000 lines together would either all get run, or else none of those 1000 lines would run – in either
case, as the result of evaluating a single condition.
59

Scope

The scope of a variable is the part of the program in which that variable can be used, the section
of the code for which that particular variable is defined and accessible.

Part of what determines the scope of a variable is what block it is declared in. For now, a block
can be thought of as the collection of code within a set of curly braces. For example, creating a
compound statement creates a block, since now you’ve got the assorted smaller statements grouped
within a set of curly braces.
Variables declared in a block are usable only until the end of that block. Things will get more
complex when we start discussing methods, but for now – as we deal with code where everything is
inside the main() method – we can think of our program as a bunch of blocks nested within other
blocks.
The outermost block is the main() method itself – all the code inside the open- and close-
braces of the main() method. In the absence of any additional blocks inside main(), a variable
declared inside main() is usable from the point of its declaration, to the end of the block it was
declared in – i.e. the end of the main() method.
But now, put another block inside the main() method:

public static void main(String[] args)


{
int i = 0;
{
int x;
x = 1;
System.out.print(i);
i++;
}
// System.out.println(x);
}

A variable such as i is declared in the outermost block – inside main() but not inside any other
block. So, it is usable everywhere in main() from the point of its declaration, down to the end of
main() at the final closing brace.
60

But, the variable x is declared inside a block that is inside main(), or, more formally, inside
a block that is nested in main(). The rule is that a variable declared inside a block goes out of
scope at the end of the block. And so, x is declared inside the nested block and thus goes out of
scope as soon as the closing brace of the nested block is reached. So x and any value it was holding
effectively vanish once the end of the nested block is reached. As a result, the program would not
compile if we uncommented the output line that is currently commented out. At that point in the
code, there isn’t a variable x, because that variable went out of scope when the nested block ended
and thus no longer exists.
The rule is as follows:

• As always, variables cannot be used before they are declared.

• Variables declared in outer blocks can be used inside any block nested inside that outer block,
and any changes made to the variable inside that inner, nested block, are still in effect when
the nested block ends and we return to the outer block. (In our example, the variable i is
usable anywhere inside that compound statement, and after the compound statement is over,
the variable i holds the value 1, since the increment that happpened to the variable i within
the compound statement, permanently affects i. The value of i does NOT reset to 0 when
the compound statement ends.)

• Variables declared inside an inner block are not accesssible to the outer block – i.e. once you
leave the inner block and you are back in the outer block inside which the inner block was
nested, the variable you declared inside the inner block has gone out of scope and cannot
be used by the outer block. (In our example, the variable x is destroyed at the close of the
compound statement and does not exist past that point, which is why the commented-out
print statement would not compile if we uncommented it – it is the same as if we tried printing
the value of the expression x when the variable x had never been declared at all.)

One advantage of this is that, if a variable is out of scope, its memory is now free for some other
variable to use. There are other advantages as well, involving properly organizing what variable
names we are using – those advantages will become a bit clearer as we learn additional Java syntax
and as our programs become more complex.
61

Scope in conditionals

From the standpoint of scope, we can consider the following two statements to be equivalent:

if (grade > 60) if (grade > 60) {


int i = 5; int i = 5;
}

That is, if you only have a single-line statement nested within the if-statement, it is still treated as
if there were a set of curly braces around it, and thus the scope of the variable i above will end at
the end of the closing curly brace, whether you actually put the curly braces into your code or not.
Or in other words, in the above left example, the variable i is NOT in scope once the if-statement
has concluded; anything declared within the if-statement is out of scope once the if-statement
has concluded.
If you actually have a compound statement within your if-statement, then the scope rules work
exactly as we said they did for compound statements earlier. For example:

// same with an if statement as with a loop

int y = 5; <--- y declared outside block


if (y < 10)
{
int x = 9; <--- x declared inside block
System.out.print(x); <-- x can be used here
y = y + 7; <--- y can be used here
}
System.out.println(y); <--- will print 12;
not only can y be used
here but whatever
alterations made to y
inside the block are
still in effect here
System.out.println(x); <--- ERROR! x went
out of scope with the
close brace of the if
statement and can’t be
used here
62

The if-else statement

Imagine we had exactly one of two statements we wanted to execute, depending on whether a
condition was true or false:

if (grade > 60)


System.out.println("Passed!");
if (grade <= 60)
System.out.println("Failed!");
System.out.println("Done!");

Only one of those conditions can be true, so only one of "Passed!" or "Failed!" will be printed
(though, as before, "Done!" gets printed no matter what). There is a more convenient form for
this, which is as follows:

if (condition)
statement1;
else
statement2;

• Again, the condition is a boolean expression

• When the condition is true, statement1 is executed and statement2 is skipped over.

• When the condition is false, statement1 is skipped over and statement2 is executed.

Example:

if (grade > 60)


System.out.println("Passed!");
else
System.out.println("Failed!");
System.out.println("Done!");

If the grade is greater than 60, then the output will be:

Passed!
Done!

On the other hand, if the grade is not greater than 60, then the output will be:

Failed!
Done!

This is exactly like the example on the top of the previous slide...only now we needed only one
statement to do it, and we didn’t need to evaluate conditions twice, just once.
63

As we stated before, whenver the form of these constructs requires a statement somewhere,
you can substitute as simple a statement or as complex a statement as you like. This means, for
example, that since the if-else statement needs a statement after the condition, and another
after the else keyword, you can put very complex statements in those spots...such as another
if-else.

if (grade >= 90)


System.out.println("gets an A.");
else
if (grade >= 80) // but < 90
System.out.println("gets a B.");
else
if (grade >= 70) // but < 80
System.out.println("gets a C.");
else
System.out.println("below C.");

It is possible to list these statements very compactly if there are many nested else cases. The
above can instead be written as:

// the above:
if (grade >= 90)
System.out.println("gets an A.");
else
if (grade >= 80) // but < 90
System.out.println("gets a B.");
else
if (grade >= 70) // but < 80
System.out.println("gets a C.");
else
System.out.println("below C.");

// compactly:
if (grade >= 90)
System.out.println("gets an A.");
else if (grade >= 80) // but < 90
System.out.println("gets a B.");
else if (grade >= 70) // but < 80
System.out.println("gets a C.");
else // grade < 70
System.out.println("below C.");
64

What happens here?

if (grade >= 80)


if (grade >= 90)
System.out.println("gets an A.");
else
System.out.println("lower than B.");

The else is paired with the nearest unmatched if. So, the computer reads it as:

if (grade >= 80)


if (grade >= 90)
System.out.println("gets an A.");
else
System.out.println("lower than B.");

This is known as a dangling else and is an error that can be very hard to track down. So, be
careful! To correct this, you can either put in an “empty statement” – i.e. a semicolon by itself...

if (grade >= 80)


if (grade >= 90)
System.out.println("gets an A.");
else
;
else
System.out.println("lower than B.");

...or (better!) remove ambiguity and make the nested if a clearer statement by using braces...

if (grade >= 80)


{
if (grade >= 90)
System.out.println("gets an A.");
}
else
System.out.println("lower than B.");

...or else (best of all) rearrange the logic.

if (grade < 80)


System.out.println("lower than B.");
else if (grade >= 90)
System.out.println("gets an A.");
65

Lecture 7 : Loops
Repeating our code statements many times

Last time, we added the ability to have certain sections of code run conditionally – i.e. sometimes
they run, and sometimes they don’t, depending on whether certain conditions are true or not.
ex.: Read a student score, print out whether each has passed (better than 60) or failed (60 or
worse).

Read score, call it examScore


If examScore is greater than 60,
print out that the student has passed
Otherwise examScore must be <= 60.
In this case, print out that
the student has failed.

• We solved problems like that above with the if statement and the if-else statement.

• What if we want to do this for many students, and not just one? How can we repeat that
code over and over again?

• And once we start repeating that code, how can we stop? How can we tell the computer that
there are no more student scores?

For example:

Read score, call it examScore

If examScore is greater than 60,


print out that the student has passed,
and then repeat all this code again.

Otherwise examScore must be <= 60. If it is


also >=0, then it is a real exam score, In
this case, print out that the student has
failed, and then repeat all this code
again.

Otherwise, examScore must be < 0. This is not


a real exam score, so it must instead be a
signal to stop reading in exam scores. So
do NOT repeat all this code; instead,
stop running any of this code, and move
on to whatever statement comes next.
66

Loops

• Loops are designed to run a particular piece of code – called the body of the loop – many
times in succession.

• The loop is controlled via a condition, just as the if statement was. The body of the loop
will continue to be repeated over and over until the condition becomes false.

• There are different kinds of loops. All of them basically do the same thing, but for each one,
the particular syntax of that kind of loop is more convenient for some situations and less
convenient for other situations.

The while loop statement

The first loop statement we will look at is the while loop. The form is very similar to the if
statement:

while (condition)
statement;

We only proceed as long as condition is true.

• If condition is false to begin with – never run statement

• If condition is true to begin with – run statement once, check condition again

• Each time you re-check condition, if it is true, run statement one more time, and then check
the condition again. If it is false, leave loop without running the loop statement again, and
instead run the next line of code after the loop.

For example:

while (i >= 0)
i--;
System.out.println("Done!");

The value of i will get smaller and smaller until finally it is less than 0, at which point the loop
will stop. The print statement is not part of the loop, and is executed only once, after you leave
the loop.
67

The while loop with a compound statement

Just as with the if statement, any kind of statement can go into the statement segment of a
while loop, including a compound statement.

while (condition)
{
statement1;
statement2;
.
.
.
statementN;
}

• If the condition is false, the loop ends immediately and none of the statements is run.

• If the condition is true, each of the statements is run once, and then, the condition is
evaluated again.

• Every time you re-evaluate the condition and it is still true, all the statements get executed
again. Once you evaluate the condition and it is false, then you stop executing the state-
ments, exit the while-loop statement, and move on to whatever statement is after the while
loop.

A common error

• Whether the internal statement is a single statement or a compound statement, what happens
if the condition is always true?

• Answer: you get an infinite loop (the loop repeats endlessly).

• This is a common run-time error. A compiler cannot find your infinite loops (it could perhaps
find some of them...but never all), and so if the combination of your code and your data
produces an infinite loop, you will not be able to tell that until run-time

• Symptom 1: your program starts printing the same thing over and over and over, or printing
a lot of “junk” very rapidly for a long time.

• Symptom 2: your program appears to freeze and do nothing, because it is running the same
internal calculations over and over and thus is not printing anything out to you. (Careful,
though! The program might just be waiting for input! A good reason to use prompts...)
68

A counting example

We wish to print the integers from 1 through 10. This is the same as repeating the “print an
integer” action 10 separate times. If some variable i held our integer, we would print it using the
following statement:

System.out.println(i);

So, let’s put that in a loop:

while (condition)
System.out.println(i);

However, we want to increase i after each print statement, so that needs to be in the loop too:

while (condition)
{
System.out.println(i);
i++;
}

And finally, we need to have i start out at 1, and the loop should stop after i has passed 10 in
value.

int i = 1;
while (i <= 10)
{
System.out.println(i);
i++;
}
69

An input-based example

So, how can we write the program we discussed earlier? Remember, we want to read in grades one
by one, printing for each grade whether it is a passing or failing grade, until a negative number is
finally entered, at which point we stop.

int grade;
boolean notDone = true;
while (notDone)
{
System.out.println("Enter grade:");
grade = Keyboard.readInt();
if (grade < 0)
notDone = false;
else if (grade > 60)
System.out.println("Passed!");
else // grade >=0 and grade <= 60
System.out.println("Failed!");
}
System.out.println("Done!");

You could also have used grade itself in the condition. Right now, we change the value of the
boolean variable once grade is negative. Why not just directly check if grade is negative instead?

int grade = 0;
while (grade >= 0)
{
System.out.println("Enter grade:");
grade = Keyboard.readInt();
if (grade > 60)
System.out.println("Passed!");
else if (grade >=0) // we know grade <= 60
System.out.println("Failed!");
// else if grade < 0 we do nothing
}
System.out.println("Done!");

• If a negative grade is entered, nothing is printed, we go back to check the condition, and the
condition is false, so we exit the loop and print Done!.

• It could be argued that while this is slightly faster, the previous version is slightly easier to
read. Usually, in practice, both versions should be okay.
70

The for loop statement

There is another type of loop which separates some more of the loop control code from the body
of the loop. This type of loop is called a for-loop.

for (statement1; condition; statement2)


statement3;

Most commonly the above statements take the following roles:

for (initialization statement;


condition to continue;
‘‘alteration statement’’)
body-of-loop;

Loop equivalence

In a language, the while loop is really all you need. The for loop is simply a more convenient
form for many loops, but anything you can express using a for loop, you can express using a while
loop.

The loop:

for (statement1; condition; statement2)


statement3;

is equivalent to the code:

statement1;
while (condition)
{
statement3; <-- note body comes
statement2; before statement2
}
71

A counting example

Once again, we wish to print the integers from 1 through 10. Using the equivalence discussed on
the previous slide, we can convert our earlier while loop that did this printing, into a for loop
that does the same thing:

int i;
for (i = 1; i<=10; i++)
System.out.println(i);

• As the loop begins, the first statement inside the parenthesis gets run. That is the only time
that statement gets run.

• Next, we have a series of three steps:

1. Check condition. Exit the loop if the condition is false. Otherwise, continue with step
2.
2. Run the body of the loop. Move on to step 3.
3. Run the last statement in the parenthesis, then move on to step 1.

which runs over and over until step 1 finally causes the loop to exit.

Note that you can also declare the integer as part of the initialization statement:

for (int i = 1; i<=10; i++)


System.out.println(i);

and just as before, that initialization statement only gets run once. In the previous example, the
assignment was only run once, and now in this example, the declaration and assignment together
are only run once.
This is slightly different from the previous example in that the scope of the variable i is now
somewhat different. We will discuss that in just a moment.
72

The do-while loop

This is another loop form that, like the for loop, is sometimes more convenient to use than
the while loop and is sometimes not more convenient. The do-while loop is generally used when
you would have a while loop, but want to make sure the body of the loop gets run at least once.
That is because the do-while loop will always run the body of the loop once before checking the
condition.

do
statement;
while (condition);

is equivalent to:

statement;
while (condition)
statement;

Scope for loops

For the most part, the scope rules for loops are nothing new. If you have a one-line statement
inside your loop, then you treat that as if it had curly braces around it – just as we mentioned
with the if-statement. That is, the following two code segments are considered equivalent, for the
purposes of scope:

while (grade < 60) while (grade < 60) {


int i = 6; int i = 6;
}

And, just as with if-statements, scope with compound statements within loops works just as we’ve
described before. For example:

int i = 7; <--- i declared outside the loop


while (i < 10)
{
int x = 5; <--- x declared inside the loop
i++; <--- i usable inside the loop
System.out.println(x); <--- x usable inside the loop
}
System.out.println(i); <---- i usable later, outside the loop
System.out.println(x); <---- this line is wrong; x is out of scope!
73

A special situation that needs addressing

Because you can declare a variable inside the parenthesis of a for loop, we must decide exactly
what the scope of that variable would be. And it turns out that the scope of the parenthesis of a
for loop ends when the loop itself ends.

for (int i = 1; i<=10; i++)


System.out.println(i);
System.out.println(i); // <-- won’t compile;
// the variable i is
// no longer
// accessible at this
// point in the code;

You could think of this as follows, if it will help you get a clearer idea of the scope involved:

{ // start a stand-alone block


int i;
for (i = 1; i <= 10; i++)
System.out.println(i);
} // stand-alone block ends; i is gone
System.out.println(i); // and so this line
// won’t compile

Example 4:

for (int i = 1; i<=10; i++) <---- i declared


{ *inside* the loop
int x = 9; <--- x declared
System.out.println(x); <--- and used
inside the loop
}
System.out.println(x); <-- ERROR!
System.out.println(i); <-- ERROR!
Neither variable is
accessible here; x went out of scope
every time the compound statement ended,
and i went out of scope when the loop
ended (i.e. when the loop condition
was evaluated to false)
74

Example 5: multiple nestings

int y = 35;
while (y >= 0)
{
int x = y;
if (x % 2 == 0)
{
int z = x;
System.out.print(z + " even");
y = y - 2; // this will indeed alter y
}
else
{
// z is NOT in scope here
int w = x;
System.out.print(w + " odd");
y = y - 1; // this will indeed alter y
}
y = y - 1; // this will indeed alter y
// neither z NOR w are in scope here
}
// neither z NOR w NOR x are in scope here

Every alteration to y is permanent, since y is in scope for every block. When we first enter the
while loop, y is 35, but due to subtractions inside the loop, y will not remain at 35.

Control Flow Fact

Any control flow you might want through your program can be created using a combination of:

1. Sequential execution

2. Conditionals

3. Loops

That’s all you need!! And so that’s all we’ll look at. There are no more “necessary” control flow
constructs. The rest of what we will look at are constructs designed to make programming more
organized and programs easier to write and maintain. But none of it is actually necessary in the
sense of “needing to be built into the hardware in order for programs to work”.
75

Lecture 8 : One-Dimensional Arrays


The need for arrays

An example we can do, given our knowledge so far: “For a given student, read 10 exam scores
and print out the total of those exam scores.” In this case, we didn’t need to save the exam scores;
we could just add them to the same variable one by one:

int total = 0;
for (int i = 1; i<=10; i++)
{
System.out.print("Enter score #" + i + ": ");
total = total + Keyboard.readInt();
}
System.out.println("Total is " + total + ".");

New problem: “For a given student, read 10 exam scores, print their total, and then print each
exam score as well.” Now, the above will not do! We haven’t saved the values so we have no way
to print them out. We know their total, but there is no way to obtain each of the ten individual
exam scores from that.

Since the above doesn’t work, we need to try something else. One thing we could do is to save
the exam scores as we read them. Since there are ten exam scores, we’d have to save them in 10
separate variables, and then later, print those 10 variables out one by one.

int score1, score2, score3, score4, score5, score6, score7, score8,


score9, score10, total;

System.out.print("Enter score #1: ");


score1 = Keyboard.readInt();
System.out.print("Enter score #2: ");
score2 = Keyboard.readInt();
System.out.print("Enter score #3: ");
score3 = Keyboard.readInt();
... // similar code for scores 4-9
System.out.print("Enter score #10: ");
score10 = Keyboard.readInt();

total = score1 + score2 + score3 + score4 + score5 + score6 +


score7 + score8 + score9 + score10;

System.out.println("Total is " + total + ".");

System.out.println("Score #1 is " + score1 + ".");


System.out.println("Score #2 is " + score2 + ".");
System.out.println("Score #3 is " + score3 + ".");
... // similar code for scores 4-9
System.out.println("Score #10 is " + score10 + ".");
76

but that has two problems:

• We have to write out the prompt and input statments 10 separate times. There is no way
to use a loop, since each input requires a slightly different variable name. Likewise, we need
10 print statements – we cannot use a loop, since each print statement is printing a slightly
different variable name.

• This repetitive work is bad enough for only 10 variables. What if there were 100? 1000?
What if we didn’t know in advance how many we would need?

What we would like to have is a way of writing the previous code, but without the two problems
we describe above. Note that all the variables score1 through score10 are very similar in name
– the only thing different is the number at the end. What if we had some way to make the
computer recognize this? What if we could simply use the variable name “score” along with some
number, and the computer could easily put the name “score” and the number together, to get the
appropriate full variable name, such as score1 or score4 or score8?

Arrays

Our solution will be very similar to that idea. What we will discuss today is a computer language
tool known as an array. An array is essentially a collection of variables – called cells in this context
– which are all of the same type and are all part of one collection with a single name. For example:

_______________________________
| | | | | | |
arr | | | | | | |
|____|____|____|____|____|____|

0 1 2 3 4 5

Above we see an array, whose name is arr. There are six cells in this array, each with an
associated integer called an index, or less commonly, a subscript. The indices of our six cells are 0,
1, 2, 3, 4, and 5, respectively. Every cell will have an index, the set of indices is always a range of
consecutive integers, and always (in Java) starts at 0. So, an array with 10 cells would have them
indexed 0 through 9, and array with 148 cells would have them indexed 0 through 147, and so on.
Above, our array of six cells has them indexed 0 through 5.
An array is created with a line such as the following:

int[] arr = new int[6];

This line tells the computer to create an array named arr, and to have that new array contain 6
cells (so the indices are therefore 0 through 5), and to have those cells be of type int. In general,
the form of such a statement is:

SomeType[] arrayName = new SomeType[ExpressionForNumberOfCells];

where SomeType is whatever type you want (int in the example above), arrayName is a variable
name of your choosing (arr in the example above), and ExpressionForNumberOfCells is some
expression that evaluates to a non-negative integer (in the example above, our integer literal 6
evaluates to a non-negative integer, namely, 6, so that is fine as well). So, another example of an
array creation would be the third line in the following code snippet:
77

int a = 10;
int b = 5;
double[] results = new double[a+b]; // array created

In that example, the array cells are of type double, the array name is results, and there are
15 cells total, and so those cells would have indices 0 through 14. We will explain a bit more about
this syntax – including why it looks like an assignment statement – in the coming lectures, but for
now, you at least know what line you would type to create an array.
You refer to a particular cell in a particular array via a combination of the name and an index.
Each name can only refer to one array – that is, just as you can’t have two int variables both named
theValue, you can’t have two arrays both named arr. A name will identify exactly one particular
array. And then the index uniquely identifies a cell within that array. So, the combination of the
two – name and index together – uniquely identifies an array cell.
You indicate this combination of name and index using square brackets ( [ ] ). You have
the following form:

arrayName[indexExpression]

where arrayName is the name of your array, and indexExpression is some expression that evaluates
to an index of the array. For example:

arr[0] // this expression gives you cell 0 of


// the array in the above picture
arr[1] // this expression gives you cell 1 of
// the array in the above picture
arr[4] // this expression gives you cell 4 of
// the array in the above picture
arr[2+3] // this expression gives you cell 5 of
// the array in the above picture
arr[2*(x-r)] // if x holds 3 and r holds 1, this expression
// gives you cell 4 of the array in the above
// picture
arr[7] // this expression will make your program
// crash when it runs, since 7 is not an
// index in the array named ‘‘arr’’

You can use this name-index combination as a variable name, to either write to or read from.
So, just as you could write statements using single variables such as the following:

int a;
a = 2; // assigning the variable
System.out.println(a); // printing the variable

you can write similar statements such as:

int[] arr = new int[6];


arr[4] = 17; // assigning the array cell
System.out.println(arr[4]); // printing the array cell

The first line will create the array:


78

_______________________________
| | | | | | |
arr | | | | | | |
|____|____|____|____|____|____|

0 1 2 3 4 5

The second line will write the value 17 into cell 4 of that array:

_______________________________
| | | | | | |
arr | | | | | 17 | |
|____|____|____|____|____|____|

0 1 2 3 4 5

and the third line will read the value 17 from cell 4 of that array and print that value 17 to the
screen.
Likewise, the following statements:

arr[0] = 13;
arr[1] = 128;
arr[2] = -10;
arr[3] = 99;
arr[5] = 0;

would then write values into the remaining cells of the array.

_______________________________
| | | | | | |
arr | 13 |128 |-10 | 99 | 17 | 0 |
|____|____|____|____|____|____|

0 1 2 3 4 5
79

Solving our earlier problem


So now, let’s solve that earlier problem. Instead of having 10 different integer variables, let’s
simply create one array with 10 cells:
int[] scores = new int[10]; // indices 0 through 9
Our code for the prompting and inputting would now look like this:
System.out.print("Enter score #1: ");
scores[0] = Keyboard.readInt();
System.out.print("Enter score #2: ");
scores[1] = Keyboard.readInt();
System.out.print("Enter score #3: ");
scores[2] = Keyboard.readInt();
... // and so on for cells 3 through 9
except now, since the index can be an expression, we can use a loop!
for (int i = 1; i <= 10; i++)
{
System.out.print("Enter score #" + i + ": ");
scores[i-1] = Keyboard.readInt();
}
Then, we only need to take care of totalling and printing and we’re all set. The finished code
snippet is as follows:
int[] scores = new int[10];
int total = 0;
for (int i = 1; i <= 10; i++)
{
System.out.print("Enter score #" + i + ": ");
scores[i-1] = Keyboard.readInt();
}

for (int i = 1; i <= 10; i++)


total = total + scores[i-1];
System.out.println("Total is " + total + ".");

for (int i = 0; i < 10; i++)


System.out.println("Score #" + (i+1) + " is " + scores[i] + ".");
Note that the loops can either run from 1 through 10, or from 0 through 9 – you only need to alter
the index expression in the body of the loop to take the particular loop range into account.

The two problems we had earlier have now gone away:


• We no longer need to repeat the same snippet of code 10 times; instead, we can use a loop to
have a variable i incremement each time through the loop, and use an expression involving
i for the array index.
• If we wanted to change this to 100, or 1000 scores, rather than just 10, all we would need to
do is change the “10”s above to a different number.
80

In fact, since the size of the array can also be an expression, it is possible to have read in the
size as user input. The following program will work for any number of scores.

public class ScorePrinting


{
public static void main(String[] args)
{
int numberOfScores;
System.out.print("How many scores are there?: ");
numberOfScores = Keyboard.readInt();

int[] scores = new int[numberOfScores];


int total = 0;

for (int i = 1; i <= numberOfScores; i++)


{
System.out.print("Enter score #" + i + ": ");
scores[i-1] = Keyboard.readInt();
}

for (int i = 1; i <= numberOfScores; i++)


total = total + scores[i-1];
System.out.println("Total is " + total + ".");

for (int i = 0; i < numberOfScores; i++)


System.out.println("Score #" + (i+1) + " is " + scores[i] + ".");
}
}
81

The length variable

Every array has a built-in length variable that tells you how many cells the array has. You
access this length variable by using the array name, followed by a period, followed by the name
length:

arr.length

This length value is initialized automatically whenever we create an array. So, as soon as we
executed a statement such as:

int[] scores = new int[10];

then from that point on, the expression scores.length will always evaluate to 10, since that is
how many cells the array has. We could have written the previous code slide as follows:

public class ScorePrinting


{
public static void main(String[] args)
{
int numberOfScores;
System.out.print("How many scores are there?: ");
numberOfScores = Keyboard.readInt();

int[] scores = new int[numberOfScores];


int total = 0;

for (int i = 1; i <= scores.length; i++)


{
System.out.print("Enter score #" + i + ": ");
scores[i-1] = Keyboard.readInt();
}

for (int i = 1; i <= scores.length; i++)


total = total + scores[i-1];
System.out.println("Total is " + total + ".");

for (int i = 0; i < scores.length; i++)


System.out.println("Score #" + (i+1) + " is " + scores[i] + ".");
}
}
82

Lecture 9 : Multi-Dimensional Arrays


Multi-dimensional arrays

Up to now we have been talking about one-dimensional arrays (or alternatively, single-dimensional
arrays. These arrays are by far the most common type of array, and so often we just say “array”
when we mean this kind of array.
However, it is possible to have arrays of multiple dimensions as well. For example, if you would
find it useful to have an array with both rows and columns (i.e. with two dimensions):
____________________________________
| | | | | |
0 | | | | | |
|______|______|______|______|______|
| | | | | |
1 | | | | | |
|______|______|______|______|______|
| | | | | |
2 | | | | | |
|______|______|______|______|______|
| | | | | |
3 | | | | | |
|______|______|______|______|______|

0 1 2 3 4
or an array with three dimensions:
___________________________________
/ / / / / /|
1 / / / / / / |
/ / / / / / |
/_____ /______/______/______/______/ |
/ / / / / /| /|
0 / / / / / / | / |
/ / / / / / |/ |
/______/______/______/______/______/ / /|
| | | | | | /| / |
0 | | | | | | / |/ |
|______|______|______|______|______|/ / /
| | | | | | /| /
1 | | | | | | / |/
|______|______|______|______|______|/ /
| | | | | | /
2 | | | | | | /
|______|______|______|______|______|/

0 1 2 3 4
then you can create such an array in Java!
83

The array creation syntax we discussed in the last notes packet – for one-dimensional arrays –
was a special case of the more general array creation syntax. In general, the syntax for creating an
array of D dimensions is as follows:

Type[][][]...[] varname = new Type[size1][size2][size3]...[sizeD];


___________ _______________________________
D of these D of these

The size1, size2, etc. expressions in the square brackets above, are the sizes of the different
dimensions in your array (for our purposes, those expressions will evaluate to positive integers; we
will only allow the size to be 0 when dealing with one-dimensional arrays). For each dimension,
the indices are a consecutive range of integers beginning at 0 and ending at size - 1, where size
is the size of that dimension. So, for the array created by the above syntax, the first dimension has
indices 0 through size1 - 1, the second dimension has indices 0 through size2 - 1, the third
dimension would have indices 0 through size3 - 1, all the way up to dimension D, which has
indices from 0 through sizeD - 1.
For multi-dimensional arrays, such as the two pictures you saw earlier, you decide which will
be the first dimension, and which will the second dimension, and so on (if there are more than two
dimensions), and then use the general syntax above. For example, for a two-dimensional array, we
simplify the general syntax to two sets of brackets:

Type[][] varname = new Type[sizeOfFirstDimension][sizeOfSecondDimension];

In the picture of the two-dimensional array that we had earlier in this notes packet, if we wanted
the number of rows in our picture to be the first dimension in our array (note there are four rows)
and we wanted the number of columns in our picture to be the second dimension in our array (note
there are five columns), and we wanted to hold values of type int, then we would write a statement
such as the following one, in order to create that array:

int[][] theMatrix = new int[4][5];

and we have now created a two-dimensional array, with a variable name theMatrix, which has
space for twenty integers. If we wanted to create an array to match the three-dimensional example
from above, and we wanted the dimension with three cells (indexed with 0, 1, and 2) to be the first
dimension, and we wanted the dimension with five cells (indexed with 0, 1, 2, 3, and 4) to be the
second dimension, and we wanted the dimension with two cells (indexed with 0 and 1) to be the
third dimension, and wanted the array to hold values of type char, then in order to create such an
array, we would use the following statement:

char[][][] threeDarray = new char[3][5][2];

Note that the syntax for one-dimensional arrays – which you learned in the last notes packet – is
just the above syntax, with D being equal to 1, since there is only one dimension. So, you would
have exactly one pair of square brackets before the variable name, and one pair of square brackets
with a size in it after the assignment operator – exactly as you saw in the previous notes packet:

Type[] varname = new Type[size1];


__ _______
1 of these 1 of these
84

Accessing cells of multi-dimensional arrays

Just as the syntax for creating a one-dimensional array was a special case of the more general syntax
for creating a D-dimensional array, the syntax for accessing a one-dimensional array is a special case
of the more general syntax for accessing a D-dimensional array. Accessing a D-dimensional array is
done with the following expression:
varname[index1][index2]...[indexD]
___________________________
D of these
In the case of a two-dimensional array, you would have two sets of brackets:
varname[index1][index2]
In the case of a three-dimensional array, you would have three sets of brackets:
varname[index1][index2][index3]
and so on.
In the case of a one-dimensional array, that just simplifies to what you learned earlier, since there
would be only one set of brackets:
varname[index1]
________
1 of these
To give an example, if we want to access row 1, column 3 of our two-dimensional array from
the earlier examples (counting row 0 as the first row and column 0 as the first column, since all
index ranges always start with 0), we would use the following expression:
theMatrix[1][3]
So, we could write the value 17 into that cell using the following assignment statement:

theMatrix[1][3] = 17;

and that would give us the following picture:


____________________________________
| | | | | |
0 | | | | | |
|______|______|______|______|______|
| | | | | |
1 | | | | 17 | |
|______|______|______|______|______|
| | | | | |
2 | | | | | |
|______|______|______|______|______|
| | | | | |
3 | | | | | |
|______|______|______|______|______|

0 1 2 3 4
85

If we then wanted to print out the value of that cell to the screen, we would use the following
statement”

System.out.println(theMatrix[1][3]);

Typically, if you are viewing pictures of two-dimensional arrays, you see the indices of the first
dimension used to select a row, and then the indices of the second dimension area used to select
a column within that row. However, it is important to point out that, even though that is the
“typical” way you tend to see examples, it is not necessary to think of things that way. All that
matters is that you are consistent.
For example, go back to the earlier statement that created a two-dimensional array:

int[][] theMatrix = new int[4][5];

What is important about that statement, is that the first dimension is of size 4 (and thus has
indices 0 through 3) and the second dimension is of size 5 (and thus has indices 0 through 4).
Making sure you only use an index from 0 through 3 for the first dimension, and making sure you
only use an index from 0 through 4 for the second dimension, is what is important. If you want
to picture the first dimension as indexing the rows, then you’d get this picture (which you saw
earlier):

____________________________________
| | | | | |
0 | | | | | |
|______|______|______|______|______|
| | | | | |
1 | | | | | |
|______|______|______|______|______|
| | | | | |
2 | | | | | |
|______|______|______|______|______|
| | | | | |
3 | | | | | |
|______|______|______|______|______|

0 1 2 3 4
86

But if you instead wanted to picture the first dimension as indexing the columns, you’d have
this picture instead, and that would be fine too:

_____________________________
| | | | |
0 | | | | |
|______|______|______|______|
| | | | |
1 | | | | |
|______|______|______|______|
| | | | |
2 | | | | |
|______|______|______|______|
| | | | |
3 | | | | |
|______|______|______|______|
| | | | |
4 | | | | |
|______|______|______|______|

0 1 2 3

The important thing is that, whatever dimension – rows or columns – you decide will be your
first dimension, that is always what must be your first dimension, for as long as the array exists.
The code is only defined in terms of “first dimension” and “second dimension”. It is you that
decides whether defining the first dimension as indexing the “rows”, or the “columns”, makes more
sense to you. So just as with any other definition, once you decide what “row” or “column” means
to you, you need to be consistent.
For example, given the two-dimensional array you just created, if you try to make the following
assignment:

theMatrix[1][3] = 17;

then the cell you are accessing has index 1 in the first dimension, and index 3 in the second
dimension. That is what is important, and relevant. Whatever you chose for the first dimension
– rows or columns – you are accessing index 1 in that dimension, and whatever you chose for the
second dimension – rows or columns – you are accessing index 3 in that dimension.
So, if you decided that the first dimension would index your rows, and thus that you have four
rows, and five columns, then that assignment would select row 1 and column 3 and write a 17 into
that cell, because the assignment selects index 1 from your first dimension, and you have decided
that your first dimension indexes the “rows”:
87

____________________________________
| | | | | |
0 | | | | | |
|______|______|______|______|______|
| | | | | |
1 | | | | 17 | |
|______|______|______|______|______|
| | | | | |
2 | | | | | |
|______|______|______|______|______|
| | | | | |
3 | | | | | |
|______|______|______|______|______|

0 1 2 3 4

On the other hand, if you decided that the first dimension would index your columns, and thus
that you have four columns and five rows, then that assignment would select column 1 and row 3,
and write a 17 into that cell, because the assignment selects index 1 from your first dimension, and
you have decided that your first dimension indexes the “columns”:

_____________________________
| | | | |
0 | | | | |
|______|______|______|______|
| | | | |
1 | | | | |
|______|______|______|______|
| | | | |
2 | | | | |
|______|______|______|______|
| | | | |
3 | | 17 | | |
|______|______|______|______|
| | | | |
4 | | | | |
|______|______|______|______|

0 1 2 3

In both examples, the index of the first dimension is 1. Whether that means “row 1” or “column 1”
depends entirely on whether you decided the first dimension should index “rows” or “columns”. It
has nothing to do with the language itself. The language itself is only accessing index 1 in the first
dimension, since the language only deals in concepts like “first dimension” and “second dimension”,
and does NOT deal in concepts like “rows” and “columns”.
88

The use of .length in multi-dimensional arrays

Imagine we created a two-dimensional array as in our earlier examples:

int[][] theMatrix = new int[4][5];

Furthermore, imagine we have written values into all twenty cells, as follows:

____________________________________
| | | | | |
0 | 3 | 6 | 9 | 12 | 15 |
|______|______|______|______|______|
| | | | | |
1 | 18 | 21 | 24 | 27 | 30 |
|______|______|______|______|______|
| | | | | |
2 | 33 | 36 | 39 | 42 | 45 |
|______|______|______|______|______|
| | | | | |
3 | 48 | 51 | 54 | 57 | 60 |
|______|______|______|______|______|

0 1 2 3 4

What value will the expression:

theMatrix.length}

produce? How do we obtain the length of the first dimension (which is 4)? What code would we
write to obtain the length of the first dimension (which is 4)? What code would we write to obtain
the length of the second dimension (which is 5)?
The best way to answer these questions is with the picture on the following page, in which the
first dimension indexes the rows, and the second dimension indexes the columns. It’s a good way
to think about a two-dimensional array. Imagine it as an array of arrays – where each row is a
one-dimensional array holding the items in that row, and then there is another array holding all of
those rows as its items. Look at the above picture, and then look at the picture on the next page,
to see how we can view each row above as a one-dimensional array of size 5, being stored in either
cell 0, 1, 2, or 3 of an array of size 4.
89

________
| __|_________________________________
| | | | | | |
| | 3 | 6 | 9 | 12 | 15 |
0 | |______|______|______|______|______|
| |
| | 0 1 2 3 4
|______|
| __|_________________________________
| | | | | | |
| | 18 | 21 | 24 | 27 | 30 |
1 | |______|______|______|______|______|
| |
| | 0 1 2 3 4
|______|
| __|_________________________________
| | | | | | |
| | 33 | 36 | 39 | 42 | 45 |
2 | |______|______|______|______|______|
| |
| | 0 1 2 3 4
|______|
| __|_________________________________
| | | | | | |
| | 48 | 51 | 54 | 57 | 60 |
3 | |______|______|______|______|______|
| |
| | 0 1 2 3 4
|______|

Given the above picture, imagine for a moment that the name of the two-dimensional array,
theMatrix, is instead the name of the one-dimensional array of size 4 that is drawn vertically
in the above picture. If that is the case, then we can consider an array access such as:

theMatrix[1][3]

to be composed of two parts. The first part is selecting index 1 from the first dimension – i.e.
accessing index 1 of the one-dimensional array of size 4 that is drawn vertically in the picture
above:

theMatrix[1]

As we would expect for any one-dimensional array, this expression will return the item at cell 1
of the array drawn vertically in the picture above. And, what is at cell 1 of that array? Another
array!
90

|______|
| __|_________________________________
| | | | | | |
| | 18 | 21 | 24 | 27 | 30 |
1 | |______|______|______|______|______| <---- array returned
| | by theMatrix[1]
| | 0 1 2 3 4
|______|
| |

And then, the one-dimensional array we have obtained in this matter, has its cell with index 3
accessed.

theMatrix[1][3]
____________ /|\
gives you the |
1-D array of |
size 5 above |______gives cell 3 of that 1-D array

giving you the cell that currently holds 27:

|______|
| __|_________________________________
| | | | | | |
| | 18 | 21 | 24 | 27 | 30 |
1 | |______|______|______|______|______| <---- array returned
| | by theMatrix[1]
| | 0 1 2 3 4
|______|
| | /|\
|
|
|
array cell returned
by theMatrix[1][3]

That is, it sometimes helps to think of a two-dimensional array as an array of arrays. Think of
the name of the two-dimensional array as the name of the vertical array in the previous pictures.
That is, the name of the array is the “name of the first dimension”, and then the cells of that
first-dimension array, hold the actual rows representing the second dimension of the array.
That would mean that the expression theMatrix.length should evaluate to the length of that
array that the name theMatrix refers to – namely, the vertical array in our pictures above, the first
dimension of the array. And in fact, that is exactly what it does. The expression theMatrix.length
will return the length of the first dimension of the array. In our examples above, the length of the
first dimension is 4, and so that is exactly what the expression theMatrix.length evaluates to –
the value 4.
So how can we get the length of the rows themselves? – i.e. the number of columns? – i.e. the
length of the second dimension? Well, remember that the expression:
91

theMatrix[1]

in a sense gave us the particular row at index 1, so that we could then access cell three of that
array by adding a [3] to the end of that expression:

theMatrix[1][3]

So, instead of accessing the cell at index 3 of that row, why not try and obtain the length of the
row instead? If we had the variable arr which was the name of an array, and we wanted to access
cell 3, we’d use:

arr[3]

and if we instead wanted the length of the array, we’d use:

arr.length

Well, instead of arr being the name of an array, we are now using:

theMatrix[1]

to access the one-dimensional array representing the row with index 1, so why not use:

theMatrix[1].length

to get the length of that row? And, in fact, that is what we do! Since the following expressions:

theMatrix[0]
theMatrix[1]
theMatrix[2]
theMatrix[3]

would return the one-dimensional arrays representing the rows at index 0, index 1, index 0, and
index 3, respectively...then the expressions:

theMatrix[0].length
theMatrix[1].length
theMatrix[2].length
theMatrix[3].length

will return the lengths of the one-dimensional arrays representing the rows at index 0, index 1,
index 0, and index 3, respectively. And, of course, all those rows have a length of 5, so each of the
four expressions involving .length above, evaluates to 5.
92

So, that is how you obtain the length of the first dimension and second dimension of some
two-dimensional array twoDarray. The length of the first dimension is given by:

twoDarray.length

and the length of the second dimension would be given by

twoDarray[0].length

or by any similar expression, where in place of 0 you had some other index that was within the
legal index range for the first dimension (an index between 0 and 3, inclusive, in the four row, five
column array of our earlier example).
And then likewise, for a three-dimensional array threeDarray, the following expressions:

threeDarray.length
threeDarray[0].length
threeDarray[0][0].length

would give you the lengths of the first, second, and third dimensions, respectively – due to much
the same reasoning as we used earlier to explain the two-dimensional array example.
93

An example – initializing a two-dimensional array

Assume we have created a two-dimensional array, where rows are the first dimension and
columns are the second dimension:

int[][] theMatrix = new int[4][5]; // array from previous examples

Furthermore, assume we would like to write the value 37 into every one of the twenty cells of this
array. How could we do it?
Well, the key to this problem will be to use loops, since we want to repeat an action (writing
37 into an array cell) over and over again. To begin with, we note that if we are writing 37 into
every array cell, that will involve writing 37 into every array cell in row 0, writing 37 into every
array cell in row 2, and writing 37 into every array cell in row 3. Or in other words, we can begin
our structuring of the solution, as follows:

for each row from 0 through 3


write 37 into every cell of that row

which we can then translate into a for-loop:

for (int r = 0; r <= 3; r++)


write 37 into every cell of row r

Now, if we want to write 37 into every cell of row r, well, that itself is a repetitive process.
There are many columns in that row, and we are writing 37 into the cell for each column in that
row. If we were doing that for a stand-alone one-dimensional array of size 5 named arr:

for every index in arr from 0 through index 4


write 37 into the cell of arr at that index

and the way to do that for a one-dimensional array would be as follows:

// how to write 37 into every cell of some array arr with


// indices 0 through 4:
for (int i = 0; i <= 4; i++)
arr[i] = 37;

We saw code like that in the previous lecture packet, when talking about one-dimensional arrays.
And, the only difference between our situation and that one, is that rather than dealing with
a stand-alone, one-dimensional array, we are trying to write 37 into indices 0 through 4 of a one-
dimensional array that is part of a two-dimensional array. We are writing 37 into every cell in
a particular row...but that isn’t much different than writing 37 into every cell of a stand-alone
one-dimensional array!

// given a row r, write 37 into every cell in that row


for all columns c from 0 through 4
write 37 into the cell with row r and column c

or, once converted to a for-loop:

// assuming you know the index r of your row


for (int c = 0; c <= 4; c++)
theMatrix[r][c] = 37;
94

That code will write 37 into every column of a given row r in our example array. Then, to do that
for every row, we just use the above loop as the statement of our earlier loop:

for (int r = 0; r <= 3; r++)


for (int c = 0; c <= 4; c++)
theMatrix[r][c] = 37;

and we are done!


Remember that the entire statement for a loop (the entire body of the loop) runs from start to
finish before the condition is checked again. And, in the case of a for-loop, the entire body of the
loop is run before even the “alteration statement” (the r++ or c++ above) is run, let alone before
the condition is checked. So, that means above, for the outer for-loop:

for (int r = 0; r <= 3; r++)

the statement for that loop – i.e. the inner for-loop – is run from start to finish before r is
incremented or r is compared to 3 again.
This means that our progression of events is as follows:
95

r is initialized to 0
r is compared to 3
enter body of loop
c is initialized to 0
c is compared to 4
enter body of loop
write 37 into (0, 0)
c is incremented to 1
c is compared to 4
enter body of loop
write 37 into (0, 1)
c is incremented to 2
c is compared to 4
enter body of loop
write 37 into (0, 2)
c is incremented to 3
c is compared to 4
enter body of loop
write 37 into (0, 3)
c is incremented to 4
c is compared to 4
enter body of loop
write 37 into (0, 4)
c is incremented to 5
c is compared to 4
loop ends!
r is incremented to 1
r is compared to 3
enter body of loop
c is initialized to 0
c is compared to 4
enter body of loop
write 37 into (1, 0)
c is incremented to 1
c is compared to 4
enter body of loop
write 37 into (1, 1)

(and so on...)
96

The inner loop runs from start to finish, writing 37 into every column of an entire row, before the
row index (the index of the outer loop) is incremented to give us a new row.
This is the general structure for traversing a two-dimensional array – use two loops, one nested
in the other, to traverse over every column in a row, for every row. If you wanted to do something
other than write 37 into each cell, you’d follow the same pattern. For example, if we wanted to
write the first twenty positive multiples of 3 into our array (as we had in an earlier picture in this
packet), you’d move across the array cell by cell in the same manner, only setting up your next
value to write into the array before you actually wrote into the array. That is, you would start out
with the general form:

for (int r = 0; r <= 3; r++)


for (int c = 0; c <= 4; c++)
// whatever you want to do for each cell goes here

and then, the “whatever you want to do for each cell” is, “figure out the next multiple of 3 and
write it into that cell”. The next multiple of 3, will be the previous multiple of 3, plus 3. (For
example, given 21, we add 3 to get 24, then add another 3 to get 27, and so on.) So, let’s have a
counter keep track of the multiple of 3 that we care about at the moment:

int counter = 0;
for (int r = 0; r <= 3; r++)
for (int c = 0; c <= 4; c++)
{
counter = counter + 3;
theMatrix[r][c] = counter;
}

Note that we needed to supply curly braces to make a compound statement, since now we are
trying to run two statements – counter = counter + 3;, and the array cell assignment – as the
body of the inner for-loop. However, the inner for-loop itself is still one statement, so the outer
for-loop does not need curly braces around its statement (i.e we don’t need curly braces around
the inner for-loop itself). That said, sometimes it helps make the code easier to read if we put
them in, even if they aren’t necessary. So, the above code could also be written as:

int counter = 0;
for (int r = 0; r <= 3; r++)
{
for (int c = 0; c <= 4; c++)
{
counter = counter + 3;
theMatrix[r][c] = counter;
}
}

In either case, the progression of events would be as follows:


97

counter is declared and initialized to 0


r is initialized to 0
r is compared to 3
enter body of loop
c is initialized to 0
c is compared to 4
enter body of loop counter is incremented by 3
write 3 into (0, 0)
c is incremented to 1
c is compared to 4
enter body of loop
counter is incremented by 3
write 6 into (0, 1)
c is incremented to 2
c is compared to 4
enter body of loop
counter is incremented by 3
write 9 into (0, 2)
c is incremented to 3
c is compared to 4
enter body of loop
counter is incremented by 3
write 12 into (0, 3)
c is incremented to 4
c is compared to 4
enter body of loop
counter is incremented by 3
write 15 into (0, 4)
c is incremented to 5
c is compared to 4
loop ends!
r is incremented to 1
r is compared to 3
enter body of loop
c is initialized to 0
c is compared to 4
enter body of loop
counter is incremented by 3
write 18 into (1, 0)
c is incremented to 1
c is compared to 4
enter body of loop
counter is incremented by 3
write 21 into (1, 1)

...and so on. Note that when we start writing values into the row with index 1, we start with 18.
The variable counter was declared outside the outer for-loop, so the changes we make to it inside
98

the loops are permanent. Every time we add 3 to the current value of counter, we are adding 3
to the most recent value of counter, which is the value it held after the most recent assignment
to counter – which is also the value we most recently wrote into the array. Now, if instead, we
wanted to write 3, 6, 9, 12, and 15 into each of the four rows of the array, then we would need to
reassign counter to zero just before starting the inner loop each time. That way, the first cell of
each row would always have 3 assigned to it, because just before that assignment, we had run the
line counter = counter + 3;, and if counter was 0 before that line was run, then after it was
run counter would be 3. The full example for this would be as follows:

int counter;
for (int r = 0; r <= 3; r++)
{
counter = 0;
for (int c = 0; c <= 4; c++)
{
counter = counter + 3;
theMatrix[r][c] = counter;
}
}

That code would write 3, 6, 9, 12, and 15 into each row of the array.
99

Three additional issues with arrays

• One interesting and useful property of arrays, is that it takes the same amount of time to
access an array cell no matter what the index is. For example, if you have a one-dimensional
array arr of size 10000, the first and last cells are obtained via the expressions arr[0] and
arr[9999], respectively. You might think it would take longer to access arr[9999], since
it’s further down the array, but actually, whether the index is 0 or 9999 or 4999 makes no
difference, with respect to how long it takes the computer to access the array cell. Now, the
reasons this is true are beyond the scope of this course – you need to learn a bit more about
computer hardware before you can fully understand why this is true. But at any rate, it is
indeed true. This means that when you write code using arrays, you don’t need to be shy
about using cells with higher indices – writing a value into arr[9999] rather than arr[0] is
NOT going to “slow down” your program. Treat each array cell, no matter the index, as if is
just as easily accessible as any other array cell.

• Once an array is created, you cannot change its size. So, for example, the following would
not work:

int[] x = new int[5];


.
.
.
x.length = 10;

In fact, that code would not even compile! If you need an array of size 10 at that point in
your program, you will need to create a second array – there is no way to expand the size of
the first array.

// this is how you would have to do this


int[] x = new int[5];
.
.
.
int[] y = new int[10];

• The assignment operator does not work for arrays the same way it does for single variables.
That is, if you had created two arrays of the same size:

int[] x = new int[6];


int[] y = new int[6];

and then wrote different values into those array cells, if you later want to make the two arrays
hold the same values, you should NOT do this:

x = y;

Instead, you need to copy the values cell by cell, as in the following code:
100

// this code assumes both arrays have at least 6 cells, indexed


// 0 through 5
for (int i = 0; i <= 5; i++)
x[i] = y[i];

There are reasons for this that we will discuss soon; for now, just know that assignment of an
entire array to another entire array needs to be done one array cell at a time, not all at once.
101

Lecture 10 : Processing Data Collections


Arrays and Loops

As you have already started to see, a great deal of our array code will rely on loops. This is
because the loop will allow us to progressively increase (or decrease) a subscript variable of an array
arr. As the variable increases from 0 through arr.length - 1, we will be able to access each cell
of the array in turn. That is helpful because much of the loop processing code we write will be of
the following form:

given a collection of array cells that we care about,


do some sort of particular work on every cell in that
collection

This usually gets set up using a for-loop statement. Whatever collection of array cells we have, we
can consider the lowest-indexed cell as having index “lo”, and the highest-indexed cell as having
index “hi”:

___________________________________
| | . . . | |
|____|________________________|___|
lo . . . hi

and in that case, the control for our for-loop could be set up as follows:

for (int i = lo; i <= hi; i++)

For example, if we have an array arr of size cells, and we want to perform work on all the cells,
then our loop will need to traverse the entire array:

___________________________________
| | . . . | |
arr |____|________________________|___|
0 . . . size-1

for (int i = 0; i <= size - 1; i++)

We could also access the length value for the array:

for (int i = 0; i < arr.length; i++)

But if, for example, you didn’t want to process the cell with index 0, and only wanted to process
the cells indexed 1 through size - 1 (that is, 1 through arr.length - 1), then you’d just have
“lo” be 1:

___________________________________
| | | . . . | |
arr |____|____|___________________|___|
0 1 . . . size-1

|____________________________|
here is the part we care about
102

and so in that case, the for-loop would start at 1, not 0:

for (int i = 1; i <= size - 1; i++)

So, whenever we are dealing with processing “every cell in our collection”, we need to decide
which cells are actually in the collection we care about, and then just have a for-loop run from the
lowest index of those cells, to the highest index of those cells.
After that, we can consider writing most array-based code as a four step process:

1. Figure out what we want to do for each cell. Presumably we are traversing across the array
to perform some particular work at each cell; what is that work? For some simpler problems,
this step is easily figured out from the problem statement itself; for other, harder problems,
this step is the hardest part of writing array code.

2. If we wanted to perform that work on one ordinary variable (i.e. a variable that we had
declared on its own, rather than a variable that was a cell of an array), how would we do
that? Figure that code out.

3. If we wanted to perform that work on the array cell with index 0, how would we do that?
This step is generally a slight alteration of step 2’s solution – but nevertheless, it can be easier
to figure step 2 out before worrying about bringing array syntax into the mix.

4. Finally, we can add in a loop to vary the index of the array cell we are dealing with. That
way, instead of performing the work only on the array cell with index 0, we will perform the
work on every array cell we care about.

Performing the same work on every cell, unconditionally

At times, you want to perform the exact same work on every cell, without any chance at all
that some cells get processed differently from other cells. For example, suppose that, given an array
arr of type int, we wish to print every cell (i.e. print the value in each of the cells), one per line.
Let’s consider the four steps above with respect to this problem.

1. What is it we want to do for each cell? Well, in this case, the problem statement pretty
clearly spells it out – we want to print the value of each cell (and start a newline after each
value is printed).

2. If we just had an ordinary variable x of type int, how would we print that variable?

System.out.println(x); // this is how we would do that

3. How would we accomplish this for cell 0 of the array?

System.out.println(arr[0]);

4. Finally, we want to do the above for all indices in the array, so have a for-loop run from 0
to size - 1 and vary the array index:

for (int i = 0; i < arr.length; i++)


System.out.println(arr[i]);
103

For another example, given an array arr of type int, add 10 to every cell.

1. Once again, figuring out what to do at every cell is pretty straightforward here – the problem
states it pretty clearly. Add 10 to every cell.

2. If we had some integer variable x, and wanted to add 10 to it, we would have the following
code:

x = x + 10;

3. To do that to cell 0 of the array, you would use:

arr[0] = arr[0] + 10;

4. And finally, since we want to do this at every cell, we have our for-loop run from 0 through
arr.length - 1:

for (int i = 0; i < arr.length; i++)


arr[i] = arr[i] + 10;

Performing some work on each cell, conditionally

Sometimes, we will want to traverse an array, but only perform work on certain cells. For
example: given an array arr of type int, print all values in the array that are greater than 60:

1. To begin with, we look at the problem statement and figure out what to do at every cell. It
says we want to print all values that are greater than 60, but of course, we don’t know if a
value is greater than 60 until we check! So, what we need to do at every cell is (1) see if the
value is greater than 60, and (2) if so, print it out.

2. If we had some regular int variable x, then we could do the following:

if (x > 60)

to check if x were greater than 60. And then, if the condition is true, we just have a print
statement, similar to the one we had earlier.

if (x > 60)
System.out.println(x);

3. Once we have the above code snippet, then to perform the same sort of code on the first cell
of an array, we just replace x with arr[0] in both places:

if (arr[0] > 60)


System.out.println(arr[0]);
104

4. And finally, since we want to check this for every cell, the for-loop again runs from 0 to
arr.length - 1, and we vary the array index.

for (int i = 0; i < arr.length; i++)


if (arr[i] > 60)
System.out.println(arr[i]);

For another example, given an array arr of type int, we want to print out all even numbers in the
array.

1. As with the previous example, we need to at least look at every cell to determine whether it
satisfies our condition or not. So, the work we need to do at every cell is to see if the value
is an even number, and if so, to print it out.

2. Recall that modulus is our way of determining whether a number is even – i.e. divide by 2
and check the remainder:

x % 2 == 0

If the above condition is true, the number is even. Otherwise, the result of the modulus
operation would be 1 and the expression above would be false and the number would be odd.
So, if the condition is true, you print the number.

if (x % 2 == 0)
System.out.println(x);

3. Performing this calculation on the first cell of the array is then a matter of replacing x with
that first cell:

if (arr[0] % 2 == 0)
System.out.println(arr[0]);

4. And finally, we want to perform the above calculation at every cell of the array:

for (int i = 0; i < arr.length; i++)


if (arr[i] % 2 == 0)
System.out.println(arr[i]);
105

Processing a few cells outside of the loop

Sometimes we perform work on every cell, but not the same work. It could be that we perform
a certain task on most cells (meaning we’d write a loop to handle that repetition of the task), but
one or more cells must have different work performed on them and thus must be handled on their
own, outside of the loop. In that case, step 1 becomes harder to complete, since we need to figure
out what work is done on each cell and it is not always the same.
For example, given an array arr of type int, print the values on one line, separated by ellipses,
and start a new line when the task is done. For example, if you were given this array:

__________________________
| | | | | |
| 8 | 9 | 3 | 1 | 7 |
|____|____|____|____|____|

0 1 2 3 4

we want to print

8...9...3...1...7

and then start a new line.

1. To begin, it might be easiest to consider the example above:

8...9...3...1...7

There are two differences between the printing of the last number in that sequence, and the
printing of the earlier numbers:

• the last number has no ellipses after it


• the last number has a new line after it

The other numbers are all similar in how they get printed, and so you are repeating the same
basic work for all those numbers:

8...9...3...1...7
|__||__||__||__|
same work
repeated 4 times

So, it appears that what we want, is as follows:

• For all cells except the last one, print the value of that cell, then print ellipses, but do
NOT start a new line afterwards.
• For the last cell of the array, print the value, do NOT print ellipses, and then start a
new line.
106

2. If we have an integer variable x, and we want to print its value, followed by ellipses, and then
NOT start a new line, we would do the following:

System.out.print(x + "...");

And, if we ant to print the value, WITHOUT ellipses, and then start a new line, we would
do the following:

System.out.println(x);

Those are the two expressions we need to be using in this problem. The first will be used for
most of our array cells, and the second will be used for the last array cell.

3. Writing similar code for an array cell is just a matter of replacing x with arr[0]:

System.out.print(arr[0] + "...");
System.out.println(arr[0]);

4. Finally, we need to encase the first statement above in a loop, and have the second statement
above, occur after the loop is done. The loop should NOT process the last cell, so it should
only run up through index arr.length - 2, or in other words, the loop should continue as
long as our index is less than arr.length - 1. Once the loop is done, we use the second
statement above to access the last cell (the cell with index arr.length - 1).

for (int i = 0; i < arr.length - 1; i++)


System.out.print(arr[i] + "...");

// i is out of scope at this point

System.out.println(arr[arr.length - 1]);

The following is an alternative to the above code, which keeps i in scope so that it can be
used on the last line.

int i;
for (i = 0; i < arr.length - 1; i++)
System.out.print(arr[i] + "...");

// i is still in scope at this point, and if the loop has


// exited, then the condition of the loop was false, which
// means i == arr.length - 1

System.out.println(arr[i]);
107

As another example, given an array arr of type int, find the minimum value in the array.

1. This problem, as stated, is tricky, since we haven’t even made clear what to do with the
minimum value. We find it, and...? What? We need to do something with the minimum; we
can’t just find it and forget about it.
It is reasonable to assume that if we are searching for the minimum, we intend to make use
of that information, and therefore, knowing what the minimum is, might be helpful. So, let’s
store the minimum in a variable (we can call this variable min). That restates the original
problem as follows: given an array arr of type int, find the minimum value in the array and
store it in the variable min.
Now, it would not be possible to know for sure that we have the minimum value of the array,
unless we look at every cell at least once – if there are cells we didn’t look at, those values
could have been smaller than the value we think is the minimum, so we can’t get away with
not looking at those cells. This means, at any point in the code, if we have not finished
looking at all the cells, we can’t be sure we have the minimum.
However – we could, at least hypothetically, know what the minimum is of all the values we
have seen so far. For example, if we’ve seen five values so far, we should at least be able to
know what the smallest is out of those five. We just need to keep track of what we think is
the smallest value, and be willing to update that if we find a smaller one. For example, if min
holds the smallest of the first five values, and we look at the sixth value and that is smaller
than min, then min should be updated to hold the sixth value, since that is the smallest value
we’ve seen so far. But if the sixth value is NOT smaller than min, then that means not only
is min the smallest of the first five values, but it’s also the smallest of the first six values, and
we can leave min alone for this step.
So for most cells, the work at that cell will involve checking to see if that cell is less than or
greater than the smallest value we had previously seen up to that point – and if it is less than
the smallest value we had seen up to that point, saving our cell’s value as the new “smallest
value so far”.
The one problem is that we can’t do this to start the code, because if min is not yet initialized,
we cannot compare it to anything. So, to begin with, we will just set min equal to arr[0].
That is, if all we have seen is arr[0], well, of course that is the “minimum value so far”,
since it’s the only value we’ve seen! And then once min is initialized to be equal to arr[0],
then we can run the above “check if next value is smaller” code for all the other cells – for
each cell after arr[0], if that cell’s value is less than whatever min is at that moment, then
re-assign min to hold that cell’s value.
108

2. Imagine we have a variable x of type int, and are currently storing the minimum value in a
variable min. We want to see if x is less than min and, if so, update min to hold x, the new
minimum value. We begin with a condition:

if (x < min)

and in the event that the above condition is true, you write the value of x into min, since
you’ve found a new minimum value:

if (x < min)
min = x;

3. If you want to see how the above code would run on an array cell, just replace x with an
array cell access:

if (arr[0] < min)


min = arr[0];

4. Finally, as stated in step 1, we shouldn’t have to do any comparisons when looking at the
first cell of the array – at that point, the first cell is the only value we’ve seen, so certainly,
it has to be the minimum out of all the values we have seen so far at that point:

int min = arr[0];

Once we’ve got a value stored inside min, then for all other array cells, we can compare that
cell’s value to the minimum so far. Note that the for-loop starts with i being assigned the
index of the array’s second cell, since we’ve already dealt with the first cell.

int min = arr[0];


for (int i = 1; i < arr.length; i++)
if (arr[i] < min)
min = arr[i];
Part 2 : Programming Methodologies

As programs get larger and larger, they become harder to manage if all the code for the program
is between the inner curly braces of the program skeleton. Imagine a program that was a million
lines long! Any programmer trying to make sense of the code inside the program skeleton, would
need to understand almost one million lines of code. If the programmer was adding to the code,
and misunderstood any of the existing code, then the new code could have logic errors. It could
be very difficult to track any logic errors down, when there are any one of one million lines of code
that could be wrong. Plus, a project that large might be worked on by multiple people, and it’s
hard for different people to work on different parts of the program when all the code is in the same
file.
So, we want to use the principles of composition and abstraction to better organize our work.
First of all, we would like to put a set of related instructions together to form a large set of instruc-
tions, and then we’d like to use abstraction to “hide that code away”. You have already dealt with
this a little bit when using the System.out.println(...) and Keyboard.readInt() statements.
In both cases, putting that line in your code, triggered other code to run that you didn’t have to
write. We would like to have much of our own code set up the same way, where a one-line statement
in the program skeleton activates the running of some other set of instructions we’ve written, in the
same way that a one-line statement such as System.out.println(...) or Keyboard.readInt()
in the program skeleton, activates the running of some other set of instructions someone else has
written. Organizing our code into these small modules – called methods – helps us simplify our
programs, since there will be very well-defined means of communications between different meth-
ods, and thus if there is an error somewhere in the code, it will be much easier to zero in on where
that error could be. This means of programming is known as structured programming and will be
the topic of lectures 11 through 14.
Similarly, we want to put a set of related data values together to form one large piece of data –
such as putting many int and double and char values together to form a “student record” – and
then use abstraction to hide the details away, by forcing our instructions to treat the data value as a
single composition rather than as a collection of small details. For example, for a “student record”,
we would prefer sets of instructions that “print a student record”, rather than sets of instructions
that “print three int values, five double values, and eighteen char values”. These modules of
data abstraction are known as classes, and effectively, they will be the definition of new types in
the language. If we were to create a “student record” class, for example, then we will have a new
type, StudentRecord, that we can then use in ways very similar to the ways in which we have used
primitive types. For example, we will be able to declare variables of these new types, create arrays
of these new types, and so on. This means of programming is known as object-based programming
and will be the topic of lectures 15 through 20.

109
110

Lecture 11 : Procedural Composition and Abstraction


Solving a problem...with help

Imagine I have a math problem I would like to solve. We’ll use an arithmetic arithmetic problem,
just to keep things simple, even if it will seem a little silly.
So, let’s imagine that I am faced with calculating the following value, but don’t know how, i.e.
perhaps I have no idea how to do certain arithmetic operations. Specifically, we will assume that I
know how to do addition, but that I do not know how to do subtraction, multiplication, or division.

(((2 + 5) * ((3 + 10) - 1)) / 6) + 7

Since I can perform addition, I can evaluate the values of the first two additions in the problem,
thus giving me:

I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7

[I calculate]

I have: ((7 * (13 - 1)) / 6) + 7

but I cannot perform the third addition, since I first need to know the value of the expression

((7 * (13 - 1)) / 6)

since that value is one of the operands of the addition. Since I do not know how to perform
subtraction, multiplication, or division, I cannot evaluate that expression, and thus cannot obtain
the first operand of the remaining addition.
So, now it’s time to call on friends for help. Let’s imagine I have a friend, named FriendA, who
knows how to do subtraction. Let’s send FriendA the subtraction part of our problem. The chain
of events so far is as follows:

I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7

[I calculate]

I have: ((7 * (13 - 1)) / 6) + 7

--------> I send FriendA: (13 - 1)

FriendA has: (13 - 1)

FriendA isn’t seeing the entire problem, but FriendA doesn’t need to! All FriendA cares about, is
that I have sent a subtraction problem. FriendA doesn’t need to know why I need the subtraction
done, in order to do the subtraction. FriendA can simply calculate the result, and send it back to
me. Likewise, I don’t need to know how FriendA does the work – I just need to send the problem
to FriendA, and receive the solution in return.
111

I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7

[I calculate]

I have: ((7 * (13 - 1)) / 6) + 7

-------> I send FriendA: (13 - 1)

FriendA has: (13 - 1)

[FriendA calculates]

FriendA has: 12

<------- FriendA sends back 12

and now, thanks to FriendA, I can replace the expression (13 - 1) with the expression 12:

I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7

[I calculate]

I have: ((7 * (13 - 1)) / 6) + 7

-------> I send FriendA: (13 - 1)

FriendA has: (13 - 1)

[FriendA calculates]

FriendA has: 12

<------- FriendA sends back 12

I have: ((7 * 12) / 6) + 7

Now, we’ve still got a multiplication and a division to deal with. I have no idea how to do those (or
so we are pretending), but I have a friend, FriendB, who claims to know how to do multiplication
and division. So, I send FriendB that part of the expression.
112

I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7

[I calculate]

I have: ((7 * (13 - 1)) / 6) + 7

-------> I send FriendA: (13 - 1)

FriendA has: (13 - 1)

[FriendA calculates]

FriendA has: 12

<------- FriendA sends back 12

I have: ((7 * 12) / 6) + 7

-------> I send FriendB: ((7 * 12 / 6)

FriendB has: ((7 * 12) / 6)

Now, perhaps FriendB knows how to do multiplication, but not division. FriendB lied to us!! But
fortunately for FriendB, we don’t need to find that out. This is because we’ve asked FriendB to
perform a task for us, but we don’t care how that task gets done. All we want is the correct answer
returned to us. So, FriendB could likewise ask for help, completely unbeknownst to us.
First of all, FriendB performs the multiplication, since FriendB knows how to do multiplication:
113

I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7

[I calculate]

I have: ((7 * (13 - 1)) / 6) + 7

-------> I send FriendA: (13 - 1)

FriendA has: (13 - 1)

[FriendA calculates]

FriendA has: 12

<------- FriendA sends back 12

I have: ((7 * 12) / 6) + 7

-------> I send FriendB: ((7 * 12 / 6)

FriendB has: ((7 * 12) / 6)

[FriendB calculates]

FriendB has: (84 / 6)

Next, since FriendB doesn’t know how to do division, FriendB asks another friend, FriendC, for
help:
114

I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7

[I calculate]

I have: ((7 * (13 - 1)) / 6) + 7

-------> I send FriendA: (13 - 1)

FriendA has: (13 - 1)

[FriendA calculates]

FriendA has: 12

<------- FriendA sends back 12

I have: ((7 * 12) / 6) + 7

-------> I send FriendB: ((7 * 12 / 6)

FriendB has: ((7 * 12) / 6)

[FriendB calculates]

FriendB has: (84 / 6)

------> FriendB sends FriendC: (84 / 6)

FriendC has: (84 / 6)

Note a few things here. Firstly, just as FriendA never saw the entire problem, likewise, FriendB
does not get to see the entire problem, and FriendC sees even less. That does not matter! All I
need from FriendB is to solve the part I sent to FriendB, just like all I needed from FriendA was
to solve the part I sent to FriendA. If FriendA is asked to solve (13 - 1), FriendA does not need
to know why I want that answer – FriendA just needs to give me the answer I am asking for. And
likewise, FriendB does not need to know why I need the answer to ((7 * 12) / 6) – FriendB
just needs to give me the answer I am asking for. And likewise, FriendC doesn’t need to know
why FriendB wants the answer to (84 / 6) – FriendC just needs to give FriendB the answer that
FriendB is looking for.
But likewise, this “secrecy” works in reverse as well! When I sent FriendA part of the original
problem, I didn’t care how FriendA got the answer. I just wanted the answer. So maybe FriendA
did all the work, or maybe FriendA asked someone else for help. Eitehr way, I don’t need to care.
I just need to ask FriendA for an answer to a problem, and then wait to receive that answer. And
similarly, when I ask FriendB for help, I don’t care how FriendB got the answer, as long as FriendB
sends the answer back to me. Whether FriendB can do all the work, or has to ask someone (such
as FriendC) for help, does not concern me. All I care about is that I sent a problem to FriendB
and that I get the answer back.
So, finishing our example, FriendC is able to return the answer 14 to FriendB:
115

I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7

[I calculate]

I have: ((7 * (13 - 1)) / 6) + 7

-------> I send FriendA: (13 - 1)

FriendA has: (13 - 1)

[FriendA calculates]

FriendA has: 12

<------- FriendA sends back 12

I have: ((7 * 12) / 6) + 7

-------> I send FriendB: ((7 * 12 / 6)

FriendB has: ((7 * 12) / 6)

[FriendB calculates]

FriendB has: (84 / 6)

------> FriendB sends FriendC: (84 / 6)

FriendC has: (84 / 6)

[FriendC calculates]

FriendC has: 14

<------ FriendC sends back 14

FriendB has: 14

and now FriendB can return that result back to me:


116

I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7

[I calculate]

I have: ((7 * (13 - 1)) / 6) + 7

-------> I send FriendA: (13 - 1)

FriendA has: (13 - 1)

[FriendA calculates]

FriendA has: 12

<------- FriendA sends back 12

I have: ((7 * 12) / 6) + 7

-------> I send FriendB: ((7 * 12 / 6)

FriendB has: ((7 * 12) / 6)

[FriendB calculates]

FriendB has: (84 / 6)

------> FriendB sends FriendC: (84 / 6)

FriendC has: (84 / 6)

[FriendC calculates]

FriendC has: 14

<------ FriendC sends back 14

FriendB has: 14

<------ FriendB sends back 14

I have: 14 + 7

at which point I can finally complete the last addition and finish the evaluation of the original
expression:
117

I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7

[I calculate]

I have: ((7 * (13 - 1)) / 6) + 7

-------> I send FriendA: (13 - 1)

FriendA has: (13 - 1)

[FriendA calculates]

FriendA has: 12

<------- FriendA sends back 12

I have: ((7 * 12) / 6) + 7

-------> I send FriendB: ((7 * 12 / 6)

FriendB has: ((7 * 12) / 6)

[FriendB calculates]

FriendB has: (84 / 6)

------> FriendB sends FriendC: (84 / 6)

FriendC has: (84 / 6)

[FriendC calculates]

FriendC has: 14

<------ FriendC sends back 14

FriendB has: 14

<------ FriendB sends back 14

I have: 14 + 7

[I calculate]

I have: 21
118

This concept we are illustrating, is known as procedural abstraction. It is a kind of abstraction in


which you hide away the details of how a particular task is done. Something/someone else worries
about how the details of the work are done, and all you need to do is ask that something or someone,
“please do this work for me” and then wait for the work to get done (and if you are waiting for an
answer of some kind, wait for that something or someone to return your answer).
Because of the idea of procedural abstraction, it is not necessary for you to worry about every
single detail of a computation. In the example above, I didn’t worry about any of the details of the
subtraction, multiplication, or division – for each of those tasks, I sent those problems to someone
else and awaited my answer. And even though I did a little bit of the work in our example above
(the addition work), there were details that I didn’t worry about – I sent sections of the problem
to other friends and waited for their answers, without worrying about how those friends were doing
the work. In this way, I was able to get the value of a complex arithmetic expression, without
knowing any arithmetic other than addition.
You have already used this same idea in your own MPs. When you have printed something to
the screen, you sent the value you wanted to print, to a System.out.println or
System.out.print statement. You didn’t worry about the specifics of how that
System.out.println or System.out.print statement did its work – you just typed the state-
ment and trusted that the statement would do what it was supposed to do. Similarly, you didn’t
need to type a lot of code in order to input a value. You just typed Keyboard.readInt() or
Keyboard.readDouble() or Keyboard.readChar(), and trusted that that expression would work
as we said it would, and would do the work of obtaining your input value and sending it back to
you, even if you have no idea how that task is actually working behind the scenes.
Again, this is how abstraction helps us. Since you are freed from having to worry about every
little detail, you have more time to focus on the “big picture” and develop that further.

We can also relate this example to how the machine implements procedural abstraction. To do
this, let’s go through the example again, but let’s pretend that there is a common sheet of scratch
paper that FriendA, FriendB, FriendC, and I are using:

Column 1 Column 2 Column 3 Column 4


119

I receive the initial problem, and start working in column 1 of the scratch paper:

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7

After I perform the two additions I can do, I have reduced the problem further:

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7
...some work done...
((7 * (13 - 1)) / 6) + 7

At this point, I needed to ask FriendA for help. So I will write the problem I need FriendA to
solve, at the top of column 2 – thus showing FriendA where that scratch work should be done –
and then I will pass the scratch paper to FriendA.

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7 (13 - 1)
...some work done...
((7 * (13 - 1)) / 6) + 7

Note now that I cannot do anymore work for the time being. This is because FriendA has the scratch
paper! There wasn’t any other arithmetic I could do anyway, but even if there was, I am forced to
sit around waiting for FriendA to finish, since FriendA has the scratch paper and I do not. This is
similar to how, in your programs, when you call Keyboard.readInt() or System.out.println(),
120

your program sits there and waits for the Keyboard.readInt() call or the System.out.println()
call to finish, before continuing. Likewise, I cannot continue until FriendA finishes and gives me
back the scratch paper.

Likewise, we will assume FriendA does NOT work in column 1, and we will further assume that
FriendA does not even look at the work I have written in column 1. We have told FriendA to work
in column 2, and so that is where FriendA works. And what FriendA does is to perform whatever
scratch work is needed to figure out that (13 - 1) is 12:

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7 (13 - 1)
...some work done... ...some work done...
((7 * (13 - 1)) / 6) + 7 12

Now that FriendA has completed the calculation, FriendA must do three things. First, FriendA
must notify us of the final answer, by writing the answer in our column:

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7 (13 - 1)
...some work done... ...some work done...
((7 * (13 - 1)) / 6) + 7 12
12 <--- answer from FriendA

Second, FriendA should erase column 2. After all, I might want to give this scratch paper to
someone else later on. If FriendA is not using that area of the scratch paper anymore, than erasing
all the remarks there, so that the area is free for someone else to use, seems to be the right thing
121

to do. To leave all the now-useless remarks there, taking up space, would be wasteful:

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7
...some work done...
((7 * (13 - 1)) / 6) + 7
12 <--- answer from FriendA

Finally, FriendA should give the scratch paper back to me! Now, finally, I can do some work again,
just like when Keyboard.readInt() finishes its job and reports the input integer to your program.
In that situation, your program can then finally resume work where it had paused while waiting
for the Keyboard.readInt() call to finish, and likewise, now that I have the scratch paper again,
I can make use of the value that FriendA gave me, to reduce my expression further. This work is
of course done in column 1, since that’s where I do my work:

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7
...some work done...
((7 * (13 - 1)) / 6) + 7
12 <--- answer from FriendA
((7 * 12) / 6) + 7

Note that not only did FriendA not mess with anything in column 1 (aside from writing in the
result value in the end, and noting it was the result value), but in addition, I cannot move to
column 2 and mess with any of the work FriendA did. First of all, that work has been erased, and
is thus completely gone; secondly, I am supposed to stay in column 1 anyway.

Now, the next step was to pass the multiplication and division to FriendB. I will again use
column 2 for this, since it is the column right next to where I am working, and it is (and should
be) free and available for this purpose. So, I will write the problem I am handing to FriendB, at
the top of column 2, and then will hand the scratch paper to FriendB. As with FriendA, when
FriendB is working on the problem, I do not have the scratch paper, and so I cannot do anything.
And similarly, as with FriendA, FriendB will know to only do work in the designated column, and
122

will know to NOT mess with my work in column 1.

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7 ((7 * 12) / 6)
...some work done...
((7 * (13 - 1)) / 6) + 7
12 <--- answer from FriendA
((7 * 12) / 6) + 7

So what we see here is that our columns are available for general use. Just because FriendA
was using column 2 for scratch earlier, doesn’t mean we can’t use it now for FriendB. After all,
FriendA was finished with the subtraction work, and with column 2, and thus erased everything
from column 2! So if column 2 is sitting there unused, why shouldn’t FriendB go ahead and use
it? There’s no reason not to! If I am working in column 1, then when it’s time to have a friend
help me out, I just have that friend work in the open column to my right – column 2, in this
case. Regardless of whether that scratch work is scratch work for FriendA who does subtraction,
or FriendB who does multiplication and division, if column 2 is the next open column, then when
I pass work off to a friend, that’s the column my friend will use.

FriendB will then do some multiplication work in an attempt to reduce the expression to
something simpler:

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7 ((7 * 12) / 6)
...some work done... ...some work done...
((7 * (13 - 1)) / 6) + 7 (84 / 6)
12 <--- answer from FriendA
((7 * 12) / 6) + 7

And now FriendB is stuck. This is where FriendB had to turn for help to FriendC, and that is
exactly what happens here. FriendB will do the same thing I did – write the problem for which
help is needed, at the top of the next open column, and then pass the scratch paper to someone
123

who will work on that problem – in this case, FriendC:

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7 ((7 * 12) / 6) (84 / 6)
...some work done... ...some work done...
((7 * (13 - 1)) / 6) + 7 (84 / 6)
12 <--- answer from FriendA
((7 * 12) / 6) + 7

What has happened here is, FriendC needs a scratch column, just as I did, and just as FriendA
and FriendB did. But right now, not only is column 1 taken, but column 2 is taken as well. This
does not matter!. FriendC can use any column for scratch work; it just so happened before, that
column 2 was always the next available open column. Right now, column 3 is the next available
open column, so that is the column that gets used by FriendC instead. Either way, FriendC still
gets a scratch area to work in. Whether it is me doing work, FriendA doing work, FriendB doing
work, or FriendC doing work, none of us care which particular column we do our work in. We only
care that we get some column – whichever one it might be – to do our work in. So the easiest thing
to do is to just use the leftmost column that is not in use; I used column 1, people who I asked for
help used column 2, and anyone they ask for help, uses column 3. If FriendC were to ask for help,
then that person would use column 4, since that is the next available column.
This is an important concept! Someone can use any column that is convenient as their scratch
column. FriendA, for example, is not bound to always use column 2; FriendA only used column
2 since the person using column 1 (me) was the one who asked for help. If FriendC had sub-
traction to do, and asked FriendA for help, FriendA would proceed to work in column 4 – and
yet doing subtraction, the same kind of work FriendA had done for me in column 2. Similarly,
when you actually write Java code, any collection of Java instructions such as the instructions to
make Keyboard.readInt() work or the instructions to make System.out.println() work, will use
whatever area of memory is to the right of where the program had just been working. In the case of
your programs so far, you called Keyboard.readInt() from main(), and so whatever area of mem-
ory main() had used for its variables, if you then call Keyboard.readInt(), Keyboard.readInt()
will use the area of memory next to the area being used by main(), to do any scratch work that
Keyboard.readInt() needs to do. It is the same when you call System.out.println(). Every
time you ask another piece of code to do some work for you, that piece of code just uses the
next available memory to the right of where you were working, to do its job – and then releases
that memory once the code is done. Whatever area of memory Keyboard.readInt() is using,
Keyboard.readInt() erases that memory and gives it back to the system when it is done and
returning back to main(), just like FriendA erased column 2 just before handing the scratch paper
back to me.
That is, your Java code will not be bound to run in a particular area of memory, but rather,
will run in whatever area is most convenient. (You don’t need to worry about that level of detail,
but it will help you if you realize that concept.)
Continuing on with our example, FriendC will perform the work needed to figure out the result
124

of the division:

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7 ((7 * 12) / 6) (84 / 6)
...some work done... ...some work done... ...some work done...
((7 * (13 - 1)) / 6) + 7 (84 / 6) 14
12 <--- answer from FriendA
((7 * 12) / 6) + 7

And now FriendC will do the same thing that FriendA did earlier. First, FriendC will return the
result, to the person who had asked for help – namely, FriendB in column 2:

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7 ((7 * 12) / 6) (84 / 6)
...some work done... ...some work done... ...some work done...
((7 * (13 - 1)) / 6) + 7 (84 / 6) 14
12 <--- answer from FriendA 14 <--- answer from FriendC
((7 * 12) / 6) + 7

Next, FriendC will erase all the scratch work in column 3, since now FriendC is done, and there is
no sense cluttering up a column someone else could use, with scratch work that no longer serves a
purpose:

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7 ((7 * 12) / 6)
...some work done... ...some work done...
((7 * (13 - 1)) / 6) + 7 (84 / 6)
12 <--- answer from FriendA 14 <--- answer from FriendC
((7 * 12) / 6) + 7

And finally, FriendC will return the scratch paper back to FriendB, and FriendB can resume work
now that both the scratch paper, and the needed result value, have been returned.
Now, all FriendB has left to do, is to recognize that the result given by FriendC, is the final
125

answer that FriendB needed:

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7 ((7 * 12) / 6)
...some work done... ...some work done...
((7 * (13 - 1)) / 6) + 7 (84 / 6)
12 <--- answer from FriendA 14 <--- answer from FriendC
((7 * 12) / 6) + 7 14

And now FriendB can prepare to return back to me. First, FriendB tells me what the result is:

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7 ((7 * 12) / 6)
...some work done... ...some work done...
((7 * (13 - 1)) / 6) + 7 (84 / 6)
12 <--- answer from FriendA 14 <--- answer from FriendC
((7 * 12) / 6) + 7 14
14 <--- answer from FriendB

Note that I got my result from FriendB, not FriendC. In fact, since I have no idea what FriendB
did to get this result, I don’t even have any idea FriendC was ever involved.
Second, FriendB erases column 2, since now FriendB is done and so there’s no point in keeping
column 2 cluttered up with completed work when someone else could use that column in the future:

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7
...some work done...
((7 * (13 - 1)) / 6) + 7
12 <--- answer from FriendA
((7 * 12) / 6) + 7
14 <--- answer from FriendB

And finally, FriendB hands the scratch paper back to me, and I can resume my own work where I
126

left off. Since I now know the result of the first addition operand, I can make a substitution:

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7
...some work done...
((7 * (13 - 1)) / 6) + 7
12 <--- answer from FriendA
((7 * 12) / 6) + 7
14 <--- answer from FriendB
14 + 7

And after some calculation work, I can complete the final addition – and with it, finish the evaluation
of the expression:

Column 1 Column 2 Column 3 Column 4


(((2 + 5) * ((3 + 10) - 1)) / 6) + 7
...some work done...
((7 * (13 - 1)) / 6) + 7
12 <--- answer from FriendA
((7 * 12) / 6) + 7
14 <--- answer from FriendB
14 + 7
...some work done...
21

Now that I know the result of the expression, I can note it somewhere, and erase column 1 so
that column 1 can be used for a different bunch of scratch work the next time I need to do some
calculation work in the future:

Column 1 Column 2 Column 3 Column 4

<--- result : 21

So that is an example of how different areas of memory (different columns, in our example)
can get used by various sets of instructions. Any particular set of instructions (such as main() or
Keyboard.readInt()) can make use of any empty area of memory. And as the instructions run,
when a new area of memory is needed in order to run a new set of instructions, we just move over
127

to the right, to the closest-available empty area and use that. And when any set of instructions
has finished, the memory that was being used by those instructions is erased and can be re-used
by a different set of instructions in the future.
128

Lecture 12 : Method Syntax


Methods

A method (also called a “procedure” or “function” or “subroutine” in some other programming


languages) is a programming tool which allows us to label a set of instructions and reuse that set
of instructions over and over by simply using the label instead of having to retype the instructions
over and over.
We have already seen the use of a few methods:

• The first method we saw was the method named main, which is part of the “program skeleton”
we’ve been using since lecture #3. In this case, the set of instructions that form the method
are whatever code we’ve written inside main().

• Next, we made use of System.out.println(); and, after that, System.out print(). In


both of these cases, we didn’t write the set of instructions for printing; instead, we simply
used the “label” – the actual System.out.println(); or System.out print() line of code
– and the instructions for printing were written into our program by the compiler.

• Finally, we made use of the Keyboard.readInt() method. In this case, the “label” was all
you used in your own program, but you were also able to look inside the Keyboard.java file
to see the instructions that that “label” told the compiler to run.

The method signature and method call

The code for a method (for example, what you find in Keyboard.java) is called the method
definition, since it defines what the method does. The first line of the method definition is referred
to as the method signature. The method signature is composed of three parts:

1. a name – just like variables have names, every method has a name as well. For example,
readInt and readDouble are the names of two of the methods in the Keyboard.java file.

2. a return type – the type of value being returned. For example, Keyboard.readInt() sends
back an int to whoever called the method. System.out.println() sends back nothing.
When writing a method, you need to indicate the type of the value that is returned by the
method (you can return only one value). If you don’t return any value, then you indicate this
via the “placeholder” type void.

3. zero or more parameters – a kind of variable specific to methods. These parameters appear
within the parenthesis of the method, and when you use a method “label”, you send in values
to be stored in these parameters. You can then use the parameters as variables in the method
definition.
129

This “label” that we use to activate a method is known as a method call or a method invocation,
and it is said that we are calling the method or invoking the method.
So, we will give you a quick glimpse of an example of both a method signature and a method
call, and then talk about the pieces in detail. This would be an example of a method signature:

int Add3(int x, int y, int z)

Note the name, Add3, the return type, int, and the parameters (which we’ll discuss in just a
moment) inside the parenthesis. One possible method call could be a code snippet like this:

int d;
d = Add3(3, 9, 1);
^^^^^^^^^^^^^

The underlined code is the actual method call – values are placed inside the parenthesis to be stored
in the parameters, and the method returns a value of type int (just like the Keyboard.readInt()
method returns a value of type int) to be stored in the int variable d.
Our example in this packet will involve this method Add3, which does something simple (adds
three numbers) so that we can concentrate on learning the actual method syntax instead of under-
standing a complex calculation.

Parameters

Listing a single parameter is very similar to declaring a variable, in that you need both a type
and a name. We need the name so that we can refer to this parameter inside the method definition
in the same way we would refer to any other variable. And, of course, all variables need a type, so
that is why the parameter needs a type.

So, the form for listing a single parameter is:

paramType paramName

and the form for a list of parameters is to list single parameters, separated by commas. For example,
if you had three parameters, those three parameters would be listed as follows:

type1 name1, type2 name2, type3 name3

For example, if we wanted a boolean, a char, and an int as parameters, we would pick names for
these parameters and list them as follows:

boolean done, char oneChar, int value


130

Completing the method signature

The form for the method signature – which appears on the first line of a method definition – is:

return-type method-name(t1 p1,...,tn pn)

where

(t1 p1,...,tn pn)

is

(type1 param1,...,typeN paramN)

We want our “Add3” method to accept three integers and return their sum. The sum of three
integers will certainly be an integer as well. So, the first line of our Add3 method should be as
follows:

int Add3(int x, int y, int z)

In this method, the return type is int, and the names of our three parameters of type int are x,
y, and z. We will return to this method in just a bit to finish writing its definition.

Calling a method

The information stored in the method signature is the only information you need to know in
order to use the method. We can’t call the method unless we know its name, knowing the return
type lets us know how to make use of whatever value might be sent back by the method, and
knowing the parameters allows us to pass the appropriate values into the method to be stored in
those parameters.
The details of how the method does its job are unimportant, from the standpoint of calling the
method. That is why we don’t need to understand, or even see, the code for Keyboard.readInt()
or System.out.println(), or other methods, in order to use them. We simply need to know what
to call the method, what parameters we need to supply values for, and what the type is of the value
that gets returned, if any value is returned at all. (We generally also need to know what we are
accomplishing by calling this method – does this do a calculation? Is is that calculation result that
is being returned, or something else, and if so, what? What if we are not doing a calculation; what
else does this method accomplish, then? – but usually a small bit of documentation will provide that
information.) Given that information, you would be able to write a syntactically-correct method
call.
131

The general form for calling a method – as you have already seen with the methods you have
used – is to write the method name, and then in parenthesis list any values being sent to the
method.

method-name(argument1,...,argument n);

The values you send in are called arguments.


So, if we want to invoke our Add3 method, we need to pass in three arguments of type int,
because Add3 has three parameters of type int that need values. There are many ways we might
do this; below is one way, where we send in actual integer literals:

Add3(2, 1, 5);

We might also have int variables a, b, and c that we use as arguments:

Add3(a, b, c);

or – for any of the arguments – an expression that evalutates to type int – since the expression
results in a single value:

Add3(a, b+1, 2);


Add3(5, c+a, b*a-c);
Add3( (2-a-b-c)*7, a+b-c, 6+a/c);

We also need to make use of the value returned by this method, so we would call Add3 the same
way we called Keyboard.readInt(), namely, as part of an assignment statement.

public class Program


{
public static void main(String[] args)
{
int a, b, c, d;
a = 2;
b = 1;
c = 5;
d = Add3(a, b, c);
.
.
.
132

Completing the method definition

We have the first line of our method definition:

int Add3(int x, int y, int z)

and now need to finish the definition. First, we need open- and close- braces. Just as we turned a
collection of statements into a single compound statement in conditionals and loops by enclosing
them in braces, we do the same thing with methods. The difference here is that you have to have
the braces; even if a method has only one line of code, it needs to be enclosed in a pair of braces.

int Add3(int x, int y, int z)


{
// code goes here
}

As far as what code is needed, well, we are trying to add three numbers, and we are pretending
that you cannot chain additions together on one line, even though you can, so let’s declare what
we call a local variable to hold the sum, and then progressively add to it.

int Add3(int x, int y, int z) {


int sum;
sum = x + y;
sum = sum + z;
}

Completing the method definition – return statements

We have calculated the sum, but the last thing we need in our Add3 method is a way of sending
the sum back as the return value of the method. This is done via a return statement. The syntax
of a return statement is:

return expr;

where expr is some expression (perhaps a non-existent one; more on that later). As we have already
seen, an expression can be very complex, or it can be as simple as a single literal or variable. In
this case, it will only be a single variable, namely, sum.

// Final version of this method


int Add3(int x, int y, int z)
{
int sum;
sum = x + y;
sum = sum + z;
return sum;
}

The statement return sum; sends back the value of sum as the return value of this method. Since
the method is supposed to return a value of type int, and since sum is indeed a variable of type
int and thus holds a value of type int, everything matches up perfectly.
133

Overall code (info on next slide)

public class Program


{
public static void main(String[] args)
{
int a, b, c, d;
a = 2;
b = 1;
c = 5;
d = Add3(a, b, c);
System.out.println("Total is " + d);
}

public static int Add3(int x, int y, int z)


{
int sum;
sum = x + y;
sum = sum + z;
return sum;
}
}
134

• Note that “public static” appears in front of our new method just like it appears in front
of main(). The method signature is what you need in order to be able to use the method.
The public and static keywords in front of our method definitions are keywords that are
there due to some other issues that we will learn about in a few lectures rather than right
now. So for now just that assume all methods you write should have public static in front
of them, and later on we will learn what it means to leave them out or to use different words
instead.

• The parameters of Add3 can be thought of as a special kind of local variable. The local
variable sum must be declared and assigned inside the braces, but the parameters x, y, and z
are “declared” before the open brace is reached, and are also assigned values before the open
brace is reached (the values they are assigned, of course, being the values of the arguments
sent in to Add3). Other than that, they function exactly the same way as any local variable
such as sum that you declare and assign within the braces themselves, and thus you can use
those parameters in your code in the same way you would use any local variable that you
declared and assigned yourself.

• There is no particular order that the methods need to be in – our example had main before
Add3, but you could have had Add3 before main if you preferred. If the compiler sees a method
call before it has encountered that method (as in our example, where the call to Add3 is seen
before the Add3 method itself), it’s not a problem – the compiler just makes a note to itself
to keep an eye out for the Add3 method, and it will complain to you only if it has finished
trying to compile and has still not found the Add3 method. If it does eventually find the Add3
method – as it will in our example – then the compiler will make use of that information and
will not have any error to report to you in that regard.

• In general, the code inside a method can be as simple or as complex as you want. The code
will probably tend to be pretty simple in this class to start with, since we want you to focus on
learning method syntax. But usually, very simple code is better left in main, and a method
should be written for a task that takes more than just one or two lines of code. As with
so much else, knowing when to write a method and when not to is a matter of experience,
programming style, and the particulars of the situation you are programming for.
135

Scope in methods

When we went over loops we talked a little about the scope of variables. Specifically, we said
that a variable declared within the braces ({}) of a compound statement will vanish from existence
and no longer be accessible once you have reached the closing brace of that compound statement
block.
Methods work in a very similar manner.

• Variables declared within a method have as their scope the life of the method; they vanish
when the method ends.

• Other variables in other methods are not accessible from that method. So, if from main you
invoke Add3, you cannot access main’s local variables from Add3.

Thus, the method scope rule is:

• Variables declared in a method are only accessible in that method, and in no other methods
anywhere else in the program. A method’s parameters work the same way as any other local
variables of a method and thus go out of scope at the end of the method.

Using methods from other classes

In our program from the last lecture (repeated above), both of the methods were inside the
same class. So, main was able to invoke Add3 just by using the name of that method. It is assumed
in the absence of any further information that you are talking about the method with that name
from the class you are in.
If you want to use methods from another class inside the class you are in, you must list the
class name with the method name, so that the system knows in which class to find the method you
want. This is done via the following syntax:

className.methodName(arg1,...,argN);

That is, you list the class, followed by a “dot”, followed by the method call as usual. This is the
idea behind the call to Keyboard.readInt() – the method is called readInt() (you will find it if
you look inside the
Keyboard.java file), and since it is in the class Keyboard, you access it by listing the class
(Keyboard), followed by a dot, followed by the method name (readInt).
In the code above, we could have written:

d = Program.Add3(a, b, c);

in place of the method call we do have, but it is not necessary, since Add3 and main are within the
same class.
136

Compilation checks

The compiler does a lot of type checking and other such things when compiling code that uses
methods. For each method call, the following things are checked:

1. The name of the method on the calling line must match the name of some actual method
that has been written.

2. The number of arguments being sent to this method must match the number of parameters
the method has, and their types must match in order – i.e. the type of the first argument
must match the type of the first parameter, the type of the second argument must match the
type of the second parameter, and so on.

3. If there is a return type other than void, then there must be a return statement in the
method. If the return type is void, it’s okay to have no return statement at all, and it’s also
okay to have a return statement with no expression (i.e. return;), which simply exists the
method at that point. What you cannot do in this case is have a return statement with an
expression.

4. If there is a return type other than void – and thus, by definition, a return statement in the
method – then the value of the expression in the return statement – which is the value being
returned – must have a type equal to the stated return type. You can’t return a boolean
value from a method with an int return type, for example.

5. The method call statement must be used in a way consistent with its return type. For
example, you cannot have a method call to a method that returns void as the right-hand-side
of an assignment statement. You cannot have a method call to a method that returns a
value of type boolean as the right-hand-side of an assignment statement that assigns to a
int variable. You cannot have as an argument to the logical operator NOT (!) a method
call that returns int (NOT needs a boolean value for an argument). And so on.

This makes life very nice for you, because it means that the compiler will catch any syntax or
calling/returning consistency mistakes you might have made, rather than somehow having those
mistakes crash your program when it runs.
137

Visual example of method calling

As far as having a visual example of method calling, an analogy to notecards works nicely. This
is because, as we have now discussed, the scope for a local variable is that method only – it is
not available to any other method. So, we can think of each method call as being on a separate
notecard, and the variables declared in that method are unique to that method call – just as if we
write some numbers on a notecard, they are unique to that notecard and don’t magically appear
on other notecards as well.
So, we start off with main (we are again referring to our earlier code example with main and
Add3):

_______________________________________
| |
| main |
| |
| a: ? b: ? c: ? d: ? |
| |
| |
| |
|_____________________________________|

Above we see the main method after the declarations have been made. We have made no assign-
ments, so we consider the values in the variables unknown. Next, we will run the three lines of
code that assign values to three of the variables.

_______________________________________
| |
| main |
| |
| a: 2 b: 1 c: 5 d: ? |
| |
| |
| |
|_____________________________________|

And now we reach the line that assigns to d. As always, the expression on the right-hand-side of an
assignment statement is evaluated to obtain its value before the actual assignment of that value to
the variable can be done. Here, that means we must complete the Add3 method call before writing
the result to d, so we next perform the call to Add3.
So, let’s set up a new notecard for Add3. Since we are leaving main and thus don’t have access
to its variables until we return to it, we will place this new notecard on top of the one for main. In
this manner we make it clear that Add3 (the method on the top notecard) is the currently-active
method, and that the methods on notecards below it (in this case, only main) are methods we will
eventually return to later on as we complete method calls we have begun.
138

_______________________________________
| |
| main |
| |
| a: 2 b: 1 c: 5 d: ? |
| |
| ___________________________________|______
| | |
|__| Add3 x: y: z: |
| |
| |
| |
| |
|________________________________________|

When we first call the method, we have three parameters that need to be assigned values. These
parameters are automatically assigned the corresponding values from the method call code line
itself. That is, the first argument’s value is written into the first parameter, the second argument’s
value is written into the second parameter, and so on.
So, above, the value inside a is copied into x, the value inside b is copied into y, and the value
inside c is copied into z.

_______________________________________
| |
| main |
| |
| a: 2 b: 1 c: 5 d: ? |
| |
| ___________________________________|______
| | |
|__| Add3 x: 2 y: 1 z: 5 |
| |
| |
| |
| |
|________________________________________|

Now, we start the actual code of the Add3 method. First we declare the local variable sum
(picture squished to fit it more on slide):
139

_______________________________________
| main |
| a: 2 b: 1 c: 5 d: ? |
| ___________________________________|______
| | |
|__| Add3 x: 2 y: 1 z: 5 |
| |
| sum: ? |
|________________________________________|

Next, we run sum = x + y;...

_______________________________________
| main |
| a: 2 b: 1 c: 5 d: ? |
| ___________________________________|______
| | |
|__| Add3 x: 2 y: 1 z: 5 |
| |
| sum: 3 |
|________________________________________|

...and then we run sum = sum + z;.

_______________________________________
| main |
| a: 2 b: 1 c: 5 d: ? |
| ___________________________________|______
| | |
|__| Add3 x: 2 y: 1 z: 5 |
| |
| sum: 8 |
|________________________________________|

Finally, we come to the statement return sum;, which signifies the end of the method. What
happens here is that the machine stores the value of sum in a “return value” location, and then the
method is concluded, meaning that the parameters and local variables go out of scope and thus
”vanish”. In pictoral terms, this is equivalent to lifing up the notecard and throwing it away.
140

_______________________________________
| main | return
| a: 2 b: 1 c: 5 d: ? | value: 8
| ___________________________________|______
| |\ / \ / \ / \ / |
|__| \dd/ x:\2 / y: \ / z: 5\ / |
| \/ \/ \/ \/ |
| / \sum: 8 / \ / \ / \ |
|/___\_____/___\____/___\_____/___\______|

And finally, the return value gets used in the assignment statement...

_______________________________________
| |
| main | return
| | value: 8
| a: 2 b: 1 c: 5 d: 8 |
| |
|_____________________________________|

...after which the return value itself vanishes (it only gets stored temorarily, until the assignment
statement was over).

_______________________________________
| |
| main |
| |
| a: 2 b: 1 c: 5 d: 8 |
| |
|_____________________________________|

And now, we are back in main, with a value stored inside d. However, it is a value we generated
using a method call, instead of a value we calculated directly in main.
The notecard analogy works especially nicely for methods because methods don’t have the ability
to communicate with each other freely due to the scoping rules for local variables of methods. Two
different method calls really are two separate, distinct processing segments. The only ways that
they can communicate are:

1. the calling method can send data to the called method by passing arguments obtained in the
calling method to the called method’s parameters, and

2. the called method can send data back to the calling method by returning a value (which is
why we have a return type).
141

Structured Programming

The use of methods can lead to a more organized form of program design that serves us better
as our programs become larger. Rather than write a complete program as a very long main()
method, instead we break our large task up into a number of smaller tasks, and we write a method
to handle each task. The main() method then becomes a “summary” of our program, guiding the
overall work, but invoking the various other methods we wrote to handle many of the subproblems
involved in the larger task. So, reading over main() will still give us a general idea of what is going
on in the program, but the details are left to the actual methods that are called from main().
Then, those methods themselves can be broken down in a similar way, where each method
makes various other method calls to take care of details related to its computation. No one method
gets too large, and each method either performs work that is needed, or calls other methods in a
certain order to accomplish some work (or both).
This form of programming is known as structured programming, because you are taking the
large collection of statements in main(), and providing structure to that collection by breaking
those statements into groups, where each group of statements accomplishes one task.
Another advantage to this technique is that we can “reuse” code statements. Instead of having
to write many copies of the same code, we simply write it once, in a method, and then call that
method whenever we need to run that code. (That’s basically what you’ve done with your use of
System.out.println() and Keyboard.readInt().) One big advantage to this is that if we need
to make changes to the code in question, we only need to change it once, in the method, and not in
many places all over the program. If we did not have methods, and instead pasted the same code
into many different places in the program, then any change to that code would have to be made in
all the different places where it appeared, rather than just changing it once in the method.
This organization also makes a program easier to read, especially if you are new to the program
and are seeing the code for the first time, because you can read the “overview” of the code and
get a basic idea of how the program runs, without having to inspect every detail of every method.
(For example, you did not need to understand how Keyboard.readInt() worked in order to use
it.) As long as the method names are descriptive and there is good documentation about what
those methods do, there should be little to no reason to have to inspect the details of every method
simply to understand how the code works.
So structured programming has a lot of advantages over the “put all your code in main()”
approach to program design.
142

Lecture 13 : Reference Variables and Objects


Reference variables

In reality, the line:


int[] scores = new int[10];
is quite similar to the line:
int b = 2;
in that, in both cases, we have put a declaration and an initialization on the same line. The latter
could have been done on two lines as well:

int b;
b = 2;

and likewise, the former could also have been done on two lines:

int[] scores;
scores = new int[10];

That is, rather than thinking of the single line:


int[] scores = new int[10];
as some big mess of syntax that you need to create an array, take note that it is really two different
things put together:
• a variable declaration int[] scores;

• an initialization scores = new int[10];


and that the line:
int[] scores = new int[10];
is declaring and then initializing a variable, just as the line:

int b = 2;

is doing. However, the type of this varible is int[], and that was not one of our eight primitive
types (int is a primitive type, but not int[]). So, since the type int[] of this variable score is
not one of our eight primitive types, score is not a primitive type variable. Instead, it is something
known as a reference variable. Any variable which is not of a primitive type will be a reference
variable; array variables merely happen to be the first example of this that we’ve seen. But before
the semester is over, we will see many more examples of variables which are not of primitive types
– that is, we will see many more examples of reference variables.
So, how are primitive type variables and reference variables different? Well, the integer variable
declaration will produce an integer variable, but the reference variable declaration does not produce
an array. Instead, what it produces is a reference (that’s why it is called a reference variable). If
a primitive-type variable is similar to a box that holds a piece of data, then think of a reference
variable as being similar to an “arrow”:
143

________
| |
b - - - - - | 2 |
|______|

________
| |
scores - - - | ---|----->
|______|

That is, you can store an integer value inside the int variable, but you don’t store any data
value of your own inside the reference variable, because it is not designed to hold your data. Instead,
its job is to refer you to some other memory location where some important data is located – in
this case, where the array itself is located.

Objects and reference variables

There are four things that every variable you declare in Java will have:

1. A name

2. A type

3. A location in memory where it will be stored, i.e. an address. (You don’t choose this; the
compiler and run-time environment choose it.

4. A value that the variable stores.

Since we have no idea what the value in the variable is, until we initialize the variable, we’re
going to set that property aside for now, and focus on just the first three properties, all of which
are well-defined as soon as the variable is declared.
It does not matter whether you are talking about a primitive-type variable or a reference
variable. In the declaration int b;, the variable name is b, the type is int, and there will be
some location where this variable gets stored. (Let’s assume for the sake of this discussion that the
compiler chooses address a80 for that variable.) In the declaration int[] x;, the variable name is
x, the type is int[] (i.e., ”reference to integer array”), and there will be some location where this
variable gets stored. (Let’s assume for the sake of this discussion that the compiler chooses address
a88 for that variable.)
So, let’s look at the section of memory where these variables are stored:
144

| .
| .
| .
|__________________________ _____
a80 |__________________________ |
a81 |__________________________ | b
a82 |__________________________ |
a83 |__________________________ _____|
a84 |__________________________
a85 |__________________________
a86 |__________________________
a87 |__________________________ _____
a88 |__________________________ |
a89 |__________________________ | x
a90 |__________________________ |
a91 |__________________________ _____|
a92 |__________________________
a93 |__________________________
a94 |__________________________
a95 |__________________________
a96 |__________________________
a97 |__________________________
a98 |__________________________
a99 |__________________________
a100 |__________________________
a101 |__________________________
a102 |__________________________
a103 |__________________________
a104 |__________________________
a105 |__________________________
a106 |__________________________
| .
| .
| .
| .

Variables of type int take up 32 bits, so the variable b needs four 8-bit memory cells to store
its value. Likewise, we’ll assume reference variables need 32 bits as well, so therefore the reference
variable x would also need 32 bits, i.e. four 8-bit memory cells.
The way these variables differ is in the data they store. When you execute the assignment
statement b = 2;, the actual value 2 (or rather, the encoded version of it) gets stored in the memory
location corresponding to the variable b (which starts at the memory location with address a80 in
our example).
145

| .
| .
| .
|__________________________ _____
a80 | |
a81 | 32 bits that, together, | b
a82 | encode the integer 2 |
a83 |__________________________ _____|
a84 |__________________________
a85 |__________________________
a86 |__________________________
a87 |__________________________ _____
a88 |__________________________ |
a89 |__________________________ | x
a90 |__________________________ |
a91 |__________________________ _____|
a92 |__________________________
a93 |__________________________
a94 |__________________________
a95 |__________________________
a96 |__________________________
a97 |__________________________
a98 |__________________________
a99 |__________________________
a100 |__________________________
a101 |__________________________
a102 |__________________________
a103 |__________________________
a104 |__________________________
a105 |__________________________
a106 |__________________________
| .
| .
| .
| .

Now, remember that in an assignment statement, our variable type and value type had to
match. You could only assign char values to char variables. You could only assign boolean values
to boolean variables. And so on. So, we have this reference variable, x. We want to have an
assignment statement such as x = ?; but so far we have not discussed what we would replace the
question mark with. What values can be written into reference variables?
And the answer is: memory addresses. The data that gets stored inside reference variables are
bit strings that tell the machine to go to particular memory locations. So, just as we’ve labelled
our memory locations with addresses like a5 and a7, those exact same kinds of labels – or rather,
their bit string encodings – are what get stored in our reference variables.
The question is, how can we obtain memory addresses to actually store in our reference vari-
ables? And the answer is, we do this by using the keyword new. That is, we would use an expression
such as the following to create an array and return the address of that array:
146

new int[3];

You have seen this expression before, as part of an array creation line:

int[] x = new int[3];

and we said at the start of this notes packet that the above line could actually be split into two
parts:

int[] x;
x = new int[3];

so, this expression is used in the second line above and whatever the expression evaluates to is
being written into the variable x.
What this expression is doing is telling the computer to set aside enough memory for an integer
array of size 3. This process is known as an allocation, and thus we say that we are allocating
memory. This chunk of memory we ask the system to set aside for us is called an object – in
Java, any memory you allocate using new is an object, and any memory you obtain through a
variable declaration statement is a variable. You never create objects using variable declarations
statements – you only create objects by using new. Variables are declared (using variable declaration
statements), and objects are allocated.
All expressions involving the Java keyword “new”, will set aside some amount of memory for an
object, and then inform the program of where the start of that memory is located in the machine.
The expression actually evaluates to a memory address. So, for example, if the collection of memory
cells set aside for the array begins at address a100, then the expression will evaluate to a100, since
that is the starting location of the cells that were set side for the array.
147

| .
| .
| .
|__________________________ _____
a80 | |
a81 | 32 bits that, together, | b
a82 | encode the integer 2 |
a83 |__________________________ _____|
a84 |__________________________
a85 |__________________________
a86 |__________________________
a87 |__________________________ _____
a88 |__________________________ |
a89 |__________________________ | x
a90 |__________________________ |
a91 |__________________________ _____|
a92 |__________________________
a93 |__________________________
a94 |__________________________
a95 |__________________________
a96 |__________________________
a97 |__________________________
a98 |__________________________
a99 |__________________________ _____
a100 |__________________________ | The memory allocated for
a101 |__________________________ | the array object begins
a102 |__________________________ | here (and ends somewhere
a103 |__________________________ | further down, beyond the
a104 |__________________________ | edge of our picture).
a105 |__________________________ |
a106 |__________________________ |
| . .
| . .
| . .

So, we would have used this expression as follows:

x = new int[3];

and when the expression new int[3] evaluates to a100, that address would be what is stored
in the reference variable x:
148

| .
| .
| .
|__________________________ _____
a80 | |
a81 | 32 bits that, together, | b
a82 | encode the integer 2 |
a83 |__________________________ _____|
a84 |__________________________
a85 |__________________________
a86 |__________________________
a87 |__________________________ _____
a88 | 32 bits that, together, | x
a89 | encode the memory |
a90 | address a100 |
a91 |__________________________ _____|
a92 |__________________________
a93 |__________________________
a94 |__________________________
a95 |__________________________
a96 |__________________________
a97 |__________________________
a98 |__________________________
a99 |__________________________ _____
a100 |__________________________ cell |
a101 |__________________________ 0 of |
a102 |__________________________ array|
a103 |__________________________ _____|
a104 |__________________________ cell |
a105 |__________________________ 1 of | the entire array
a106 |__________________________ array| (12 memory cells)
a107 |__________________________ _____|
a108 |__________________________ cell |
a109 |__________________________ 2 of |
a110 |__________________________ array|
a111 |__________________________ _____|
a112 |__________________________
| .
| .
| .

Finally, when you have a statement such as:


x[2] = 17;
which uses the square brackets, that tells the machine to (1) read the memory address stored in
x, and then (2) taking that location as the start of the array, jump to the start of cell 2 of the
array, and then (3) write 17 into that cell. So, step (1) would read the value a100 from x, and then
step (2) says that, since a100 is cell 0, a100 + 2 * sizeOfInt = a100 + 2 * 4 memory cells
149

= a108 must be the address of cell 2, so go to a108. Finally, since that memory location, a108, is
x[2], that is the location into which 17 is written:
| .
| .
| .
|__________________________ _____
a80 | |
a81 | 32 bits that, together, | b
a82 | encode the integer 2 |
a83 |__________________________ _____|
a84 |__________________________
a85 |__________________________
a86 |__________________________
a87 |__________________________ _____
a88 | 32 bits that, together, | x
a89 | encode the memory |
a90 | address a100 |
a91 |__________________________ _____|
a92 |__________________________
a93 |__________________________
a94 |__________________________
a95 |__________________________
a96 |__________________________
a97 |__________________________
a98 |__________________________
a99 |__________________________ _____
a100 |__________________________ cell |
a101 |__________________________ 0 of |
a102 |__________________________ array|
a103 |__________________________ _____|
a104 |__________________________ cell |
a105 |__________________________ 1 of | the entire array
a106 |__________________________ array| (12 memory cells)
a107 |__________________________ _____|
a108 | cell |
a109 | 32 bits that, together, 2 of |
a110 | encode the integer 17 array|
a111 |__________________________ _____|
a112 |__________________________
| .
| .
| .
This memory that we request from the system using new, we call it dynamic memory, we say that we
allocate that memory from the heap, and we call that memory an object – i.e. we declare variables,
but we allocate objects. Note that objects do not have names!!!!. Therefore, the only way we can
actually refer to objects in our program is by having reference variables that store labels of the
memory addresses of those objects. This is why reference variables are so important. Without the
150

ability to store the addresses of object locations – the task which reference variables are designed
to do – we could never actually reach our objects, and so there would be no point in creating them.
(Objects are not bound by normal scope rules, which is why we want to create them.)
In Java, there are only three different types of data storage. The first are variables of primitive
types, and you’ve used those already. The second are reference variables, which store memory
addresses rather than primitive-type values. And the third kind of data is an object, which is
stored somewhere in memory just like a variable, and which holds some kind of data, just like a
variable – but which does not have a name of its own, unlike a variable.

Our more typical “abstract” picture

Now, it is not necessary to think about the array that way in order to program in Java. In fact,
thinking about specific memory locations and such is usually more a hinderance than a help. So
instead, we tend to hide away all those details and instead just draw pictures.
So, as we did earlier, we can draw reference variables as arrows. The declaration:

int[] scores;

produces a reference variable, which we will draw as follows:

________
| |
scores - - | ---|----->
|______|

Now, it turns out that reference variables are always automatically initialized by the Java virtual
machine. The value a reference variable has when you first declare it, is the value null. The value
null is a “reference variable literal” in the same way that 2 is an int literal and 45.4 is a double
literal. When a reference variable “points to nothing”, it is pointing to null. We draw it like this:

________
| | _
scores - - | ---|-----> |\|
|______|

i.e. a little box with a slash through it. Sometimes the slash is put through the reference variable
picture itself:

________
|\_ |
scores - - | \_ |
|____\_|

Either way, that’s how we convey “reference variable points to null” or “reference variable holds
the value null” in our pictures. If we want to check if a reference variable “points to nothing”
instead of pointing to an actual object, we can use boolean expressions such as:

scores == null

If that expression evaluates to true, then the reference variable points to null, just as if we have
an integer variable x, then if:
151

x == 2
evaluates to true, we know x contains the value 2. We can also assign a reference variable to store
null via an assignment statement such as:
scores = null;
just as the assignment statement:
x = 2;
would store the value 2 in the integer variable x.
Now, we have not created the array yet; that is done with the following expression:
new int[6]
giving the following picture:
________
| | _
scores - - | ---|-----> |\|
|______|

_______________________________
| | | | | | |
| | | | | | |
|____|____|____|____|____|____|
0 1 2 3 4 5
We simply draw the six array cells together floating off in space; we don’t worry about where they
are located in memory.
Finally, the new int[6] expression would have been part of an assignment statement:
scores = new int[6];
and the location of the array is written into scores. In our picture, we will show this by having
the reference variable scores point to the array.
________
| |
scores - - | | |
|__|___|
|
| _______________________________
| | | | | | | |
|--->| | | | | | |
|____|____|____|____|____|____|
0 1 2 3 4 5
We will draw reference variables and arrays that way – as arrows pointing to other large chunks
of memory. But remember that, in reality, the reference variable stores a label – a memory adddress
– for some cell in memory. Upon declaration, the reference would hold null, which is an address in
the machine at which nothing will ever be stored. (Generally, the null address is either the lowest
address in the machine – i.e. a0 – or the highest address in the machine. We will assume it is a0
in CS125.) So the real picture after declaration is something like this:
152

________
| |
scores - - | a0 |
|______|

And then after allocating the object and assigning the reference variable to point to the object, the
picture would be something like this:

________
| |
scores - - | a200 |
|______|

a200
_______________________________
| | | | | | |
| | | | | | |
|____|____|____|____|____|____|
0 1 2 3 4 5
153

Summary of data storage

• Type of data:

– Primitive-type variable – primitive type


– Reference variable – non-primitive type
– Object – non-primitive type

• Does the item have a name?

– Primitive-type variable – yes, all variables have names


– Reference variable – yes, all variables have names
– Object – no, there is no name; you cannot use objects directly, but instead must have a
reference variable pointing to the object and then work through the reference variable

• How is the item created?

– Primitive-type variable – variable declaration


– Reference variable – variable declaration
– Object – you use new to dynamically allocate memory from the heap

• What is the scope of the item?

– Primitive-type variable – the block or method it was declared in


– Reference variable – the block or method it was declared in
– Object – as long as a reference points to the object, it still exists. Once no reference
points to the object, the object is gone. So there is no particular scope to an object –
you can simply use it for as long as it is still accessible from your program in some way.

• What can the item store?

– Primitive-type variable – values of primitive types


– Reference variable – memory addresses
– Object – depends on the type of the object (more on that later, as we study classes; for
now, the only object we know is arrays and they store many items of the same type)
154

One last important piece of info

Since we are manipulating objects through references, it is important to realize that assignment
doesn’t work the same way.

int[] A = new int[10];


int[] B = new int[10];
... // assume we have some code that
// writes values into A’s array.
B = A;

That last line will not copy the values of the array that A refers to, into the cells of the array that
B refers to. Instead, it will change the reference variable B to point to the same array that the
reference variable A points to. That is, the assignment statement above copies the memory location
label stored in the reference variable A, into the reference variable B.

// BEFORE
__________________
A ----------> | first array |
|________________|

__________________
B ----------> | second array |
|________________|

// AFTER
__________________
A ----------> | first array |
__ |________________|
/|
/ __________________
B --------/ | second array |
|________________|

and then the array that the reference variable B used to point to will be lost and unaccessible, since
we no longer have any references to it.
That is, dealing with objects is quite different than dealing with variables of primitive types.
Assignment of references is not assignment of objects, it is just an assignment of a memory location
into a reference. Thus, assignment of one reference to another does not automatically copy an
object – it simply writes a memory location into a reference. When B is declared to be an integer
array reference, and we write the line:

B = new int[10];

the expression involving new sends back the location of the array and it is written into the reference
variable B. Likewise, when we write

B = A;
155

the reference variable A stored a memory location and that location is then written into B. It is
important to realize this, as it means when we deal with objects in general we cannot copy one into
another by assigning one’s reference to the other’s reference. We need to explicity copy the object
somehow. In the case of the array, that would be done through a loop:

for (int i = 0; i < A.length; i++)


B[i] = A[i];

That example assumes that the array pointed to by A and the array pointed to by B are the same
size. If they were not, then you’d have to write slightly more complicated code to adjust for that
possibility. The following code:

int[] A = new int[10];


int[] B = new int[8];
B = A;
System.out.println(B.length);

will print 10 since even though B starts out pointing to an array of length 8, we then reassign B to
point to an array of length 10.
If two reference variables are pointing to the same object, then it doesn’t matter which reference
you use to access the object. The following code:

int[] A = new int[10];


int[] B = A;
B[0] = 19;
A[0] = 3;
System.out.println(B[0]);

will print the value 3, because A[0] and B[0] are the exact same array cell, since A and B point to
the same array.

__________________
A ----------> | | . . . | |
__ |___|_________|__|
/| 0 . . . 9
/
B --------/

Therefore, when we assign A[0], we are assigning B[0] as well – replacing the 19 with a 3.
156

Lecture 14 : Objects and Methods


Passing objects to methods via reference variables

What we will do now is to look at a program whose methods have references to arrays as
parameters and as return values.

public class Lecture14


{
public static void main(String[] args)
{
int numberOfScores;
System.out.print("How many scores are there?: ");
numberOfScores = Keyboard.readInt();
int[] scores;
scores = new int[numberOfScores];
readData(scores);
printData(scores);
System.out.println(scores[2]);
badInit(scores);
System.out.println(scores[2]);
System.out.println(scores.length);
scores = getArray(4);
System.out.println(scores.length);
}

public static void readData(int[] arr)


{
for (int i = 0; i < arr.length; i++)
{
System.out.print("Enter score for index #" + i + ": ");
arr[i] = Keyboard.readInt();
}
}

public static void printData(int[] arr)


{
for (int i = 0; i < arr.length; i++)
System.out.println("Score at index #" + i + " is " + arr[i] + ".");
}
157

public static void badInit(int[] arr)


{
arr = new int[3];
for (int i = 0; i < arr.length; i++)
arr[i] = -1;
}

public static int[] getArray(int n)


{
int[] temp;
temp = new int[n];
return temp;
}

} // end of the class Lecture14

Note the statements:

readData(scores);
printData(scores);
badInit(scores);

in main. Each of those statements is a method call, to a method that appears later in the program.
For each of those method calls, the reference variable scores is an argument for the method. To
use a reference variable as an argument, we simply need to put the reference variable name in the
method parenthesis, like we would do for variables of primitive types. Of course, any variable name
is an expression, one that evaluates to the value stored in the variable. Reference variables are
no different; as we’ve already discussed a little bit, a reference variable name, when used as an
expression, evaluates to the memory address stored in the reference variable. So, in each of the
three method calls above, the value we are sending to the method as an argument, is a memory
address – specifically, the memory address stored in the reference variable scores.
Note also that in the method signatures for readData, printData, and badInit, the parameter
that matches the reference variable argument has int[] as the type, just like the variable scores
does when we first declare that array reference variable in main:
158

public static void main(String[] args)


{
...
int[] scores;
...
}

public static void readData(int[] arr)


.
.
.
public static void printData(int[] arr)
.
.
.
public static void badInit(int[] arr)

Since the argument is of type int[] – a type that holds memory addresses as values – the parameter
is also of type int[]. The argument type and the parameter type match – as they should – and
the value that gets copied into the parameter, is a value of type int[], i.e. a memory address.
So, with respect to type matching, the parameter-passing rules apply for reference variables
just as they did for primitive type variables:
• the parameter type and the argument type must match, whether that common type is int
or double or int[]. The fact that now our type is a non-primitive type doesn’t change that.

• The parameter “type and variable name” pair will look just like a variable declaration of
that type. Our parameters previously were pairs such as int x or boolean notDone or char
choice, but now that our parameter type can be int[], we just have a pair such as
int[] arr – still a “type and variable name” pair, even though the type is now a non-primitive
type.

• The argument will still be some expression that evaluates to a value of the needed type. In
our example program, the expression we use in the method call is simply a variable name, but
that variable name will evaluate to some value of the appropriate type – a memory address,
in the case of the methods calls we are discussing – just like any other variable evaluates to
the value the variable holds.
In our code above, we send an expression of type int[] as an argument. That expression is
evaluated to produce a “value of type int[]” (the value of any reference variable type is a memory
address), and that value is sent to the method and copied into the parameter variable of type int[].
This is no different than our earlier method examples, when we would, say, send an expression of
type boolean as an argument, and that expression would be evaluated to produce a value of type
boolean and that value would be sent to the method and stored in the parameter variable of type
boolean.
That is the very important point here – fundamentally, parameters of type int[] or double[] or
any other non-primitive type work exactly the same way as parameters of primitive types, namely,
that the value of the method call’s argument is copied into the method’s parameter.
For example, let’s assume that the first integer inputted by the program from the user was a 6,
and thus the array that gets allocated in main is of size 6. If the array we allocated in main was
159

placed at memory address a8000 by the sytem, then scores, the variable of type int[], would
hold the memory address a8000. (The array object could have been located anywhere in memory;
we chose a8000 just to pick something, for our example, but the object might have been located
elsewhere instead.)

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a8000 -|------------> | | | | | | |
| |_______| | |____|____|____|____|____|____|
| | 0 1 2 3 4 5
|______________________________|

So, scores is a variable that holds the memory address a8000. And that means that when we
have scores as an argument to a method, the expression scores (a single variable name, but an
expression nonetheless) gets evaluated to the value a8000, and that value is sent to the method,
and is written into the parameter, and so now the parameter of the method readData also holds the
memory address a8000. This is no different than when you send an int variable as an argument to
an int parameter, and afterwards the int variable and the int parameter hold copies of the same
int value. Above, we sent an int[] variable as an argument to an int[] parameter, and so both
the int[] variable and the int[] parameter hold copies of the same int[] value – i.e. the same
memory address:

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a8000 -|-----------> | | | | | | |
| |_______| | |____|____|____|____|____|____|
| | 0 1 2 3 4 5
| | .
| ____________________________________ /|\
|___| _______ | |
| readData | | | /
| arr |a8000 -|------/
| |_______| |
| |
|__________________________________|

Now, what prevents us from accessing the array through arr in exactly the same way we access it
through scores? The answer is, nothing! Both of those variables – the one in main and the one in
readData – are variables of type int[] which hold the memory address of the same dynamically-
allocated array. The only difference between them is that one of them is in the scope of main and
one is in the scope of readData. But they hold the same memory address, and thus are pointing
to the same array object, and thus conceptually they are completely interchangable.
The readData code will write into the array object shown above just as easily as if we had put
that code in main and used scores instead of arr as the reference variable that array access
depended on. This illustrates a very important quality of objects and reference variables: It doesn’t
matter what reference variable you access an object through. There could be fifty different reference
160

variables to the same object inside your program; with the exception of the names and/or scopes of
the variables being different from each other, all of those reference variables are exactly equivalent.
Accessing the object – for reading or writing – works the same way regardless of which of the fifty
reference variables we use, and we will be accessing the same object regardless of which of the fifty
reference variables we use.
So, in readData, the expression arr.length will evaluate to 6, and thus the body of the for-
loop will run six times. If the user-inputted values for those six Keyboard.readInt() calls are,
respectively, 56, -9, 22, 83, 43, and 71, then afterwards, our picture will look like this:

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 |
| |_______| | |____|____|____|____|____|____|
| | 0 1 2 3 4 5
| | .
| ____________________________________ /|\
|___| _______ | |
| readData | | | /
| arr |a8000 -|------/
| |_______| |
| |
|__________________________________|

When we write 56 into the array cell arr[0], we are also writing it into the array cell scores[0],
because they are the exact same array cell!!. So, when we return from readData, and the parameter
reference variable arr goes away, the local reference variable scores is back in scope, and the
actual array object is still accessible through scores, and that value 56 is still sitting in the cell
with index 0. That doesn’t change. All that happens when we return from readData is that our
second reference variable, arr, goes out of scope. Everything else stays the same:

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 |
| |_______| | |____|____|____|____|____|____|
| | 0 1 2 3 4 5
| |
| |
|______________________________|

In short, we were able to change our array object from within the method readData(...), even
though the array object was created in main(). Since objects are not bound by scope rules, we can
read and write the same object from any method that has a reference to that object.
Note that we never actually have an object as an argument to a method. Instead of passing
in the object itself to a method, we pass in a reference to it. Reference variables follow the same
rules as variables of the primitive types – the value the argument evaluates to is copied into the
161

method parameter. If that argument was a variable, that means there are now two copies of the
data – one in the argument variable, and one in the parameter. That was how things worked for
variables/parameters of type int, it’s how it worked if they were of type boolean, and it’s how it
works if they are of type int[]. The only difference is the type of value that is copied – a value of
type int in the first case, a value of type boolean in the second case, and a memory address in the
third case. This is called passing by value because from the method call, we were sending a value
to be copied into a parameter.
But, we are passing around the address of the object from argument to parameter, instead of
passing the actual object itself. We never make a copy of the object; we only make a copy of its
address, and then use that copy of the object’s address to access the object from the new method
(that new method being readData(...), in our above example). We can then refer to the original
object from our method – and even change it.
So, let’s look at another example. When we call the printData(...) method, we again pass
the value of scores – a8000 in our example – as an argument, to a reference variable parameter:

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 |
| |_______| | |____|____|____|____|____|____|
| | 0 1 2 3 4 5
| | .
| ____________________________________ /|\
|___| _______ | |
| printData | | | /
| arr |a8000 -|------/
| |_______| |
| |
|__________________________________|

Now, as the printData(...) method successively prints out arr[0], arr[1], and so on, the
method is actually printing out scores[0], scores[1], and so on – because arr[0] and scores[0]
are the same array cell, arr[1] and scores[1] are the same array cell, and so on. We eventually
return back to main(), having printed the entire array pointed to by scores, via the use of a
completely different method (printData(...)), rather than via code written inside the main()
method.
162

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 |
| |_______| | |____|____|____|____|____|____|
| | 0 1 2 3 4 5
| |
| |
|______________________________|

Note that altering the object from a different method, as we did with readData(...), is some-
thing we could not do with variables of primitive types that were arguments in the method call, nor
can we do it with reference variables that are arguments in the method call. Again, this is because
arguments in Java are passed by value. In the Add3 method in Lecture Notes #11, changing x, y,
and z did not change a, b, or c, since we only sent the values of a, b, and c to the Add3 method
– we did not send the actual variables themselves. When you call a method, the arguments are
expressions that evaluate to values, and you simply copy those values into the method parameters.
Likewise here, changing the value of arr – that value being the memory address stored inside arr
– will not change the value held in scores.
As an example, let’s consider the three lines in our main() that appear after the call to
printData(...). When we print out scores[2], we will print 22 to the screen, since 22 is the
value stored at cell 2 of the array pointed to by scores. Next, we call the method badInit(...),
and end up with the following picture:

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 |
| |_______| | |____|____|____|____|____|____|
| | 0 1 2 3 4 5
| | .
| ____________________________________ /|\
|___| _______ | |
| badInit | | | /
| arr |a8000 -|------/
| |_______| |
| |
|__________________________________|

So far, this is not very different from the calls to readData(...) and printData(...). What is
different is what happens inside the badInit(...) method. The first line of the method definition
is:
arr = new int[3];
As we have previously seen, a statement like that will, (1) allocate a new integer array object of
size 3, and (2) write the location of that array into the variable arr. So, as the expression
new int[3] gets evaluated, we have a picture like this:
163

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 |
| |_______| | |____|____|____|____|____|____|
| | 0 1 2 3 4 5
| | .
| ____________________________________ /|\
|___| _______ | |
| badInit | | | /
| arr |a8000 -|------/ a5000
| |_______| | ________________
| | | | | |
|__________________________________| | | | |
|____|____|____|
0 1 2

(The picture assumes that the new array object was stored at memory address a5000; it could have
been stored at plenty of other different places as well but we just picked one for the example.)
The array at a5000 is allocated as part of the evaluation of the expression new int[3]. Once
the array has been allocated, the expression evaluates to the address of the array – a5000 – and
then that address is written into arr by the assignment statement, giving the following picture
once the assignment statement is complete:

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 |
| |_______| | |____|____|____|____|____|____|
| | 0 1 2 3 4 5
| |
| ____________________________________
|___| _______ |
| badInit | | |
| arr |a5000 -|------\ a5000
| |_______| | \ ________________
| | \_____\ | | | |
|__________________________________| / | | | |
|____|____|____|
0 1 2

We have changed the value of arr by pointing it to a different object – but scores still holds
the same address and thus still points to the same object. We can’t change scores from badInit
because scores is a local variable, and local variables are bound by scope. There is no way from
within badInit(...) or any other non-main() method, to reassign a variable that is local to
main(). This is just like in Lecture Notes #11, when we could not change the value of a in main
by messing with the value of x in Add3.
The expression arr.length later in badInit(...) will now evaluate to 3, since the array object
that the reference variable arr points to, is of size 3. So, in that for-loop inside badInit(...),
164

the body of the loop will run three times, and thus three assignments will be run – one for each of
the three cells in the array pointed to by arr:

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 |
| |_______| | |____|____|____|____|____|____|
| | 0 1 2 3 4 5
| |
| ____________________________________
|___| _______ |
| badInit | | |
| arr |a5000 -|------\ a5000
| |_______| | \ ________________
| | \_____\ | | | |
|__________________________________| / | -1 | -1 | -1 |
|____|____|____|
0 1 2

Now, with the for-loop complete, we can return from the method badInit(...) back to main().
When we return from a method, the local variables and parameter variables of that method go out
of scope. Thus, the variable arr in badInit(...) goes out of scope, and thus there are no more
references to the array at a5000:

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 |
| |_______| | |____|____|____|____|____|____|
| | 0 1 2 3 4 5
| |
| |
|______________________________|

a5000
________________
| | | |
| -1 | -1 | -1 |
|____|____|____|
0 1 2

And, since there are no longer any references in the program to the array object at a5000, the
system will reclaim that memory, since your program is no longer using it:
165

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 |
| |_______| | |____|____|____|____|____|____|
| | 0 1 2 3 4 5
| |
| |
|______________________________|

\ a5000 /
\ _____________/__
\|___ | __|_/ |
(object is eliminated; |___\|_/__|_-1 |
system reclaims memory) /|____|____|_\__|
/ 0 1 2\
/ \

So now we are back in main(). The variable scores still holds the address a8000, there is still an
array of size 6 at the address a8000, and that array still holds the six values we inputted earlier.
The first printing of scores.length (the third-to-last statement inside the definition of main())
will print the value 6 to the screen, just as it would have done at any earlier point in main() after
the array was allocated.

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 |
| |_______| | |____|____|____|____|____|____|
| | 0 1 2 3 4 5
| |
| |
|______________________________|

The only thing main() has access to that we could have changed in badInit(...), is the non-local
data of main() – specifically, the object at a8000 to which main() has a reference. Since we did not
change that object within badInit(...) – we reassigned arr before making changes to the cells
of the array arr pointed to – we therefore have not changed anything within badInit(...) that
main() had access to, and so there are no permanent effects of the work we did in badInit(...).
We will now inspect the next statement in main():

scores = getArray(4);
166

As far as passing parameters goes, this method call is a bit more straightforward than the previous
three, since the one parameter is an integer, and we’ve seen parameters of primitive types before:

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 |
| |_______| | |____|____|____|____|____|____|
| | 0 1 2 3 4 5
| |
| ____________________________________
|___| |
| getArray |
| _____ |
| | | |
| n| 4 | |
| |___| |
|__________________________________|

Within the method, we first declare a local reference variable of type int[]. As with any refer-
ence variable declared via a variable declaration statement, this reference variable is automatically
initialized to null:

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 |
| |_______| | |____|____|____|____|____|____|
| | 0 1 2 3 4 5
| |
| ____________________________________
|___| _______ |
| getArray | | | ___
| _____ temp| a0 -|-----------> |/|
| | | |_______| |
| n| 4 | |
| |___| |
|__________________________________|

Then the next statement will allocate an array object of size 4, and write the memory address of
that array object into the reference variable temp. (We’ll assume that the array object is allocated
at memory address a7000, just to pick something for our example.)
167

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 |
| |_______| | |____|____|____|____|____|____|
| | 0 1 2 3 4 5
| |
| ____________________________________
|___| _______ |
| getArray | | |
| _____ temp|a7000 -|------\ a7000
| | | |_______| | \ ____________________
| n| 4 | | \____\ | | | | |
| |___| | / | | | | |
|__________________________________| |____|____|____|____|
0 1 2 3

Finally, the last statement of the method:

return temp;

is the interesting part. Remember that in any return statement of the form:

return expr;

we evaluate the expression, and return that value as our return value. As we have already discussed,
reference variables evaluate to the memory addresses they hold. So, our expression temp in our
return statement, evaluates to a7000, and that is what we return. This matches our return type;
the return type is int[] and our return value is a value of type int[], i.e. a memory address.
(Remember, the value of any reference type, is a memory address.)
So, the method getArray(...) now ends, and thus temp and n go out of scope, and the method
returns a7000 as its return value.
168

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 |
| |_______| | |____|____|____|____|____|____|
| | 0 1 2 3 4 5
| |
| |
|______________________________|

a7000
/|\ ____________________
| | | | | |
| | | | | |
the value a7000 is |____|____|____|____|
returned by getArray(...) 0 1 2 3

And thus the right-hand side of the statement:

scores = getArray(4);

evaluates to a7000, and so that value is what the assignment statement writes into the reference
variable scores. That gives us the following picture:

________________________________ a8000
| _______ | _______________________________
| main | | | | | | | | | |
| scores |a7000 -|------\ | 56 | -9 | 22 | 83 | 43 | 71 |
| |_______| | \ |____|____|____|____|____|____|
| | \ 0 1 2 3 4 5
| | \
| | \
|______________________________| \
\
\ a7000
\ ____________________
\____\ | | | | |
/ | | | | |
|____|____|____|____|
0 1 2 3

That is, scores now points to the array object that was allocated in the getArray(...) method,
and there are no longer any references holding a8000. That means the system can reclaim the
memory being used for the array object at a8000, since no reference variable in the program is
pointing to it anymore:
169

________________________________
| _______ |
| main | | |
| scores |a7000 -|------\
| |_______| | \
| | \
| | \
| | \
|______________________________| \
\
\ a7000
\ ____________________
\____\ | | | | |
/ | | | | |
|____|____|____|____|
0 1 2 3

And that is what we are left with at that point in our main() method. The reference variable
scores points to an array of size 4, and so the final print statement of the main() method will
print 4, since scores.length will now evaluate to 4 (whereas before the getArray(...) call, it
evaluated to 6, since at that time, scores pointed to a completely different array object.
The general rule to remember is that variables – whether primitive-type variables or reference
variables – follow the “pass by value” rule. Parameters of methods merely hold copies of the
argument values, and are not tied in any way to the arguments themselves. So if a parameter is a
reference variable, it holds a memory address. If we have a reference as an argument, we are sending
a memory address value – NOT an object – to be copied into a reference variable parameter. If
we return a reference variable, we are returning a memory address value, and so the method call
expression evaluates to that memory address value (as in the getArray(...) call in our main()).
170

Lecture 15 : Data Composition and Abstraction: Classes and In-


stance Variables
Representing non-primitive items

Imagine a clock. How would we represent a clock with what we know? One way is with the
following set of variables:

int variable to hold hour (from 1 - 12)


int variable to hold minutes (from 0 - 59)
boolean to hold true if AM, false if PM

In the program below, we are attempting to represent two clocks. To do this, we first declare
a set of variables of the above types, for each clock that we want to represent.

public class ClockTest


{
public static void main(String[] args)
{
// declare variables for clock at home
int homeHour;
int homeMinutes;
boolean homeAM;

// declare variables for clock at office


int officeHour;
int officeMinutes;
boolean officeAM;

// main() continues on next slides...

Next, we can assign separate values to these separate sets of variables.

// assign variables for clock at home,


// representing the time 2:15AM
homeHour = 2;
homeMinutes = 15;
homeAM = true;

// assign variables for clock at office,


// representing the time 7:14PM
officeHour = 7;
officeMinutes = 14;
officeAM = false;
171

Finally, print these values out using the method below:

// Prints the "home" clock


printClock(homeHour, homeMinutes, homeAM);

// Prints the "office" clock


printClock(officeHour, officeMinutes,
officeAM);
} // end main

public static void printClock(int hour,


int minutes, boolean AM)
{
// print variables for clock
System.out.print("Time is " + hour + ":");
if (minutes < 10)
System.out.print("0");
System.out.print(minutes + " ");
if (AM == true)
System.out.println("AM.");
else // AM == false
System.out.println("PM.");
}

} // end of class

The class – definition of a new type

We spoke earlier of a class being “a helpful box to store stuff” – for example, the way the class
Keyboard held our useful command-line input methods in one place.
However, classes have a different purpose as well, and that is to (among other things) collect
variable declarations together into one. What if we take the variables we need to represent a clock,
and gather them into one class?

public class Clock


{
public int hour;
public int minutes;
public boolean AM;
}

If we were to do that, then Clock is now a new type in the language!


172

Some terminology

• class - a blueprint. The class tells us “how to make a clock”, in the same way that a blueprint
might tell us, “how to make a house”.

• object - a creation whose design is described by the class, just as the house’s creation is
described by the blueprint. An object of a class, is also known as an instance of that class.
You can create as many houses as you want from the same blueprint, and likewise, you can
create as many objects as you want that all

• The variables we declared inside that Clock class were not declared inside any method – and
thus they were not local variables, nor were they parameter variables. They are a third kind
of variable, called an instance variable. These variables are called instance variables because
every time you create a new object of a class (i.e. a new instance of that class), you also
create one copy of each of the instance variables. Every time we create an object of type
Clock, we have created a variable hour of type int, a variable minutes of type int, and a
variable AM of type boolean.

• You can use a blueprint to create many houses. Likewise, use one class to create many
identical objects; then customize objects with unique data values. You can use the Clock
class above to create many different objects of type Clock then say, set one clock to store
“2:14 AM” and another to store “7:15 PM”. But each clock has two integers and a boolean.

• This is similar to building houses – every time you build a house, you have a new set of walls,
a new roof, and so on. Painting the walls of one house red, won’t change the color of the walls
of other houses, and similarly, changing the value of hour, minutes, and AM in one object
of type Clock, won’t affect the values of any instance variables in any other objects of type
Clock.
173

Reference variables and objects

We can declare a reference variable of type Clock, just as we declared a reference variable of type
“int array”. In both cases, the reference variable serves the same purpose – it will hold the address
of a memory location.
Similarly, we can use the expression “new Clock()” to create an object of type Clock, just as
we used the expression “new int[6]” to create an object of type “int array”. Once the object of
type Clock is created, we can use a reference variable of type Clock to store the address returned
by the “new Clock()” expression – just as we had a reference of type int[] store address returned
by the expression “new int[6]”. In both cases, the reference variable holds the address of the
memory location where the new object is stored in memory.
The type of a reference variable and the type of the object it refers to must match! The compiler
will catch type-mismatch mistakes of that nature. For example, the first line below will generate a
compiler error, but the second one will not:

int[] arr = new Clock();


Clock c1 = new Clock();

As with array objects, Clock objects – or objects of any other type (i.e. anything else created
using new) – are allocated off the heap – a section of memory where the object does not go out of
scope when we reach the end of the block from which it was allocated. Likewise, just as we said was
the case with array objects, objects in general do not have names, and we can only access them by
having reference variables refer to those objects and then working through the reference variables.
All of the rules we had for reference variables and objects when we were discussing arrays likewise
are true for reference variables and objects of other types.
A quick terminology issue: when we create objects of type Clock in this manner, we can refer
to them as “Clock objects” (a shorter phrase to say than “objects of type Clock”, though both
phrases mean the same thing). Similarly, instead of saying “reference variable of type Clock”, we
can say “Clock reference variable” or “Clock reference”. The same applies for any other type of
reference variable or object.
We access the individual variables of the object by using the same dot syntax we used for arrays.
Just as length was a variable built into every array object, likewise hour, minutes, and AM are
variables built into every Clock object.
174

An example using what we’ve seen so far


public class ClockTest
{
public static void main(String[] args)
{
// declare reference variables
Clock home;
Clock office;

// create/initialize "home" clock object


home = new Clock(); // object created
home.hour = 2;
home.minutes = 15;
home.AM = true;

// create/initialize "office" clock object


office = new Clock(); // object created
office.hour = 7;
office.minutes = 14;
office.AM = false;

// Prints the "home" clock


printClock(home.hour, home.minutes,
home.AM);

// Prints the "office" clock


printClock(office.hour, office.minutes,
office.AM);
} // end main

// This method has not changed.


public static void printClock(int hour,
int minutes, boolean AM)
{
// print variables for clock
System.out.print("Time is " + hour + ":");
if (minutes < 10)
System.out.print("0");
System.out.print(minutes + " ");
if (AM == true)
System.out.println("AM.");
else // AM == false
System.out.println("PM.");
}

} // end of class
175

Clock references as parameters

public class ClockTest


{
public static void main(String[] args)
{
// declare reference variables
Clock home;
Clock office;

// create/initialize "home" clock object


home = new Clock(); // object created
home.hour = 2;
home.minutes = 15;
home.AM = true;

// create/initialize "office" clock object


office = new Clock(); // object created
office.hour = 7;
office.minutes = 14;
office.AM = false;

// Prints the "home" clock


printClock(home);

// Prints the "office" clock


printClock(office);

} // end main

// This method now has a Clock reference for a parameter


public static void printClock(Clock c)
{
// print variables for clock
System.out.print("Time is " + c.hour + ":");
if (c.minutes < 10)
System.out.print("0");
System.out.print(c.minutes + " ");
if (c.AM == true)
System.out.println("AM.");
else // AM == false
System.out.println("PM.");
}

} // end of class
176

Lecture 16 : Classes, Reference Variables, and null


Clock references as parameters

Last time, we saw this example, where we stored our information for a clock, in a Clock object,
and passed a reference to that Clock object to the printClock(...) method, rather than passing
two int values and a boolean value to the printClock(...) method:

public class ClockTest


{
public static void main(String[] args)
{
// declare reference variables
Clock home;
Clock office;

// allocate object, assign address to reference variable,


// initialize instance variables of object
home = new Clock();
home.hour = 2;
home.minutes = 15;
home.AM = true;

// allocate object, assign address to reference variable,


// initialize instance variables of object
office = new Clock(); // object created
office.hour = 7;
office.minutes = 14;
office.AM = false;

// print the time on each of the two clocks


printClock(home);
printClock(office);
}

public static void printClock(Clock c)


{
// print variables for clock
System.out.print("Time is " + c.hour + ":");
if (c.minutes < 10)
System.out.print("0");
System.out.print(c.minutes + " ");
if (c.AM == true)
System.out.println("AM.");
else // AM == false
System.out.println("PM.");
}
} // end of class
177

So far, the use of the Clock class:

public class Clock


{
public int hour;
public int minutes;
public boolean AM;
}

has not allowed us to do anything we couldn’t do before. In our first example yesterday, we called
a method to print out the clock information, and that’s exactly what we are doing now, as well.
In the first example last time, we could copy the values of our local variables, into the param-
eters of a new method, and so though we could not read main()’s clock-related variables from
printClock(...), we “effectively” had read-access to those variables anyway, since we could copy
the data of those variables from main() to printClock(...) as part of the method call.
What we could not do, however, was write to the variables of main(), from printClock(...).
The method printClock(...) could store copies of the values, but if it changed its own copies,
that had no effect on the original variables back in main(). So main() could send the values of
local variables to other methods, but those other methods could not read – or write – the actual
local variables of main().
However, now that we’ve moved the clock-related data from being stored in local variables in
main(), to being stored in instance variables in objects, we can now access those objects from other
methods, just as we did in Lecture Notes #14 when we accessed an array created in main(), from
other methods. In Lecture Notes #14, our non-main() methods were able to both read and write
to the array object created in main(), since those other methods had their own references to that
array object. Similarly, we can design methods like printClock(...), to have Clock references as
parameters – and then since a method like printClock(...) has its own reference to the Clock
object created in main(), the method could read and write the instance variables of the Clock
object created in main(), just as the methods in Lecture Notes #14 could read and write the array
cells of the array object created in main(). Again, the important concept here is that objects are
not bound by the scope rules. Any method can access any existing object, as long as the method
has a reference to that object.
So, let’s add in a new method to our ClockTest class – one that will assign to the variables of
a Clock object. We will keep the Clock class the same for this entire notes packet; for now, we are
only changing the code inside the ClockTest class, via the addition of a method setTime(...):
178

public class ClockTest


{
public static void main(String[] args)
{
// declare reference variables
Clock home;
Clock office;

// allocate objects and assign addresses to the reference variables


home = new Clock(); // object created
office = new Clock(); // object created

// set the time on each of the two clocks


setTime(home, 2, 15, true);
setTime(office, 7, 14, false);

// print the time on each of the two clocks


PrintClock(home);
PrintClock(office);

} // end main

public static void setTime(Clock c, int theHour, int theMinutes, boolean theAM)
{
c.hour = theHour;
c.minutes = theMinutes;
c.AM = theAM;
}

public static void PrintClock(Clock c)


{
// print variables for clock
System.out.print("Time is " + c.hour + ":");
if (c.minutes < 10)
System.out.print("0");
System.out.print(c.minutes + " ");
if (c.AM == true)
System.out.println("AM.");
else // AM == false
System.out.println("PM.");
}

} // end of class
179

Let’s trace though the beginning of this program, to see what happens. First of all, we declare the
two reference variables in main():

____________________________________
| ___________ |
| main | | | ___
| home | a0 --|---|----> |\|
| |_________| |
| |
| ___________ |
| | | | ___
| office | a0 --|--|----> |\|
| |_________| |
|__________________________________|

Then we allocate two objects. The first allocation occurs in the statement:

home = new Clock();

and thus the address of that object is written into the reference variable home. Likewise, the
statement after that one will allocate a second object and store its address in the reference variable
office. Let’s assume these two objects are allocated at addresses a5000 and a7800, respectively,
just for the sake of assuming some addresses for our example. In real life, the objects could have
been there, but could also have been almost anywhere else in memory instead.

a5000
__________
/---> | | hour
____________________________________ / |________|
| ___________ | / | | minutes
| main | | | / |________|
| home | a5000 --|---|--------/ | | AM
| |_________| | |________|
| |
| ___________ |
| | | |
| office | a7800 --|--|-----------\ a7800
| |_________| | \ ___________
|__________________________________| \--> | | hour
|_________|
| | minutes
|_________|
| | AM
|_________|

Next, we have our first call to setTime(...):

setTime(home, 2, 15, true);


180

Each of the four arguments in that method call are evaluated, to obtain the four values a5000, 2,
15, and true. Those four values are then copied into the four parameters of the setTime(...)
method, and control is transferred to the setTime(...) method:

a5000
__________
/------> | | hour
____________________________________ / /--> |________|
| ___________ | / / | | minutes
| main | | | / / |________|
| home | a5000 --|---|--------/ / | | AM
| |_________| | / |________|
| | /
| ___________ | /
| | | | /
| office | a7800 --|--|--------/--\ a7800
| |_________| | / \ ___________
|__________________________________| / \--> | | hour
| | / |_________|
| setTime ____________ | / | | minutes
| | | | / |_________|
| c | a5000 ---|-|--/ | | AM
| |__________| | |_________|
| _______ ________ |
| | | | | |
| theHour | 2 | theAM | true | |
| |_____| |______| |
| |
| __________ |
| | | |
| theMinutes | 15 | |
| |________| |
|__________________________________|

And now, when we run the three lines of the setTime method:

c.hour = theHour;
c.minutes = theMinutes;
c.AM = theAM;

we are writing into the instance variables of the object that the reference variable c points to, and
thus, also writing into the instance variables of the object that home points to, since c and home
point to the same object:
181

a5000
__________
/------> | 2 | hour
____________________________________ / /--> |________|
| ___________ | / / | 15 | minutes
| main | | | / / |________|
| home | a5000 --|---|--------/ / | true | AM
| |_________| | / |________|
| | /
| ___________ | /
| | | | /
| office | a7800 --|--|--------/--\ a7800
| |_________| | / \ ___________
|__________________________________| / \--> | | hour
| | / |_________|
| setTime ____________ | / | | minutes
| | | | / |_________|
| c | a5000 ---|-|--/ | | AM
| |__________| | |_________|
| _______ ________ |
| | | | | |
| theHour | 2 | theAM | true | |
| |_____| |______| |
| |
| __________ |
| | | |
| theMinutes | 15 | |
| |________| |
|__________________________________|

As a result, once we return from setTime(...) back to main(), the object home points to has
been initialized:
182

a5000
__________
/---> | 2 | hour
____________________________________ / |________|
| ___________ | / | 15 | minutes
| main | | | / |________|
| home | a5000 --|---|--------/ | true | AM
| |_________| | |________|
| |
| ___________ |
| | | |
| office | a7800 --|--|-----------\ a7800
| |_________| | \ ___________
|__________________________________| \--> | | hour
|_________|
| | minutes
|_________|
| | AM
|_________|

The same would happen with the next line of code in main() – the second call to setTime(...):

setTime(office, 7, 14, false);

Each of the four arguments in that method call are evaluated, to obtain the four values a7800, 7,
14, and false. Those four values are then copied into the four parameters of the setTime(...)
method, and control is transferred to the setTime(...) method:
183

a5000
__________
/------> | 2 | hour
____________________________________ / |________|
| ___________ | / | 15 | minutes
| main | | | / |________|
| home | a5000 --|---|--------/ | true | AM
| |_________| | |________|
| |
| ___________ |
| | | |
| office | a7800 --|--|-----------\ a7800
| |_________| | \ ___________
|__________________________________| \--> | | hour
| | /----------> |_________|
| setTime ____________ | / | | minutes
| | | | / |_________|
| c | a7800 ---|-|--/ | | AM
| |__________| | |_________|
| _______ ________ |
| | | | | |
| theHour | 7 | theAM |false | |
| |_____| |______| |
| |
| __________ |
| | | |
| theMinutes | 14 | |
| |________| |
|__________________________________|

And now, when we run the three lines of the setTime method:

c.hour = theHour;
c.minutes = theMinutes;
c.AM = theAM;

we are writing into the instance variables of the object that the reference variable c points to,
and thus, also writing into the instance variables of the object that office points to, since c and
office now point to the same object. Our second method call has written a different value into
the parameter c, and so by writing to the instance variables of what c now points to, we are writing
to a different object than we were writing to when we called setTime(...) the first time:
184

a5000
__________
/------> | 2 | hour
____________________________________ / |________|
| ___________ | / | 15 | minutes
| main | | | / |________|
| home | a5000 --|---|--------/ | true | AM
| |_________| | |________|
| |
| ___________ |
| | | |
| office | a7800 --|--|-----------\ a7800
| |_________| | \ ___________
|__________________________________| \--> | 7 | hour
| | /----------> |_________|
| setTime ____________ | / | 14 | minutes
| | | | / |_________|
| c | a7800 ---|-|--/ | false | AM
| |__________| | |_________|
| _______ ________ |
| | | | | |
| theHour | 7 | theAM |false | |
| |_____| |______| |
| |
| __________ |
| | | |
| theMinutes | 14 | |
| |________| |
|__________________________________|

And now, once we return from setTime(...) back to main(), the object office points to has
also been initialized:
185

a5000
__________
/---> | 2 | hour
____________________________________ / |________|
| ___________ | / | 15 | minutes
| main | | | / |________|
| home | a5000 --|---|--------/ | true | AM
| |_________| | |________|
| |
| ___________ |
| | | |
| office | a7800 --|--|-----------\ a7800
| |_________| | \ ___________
|__________________________________| \--> | 7 | hour
|_________|
| 14 | minutes
|_________|
| false | AM
|_________|

That is one of the big benefits of using objects – since we can access objects from any method as
long as that method has a reference to the object, we can read or write the same object from many
different methods. We could not have written a separate method to initialize local variables within
main(), but we can write a separate method to initialize the instance variables of some object. So,
by moving the variables from being local to main(), to being in an object, we gain the ability for
other methods to read and write those variables freely, rather than being forced to (for example)
put all the variable assignment code we ever want to run, into main().
186

The NullPointerException

Consider the following code:

Clock office;
office = new Clock();
office.hour = 7;

We have discussed how this code works before. First we declared the reference variable (which is
automatically initialized to null, like any other reference variable):

________
| | ___
office - - | --|------> |\|
|______|

then we allocate a Clock object for the reference variable to point to:

________ _____________
| | | |
office - - | --|---------------> | | hour
|______| |___________|
| |
| | minutes
|___________|
| |
| | AM
|___________|

and finally, when we have the statement office.hour = 7; we are using the “dot syntax” on the
variable office to “follow the arrow” from office to the object it points to, and once we have
“arrived” at the object that office points to, we find an hour variable there, and can assign it the
value 7:

________ _____________
| | | |
office - - | --|---------------> | 7 | hour
|______| |___________|
| |
| | minutes
|___________|
| |
| | AM
|___________|

That’s how things are supposed to work. But what if we leave out the allocation?

Clock office;
office.hour = 7;
187

In that case, we only declare the reference (which is initialized to null), so when we then use the
“dot syntax” on the office variable, we “follow the arrow” for the following picture:

________
| | ___
office - - | --|------> |\|
|______|

and when we arrive at the spot that office points to, there is no object there of any kind!!!. So
the line office.hour = 7; can’t do anything, since there’s no hour variable there to assign. We
“follow the arrow” to a location where there isn’t an hour variable.
This is a problem – we have an assignment statement in our code that we cannot complete! So,
the program will crash, and will announce to you that you have a NullPointerException. We have
mentioned “exception error messages” before – we said that if you access an array with an illegal
index, the program crashes, and notifies you that you had an ArrayIndexOutOfBoundsException.
It’s a similar situation here, in that the program will crash, and the error messages printed when it
crashes will tell you of some illegal condition that was encountered when the program ran. In this
case, you are trying to access an object at the null location, and since there isn’t any object at the
null location, you have a problem – you are asking for the impossible!. Hence, you have triggered
a NullPointerException.
This is a common error in Java, and one you are likely to encounter frequently. Whenever you
do encounter it, it means that some reference variable you are using the “dot syntax” on, points to
null instead of an object, and thus using the “dot syntax” on that reference variable doesn’t make
sense, given the value inside the reference variable. It might mean you haven’t assigned a value to
the reference variable yet, or it could mean you incorrectly assigned to the reference variable when
you did assign to it, i.e. you wrote null into the reference variable at some point, when you didn’t
mean to do so. In any case, the NullPointerException is the end result.
Note that this can also occur with arrays – only in that case, it’s the “bracket syntax”, as well
as the “dot syntax”, that you are worried about. First of all, you cannot ask for the length of an
array that doesn’t exist:

int[] scores; // right now, the variable "scores" holds the value "null"
int num;
num = scores.length;

The last line will trigger a NullPointerException, again because you are using the “dot syntax”
on a reference variable that holds the value null. However, you cannot use the bracket syntax on
such a reference variable, either. So in the following code:

int[] scores; // right now, the variable "scores" holds the value "null"
scores[0] = 7;

you will again get a NullPointerException from the last line, since the syntax “scores[0]” is
basically doing the same thing that a line such as office.hour would do – it is giving you access
to a variable within the object the reference points to. The difference between the two situations is
basically one of naming. The length variable of an array object and the hour variable of a Clock
object, actually have names, and so you access them from their references using the “dot syntax”.
But the individual cells of an array are accessed using integer indices – i.e. the “names” of those
188

variables, are numbers, not actual names. And so in that case, we use the “bracket syntax” instead
of the “dot syntax”, since if we used the “dot syntax”, we’d get things like the last three lines of
the following code:

int[] scores;
scores = new int[6];
scores.0 = 7;
scores.5 = 14;
System.out.println(scores.5);

and that looks a little strange. So that’s why we use the bracket syntax instead, if our “names”
are actually integer indices, rather than real names like hour or length:

int[] scores;
scores = new int[6];
scores[0] = 7;
scores[5] = 14;
System.out.println(scores[5]);

The point here is that the “dot syntax” and the “bracket syntax” mean the same thing – “follow the
arrow, to the object this reference points to” – and thus they can both trigger a NullPointerException
if the reference you are using the “dot syntax” or “bracket syntax” on, holds the value null instead
of the address of an actual object.
189

Arrays of non-primitive types

Just as you could create arrays whose cells held values of primitive types, you likewise can
create arrays whose cells hold reference variables of a particular type. The syntax for doing this is
the same as it was for primitive types; the only difference is that, when you needed a type before,
you used a primitive type, and now you could also use a non-primitive type.
For example, you could declare a reference to a Clock array using the same syntax you declare
a reference to an int array:

// Type varname;
int[] scores;
Clock[] times;

and then just as you can allocate an integer array object for the integer array reference to point
to, you can allocate a Clock array object for the Clock array reference to point to.

// varname = expr;
scores = new int[6];
times = new Clock[6];

There is a concern, however. Remember that when you create an array, you still need to initialize it.
If we had the integer array declaration and allocation above, we would have the following picture:

________
| |
scores - - | | |
|__|___|
|
| _______________________________
| | | | | | | |
|--------->| | | | | | |
|____|____|____|____|____|____|
0 1 2 3 4 5

Now consider the statement:

System.out.println(scores[4]);

What happens if we now run that statement? What will get printed? We really have no idea, since
we never initialized scores[4]. If we initialize scores[4] and then run the System.out.println(...)
statement, however:

scores[4] = 47;
System.out.println(scores[4]);

then you actually know what value will be printed – the value you had written into that cell a
statement earlier, namely, 47.
190

________
| |
scores - - | | |
|__|___|
|
| _______________________________
| | | | | | | |
|--------->| | | | | 47 | |
|____|____|____|____|____|____|
0 1 2 3 4 5

In this respect, array cells – which are effectively variables – aren’t any different than stand-
alone variables. After all, when we discussed arrays, we said to treat an array cell just like any
other variable. And with any other variable, you’d have to initialize it before printing it, or else
you have no idea what value you’ll actually print:

int x;
System.out.println(x); // what gets printed? who knows?
x = 47;
System.out.println(x); // this time, 47 gets printed

The only difference between the first example and the second one, is that in the first example,
the integer we want to print is part of an array, and in the second example (the four lines of code
above), the integer is a stand-alone local variable. In either case, however, we want to write a value
into the variable before reading the variable; if we read the variable before we ever initialize the
variable, we don’t have any idea what value we’ll see there. (The compiler might even complain,
in some cases.)
So what does all this have to do with objects and classes? Well, consider again the declaration
of a Clock reference variable and the allocation of a Clock array object:

Clock[] times;
times = new Clock[6];

When you create this array, you are creating an array of six reference variables. But just as with
the integers above, you will have not initialized those six array cells yet. So you have an array of
six un-initialized reference variables.
Well, not quite. We did say, that the virtual machine automatically initializes all reference
variables to null. So, what we really have, is six array cells, all of which point to null:

________ ___ ___ ___ ___ ___ ___


| | |\| |\| |\| |\| |\| |\|
times - - | | |
|__|___| /|\ /|\ /|\ /|\ /|\ /|\
| | | | | | |
| ___|____|____|____|____|___|___
| | | | | | | | | | | | | |
|--------->| | | | | | |
|____|____|____|____|____|____|
0 1 2 3 4 5
191

So now, imagine trying to initialize the hour variable of one of the clocks:

times[4].hour = 7;

This syntax is no different than if we had done the following:

Clock office;
office.hour = 7;

As we have already discussed, the code immediately above will generate a NullPointerException
– since we have not assigned the variable office to point to a new object, it points to null and
thus there is no hour variable to assign to. And as we pointed out with integers above, there is
no real difference between a stand-alone integer variable and an integer variable inside an array,
other than the syntax you use to obtain the “name” of the variable. It’s no different with reference
variables – there’s no real difference between a stand-alone Clock reference variable, and a Clock
reference variable that is one of the cells of an array, other than the syntax you use to obtain the
Clock reference variable.
So, in our array example, each of the cells indexed 0 through 5 is a reference variable of type
Clock, holding null, just as in the above example, office is a reference variable of type Clock,
holding null. And so, just as the line:

office.hour = 7;

will generate a NullPointerException, likewise, the line:

times[4].hour = 7;

will generate a NullPointerException as well, since the only difference between the two is in how
the Clock reference variable was obtained.
Sometimes, people get confused on this point, since they know the array has been allocated.
Indeed it has, but if the array is allocated, that only means the five Clock reference variables
have been created – it doesn’t mean any of them actually point to Clock objects. It’s no different
than when you declare one Clock reference variable – that doesn’t mean the reference variable
automatically points to a Clock object. So, just because your array reference variable points to
an array object, it does not mean all the references inside that array object, themselves point to
objects. That is why drawing pictures is so useful; if you remember that this line:

Clock[] times;

gives you this picture:

________
| | ___
times - - | --|------> |\|
|______|

and that with these two lines:

Clock[] times;
times = new Clock[5];
192

you get the following picture:

________ ___ ___ ___ ___ ___ ___


| | |\| |\| |\| |\| |\| |\|
times - - | | |
|__|___| /|\ /|\ /|\ /|\ /|\ /|\
| | | | | | |
| ___|____|____|____|____|___|___
| | | | | | | | | | | | | |
|--------->| | | | | | |
|____|____|____|____|____|____|
0 1 2 3 4 5

then that might help you remember that the individual Clock reference variables also need to be
assigned to point to objects, just as the array reference variable times was assigned to point to an
object.
So, what you need to do if you want to write into an hour variable, is to have the following
three lines (the third one is the one we’ve added to the previous two examples):

Clock[] times;
times = new Clock[5];
times[4] = new Clock();

Now that we are also allocating an object for times[4] to point to, we get the following picture:

_______
|------------> | |
| | | hour
| |_____|
________ ___ ___ ___ ___ | ___ | |
| | |\| |\| |\| |\| | |\| | | minutes
times - - | | | | |_____|
|__|___| /|\ /|\ /|\ /|\ | /|\ | |
| | | | | | | | | AM
| ___|____|____|____|____|___|___ |_____|
| | | | | | | | | | | | | |
|--------->| | | | | | |
|____|____|____|____|____|____|
0 1 2 3 4 5
193

And so, if we add a fourth line to our code – the assignment to an hour instance variable that we
wanted to perform earlier:

Clock[] times;
times = new Clock[5];
times[4] = new Clock();
times[4].hour = 7;

this time, when we follow the arrow from the Clock reference at times[4], to an object, there is
indeed a Clock object at the end of that arrow, rather than null – and thus there is indeed an
hour variable there, and thus the assignment works just fine:

_______
|------------> | |
| | 7 | hour
| |_____|
________ ___ ___ ___ ___ | ___ | |
| | |\| |\| |\| |\| | |\| | | minutes
times - - | | | | |_____|
|__|___| /|\ /|\ /|\ /|\ | /|\ | |
| | | | | | | | | AM
| ___|____|____|____|____|___|___ |_____|
| | | | | | | | | | | | | |
|--------->| | | | | | |
|____|____|____|____|____|____|
0 1 2 3 4 5

Note that this same problem could happen in reverse, if you had a class which had instance variables
that were not of a primitive type. For example, consider the following class:

public class ExamScores


{
public int examNumber;
public double average;
public int[] scores;
}

One easy mistake to make, is to write code such as the following:

ExamScores data;
data = new ExamScores();
data.examNumber = 1;
data.average = 80.3;
data.scores[0] = 89;
data.scores[1] = 57;
data.scores[2] = 95;

Each of the last three lines is a problem, because the reference variable scores is null. When we
create a new ExamScores object, we get the following:
194

________
| | ___________
data - - | --|------> | |
|______| | ? | examNumber
|_________|
| |
| ? | average
|_________|
| |
| | | scores
|_____|___|
|
\|/
___
|\|

The int variable examNumber and the double variable average, are uninitialized, and so we do
not know what values those variables hold. The scores variable, being a reference variable, is
initialized to null. And as we discussed earlier, if we then try and access individual cells of
the array object data.scores points to, when data.scores doesn’t actually point to an array
object in the first place, we would get a NullPointerException. We need to not just allocate the
ExamScores object, but also, since scores is a reference variable, we need to allocate an object for
that reference variable to point to as well, as in the following code (which is the earlier code, with
the indicated line added in the middle):

ExamScores data;
data = new ExamScores();
data.examNumber = 1;
data.average = 80.3;
data.scores = new int[3]; // this is the line we have added to the earlier example
data.scores[0] = 89;
data.scores[1] = 57;
data.scores[2] = 95;

Running the above code up through the allocation of the integer array (i.e. ignoring the last three
lines for now) would give us the following picture:
195

________
| | ___________
data - - | --|------> | |
|______| | 1 | examNumber
|_________|
| |
| 80.3 | average
|_________|
| |
| | | scores
|_____|___|
|
| ______________________
| | | | |
|---------> | | | |
|______|______|______|
0 1 2

and thus we can run the last three lines as well, since now data.scores does indeed point to an
array object, and we can write the cells indexed 0, 1, and 2 at that object:

________
| | ___________
data - - | --|------> | |
|______| | 1 | examNumber
|_________|
| |
| 80.3 | average
|_________|
| |
| | | scores
|_____|___|
|
| ______________________
| | | | |
|---------> | 89 | 57 | 95 |
|______|______|______|
0 1 2

So, when dealing with references – whether local reference variables, or parameter reference vari-
ables, or reference variables stored in the cells of an array, or reference variables that are the
instance variables of objects – keep in mind that you need to always initialize a reference to point
to an object, before you can use the “dot syntax” or the “bracket syntax” on that reference.
196

Lecture 17 : Instance Methods


During the last lecture, we used the following code to make use of a class:
public class ClockTest
{
public static void main(String[] args)
{
// declare reference variables
Clock home;
Clock office;

// allocate objects and assign values to reference variables


home = new Clock();
office = new Clock();

// set the times on the clocks


setTime(home, 2, 15, true);
setTime(office, 7, 14, false);

// print the clocks


printClock(home);
printClock(office);

} // end main

public static void setTime(Clock c, int theHour, int theMinutes,


boolean theAM)
{
c.hour = theHour;
c.minutes = theMinutes;
c.AM = theAM;
}

public static void printClock(Clock c)


{
// print variables for clock
System.out.print("Time is " + c.hour + ":");
if (c.minutes < 10)
System.out.print("0");
System.out.print(c.minutes + " ");
if (c.AM == true)
System.out.println("AM.");
else // AM == false
System.out.println("PM.");
}
} // end of class
197

And this was the class we were using:

public class Clock


{
public int hour;
public int minutes;
public boolean AM;
}

We’re going to start changing that code a bit. First of all, why do we have setTime(...) and
printClock(...) in the ClockTest class? If we wanted to use the Clock.java file in the future,
we would copy Clock.java into the directory where the rest of our new project was. But, if we
did that, we still wouldn’t have setTime(...) and printClock(...) available to us, since those
methods are not in Clock.java. We’d have to either also copy ClockTest.java into our directory
as well...or else we’d have to open ClockTest.java and copy setTime(...) and printClock(...)
so that they could be pasted into some other file in our project directory. Either way, working on
our new project requires us to deal with both Clock.java and ClockTest.java, since the code we
want is scattered across both those files.
Wouldn’t it be easier if those two methods were in the Clock.java file? After all, the purpose
of those two methods is to manipulate Clock objects, so we’d never use those methods without
also needing the Clock.java file to provide the instance variables. If we had the instance variables
and those two methods, in the Clock.java file, then when we copy the Clock.java file to a new
directory, we are copying not just the blueprint for Clock objects, but also, we are copying the
methods for manipulating Clock objects. Keeping all the Clock-related code in one file, makes
more sense than does scattering that code across two or three or four files. This way, to use Clock
objects, you would only have to copy one file – Clock.java – which contains all the code that
pertains to Clock objects – the instance variable declarations that form the blueprint for those
objects, and the methods that use those objects.
That would lead to the following Clock.java file:
198

public class Clock


{
public int hour;
public int minutes;
public boolean AM;

public static void setTime(Clock c, int theHour, int theMinutes,


boolean theAM)
{
c.hour = theHour;
c.minutes = theMinutes;
c.AM = theAM;
}

public static void printClock(Clock c)


{
// print variables for clock
System.out.print("Time is " + c.hour + ":");
if (c.minutes < 10)
System.out.print("0");
System.out.print(c.minutes + " ");
if (c.AM == true)
System.out.println("AM.");
else // AM == false
System.out.println("PM.");
}

The Clock class above is the same as the Clock class from the earlier example, except that now we
have also moved the two non-main() methods from ClockTest.java, into Clock.java, as well.
That would mean that in the ClockTest.java file, the only method you’d have left is main(...).
And the code within main(...) can remain exactly the same, with one exception – since now,
when the methods setTime and printClock are called, those methods are in a different class than
ClockTest, you need to put that different classname in front of the method call, followed by dot.
That is, rather than a method call such as:

printClock(home);

you now need to have:

Clock.printClock(home);

This is no different than how, in your own MPs, you need to use the expression Keyboard.readInt(),
rather than just the expression readInt(), to input an integer.
Thus, the ClockTest.java file now looks like this:
199

public class ClockTest


{
public static void main(String[] args)
{
// declare reference variables
Clock home;
Clock office;

// allocate objects and assign reference variables


home = new Clock();
office = new Clock();

// set the times on the clocks


Clock.setTime(home, 2, 15, true);
Clock.setTime(office, 7, 14, false);

// print the clocks


Clock.printClock(home);
Clock.printClock(office);

} // end main
} // end of class

Note that the main() method simply uses existing methods to manipulate the Clock objects, and
has no need to directly read or write the hour, minutes, or AM variables of the Clock objects.
Now, consider for a moment the Clock class, as it is currently written. We have two meth-
ods, setTime(...) and printClock(...), both of which have a Clock reference as a parameter.
What if we had many more methods in Clock.java, all of which manipulated Clock objects?
For example, in addition to setTime(...) and printClock(...), we might have a method
incrementOneMinute(...) which would move a Clock object’s time forward one minute. We
might have a method changeToDST(...) that would convert the time from “standard time” to
“daylight savings time”. You can imagine many other methods you might write, all of which would
be designed to write or read the instance variables of a Clock object. And thus, each of these meth-
ods would need a Clock reference as a parameter, since there’s no way a method could manipulate
a Clock object, unless it had access to a Clock reference that pointed to that object.
So, imagine you had 100 different methods in the Clock.java file, all of them designed to ma-
nipulate Clock objects in some way, just like setTime(...) and printClock(...) do – and thus
all of them having a Clock reference as a parameter, just like setTime(...) and printClock(...)
do. In such a case, you could argue that the need for a Clock reference as a parameter in each
and every one of those 100 methods, is a little bit annoying. After all, of course each of these 100
methods needs access to a Clock reference. That’s why they are in the Clock.java file in the first
place – because they manipulate Clock objects! It seems like it’s a bit redundant to then have to
say, for every single method in that file, “Oh, and this method needs a Clock reference too!”. It
would be nice if every method in the Clock.java file automatically had a Clock reference, without
us having to list it explicitly – since we wouldn’t put a method in the Clock.java file in the first
place unless we also felt it needed a Clock reference as a parameter.
Can we do this? Is there some facility in the Java language for saying “Assume every method
in this class has a Clock parameter, so that I don’t have to list that parameter in each method!”?
200

The answer is, yes, there is! (Well, almost. That’s not quite what we’re allowed to do, but what
we’re allowed to do will be good enough.)
The syntax for accomplishing this is what we will look at next. However, before we begin, a word
of warning – the earlier code examples in this packet compiled, and the final pair of ClockTest.java
and Clock.java files in this packet will compile, but the intermediate steps will not. That is, we’ll
be making four changes to the last code example, to produce our new code example, and all four
changes have to be made. So, when we’ve made only one of the changes, or two, or three of them,
we will show you the “code so far”, but those examples won’t compile. It will only be once we’ve
made all four changes, that the resultant code will compile. That will be the last Clock.java and
ClockTest.java files in this packet.
The first of our four changes, will be to change the parameter name in the two methods in
Clock.java from c to this (and thus we will change all the code that uses that parameter name,
as well):
public class Clock
{
public int hour;
public int minutes;
public boolean AM;

public static void setTime(Clock this, int theHour, int theMinutes,


boolean theAM)
{
this.hour = theHour;
this.minutes = theMinutes;
this.AM = theAM;
}

public static void printClock(Clock this)


{
// print variables for clock
System.out.print("Time is " + this.hour + ":");
if (this.minutes < 10)
System.out.print("0");
System.out.print(this.minutes + " ");
if (this.AM == true)
System.out.println("AM.");
else // AM == false
System.out.println("PM.");
}
}
The new parameter name specifically has to be this; the variable name this is a reserved word in
Java, that is specifically used for the sorts of situations we are talking about now, and which cannot
be used as the name of any other kind of variable – this can only be the name of a parameter we
are trying to have the compiler assume “automatically exists”, as we are in the process of discussing
right now.
The second change we want to make, is to remove the word static from the first line of each of
the two methods. The word static is basically a signal to the compiler – when the word is there,
201

one signal is sent to the compiler, and when the word is not there, a different signal is sent to the
compiler. So, static doesn’t have any inherent meaning itself; it’s the presence or lack of the word
static that is important.
Up to now, every method we’ve written has had static on it’s first line; such methods are
called class methods. Now, for the first time, we are writing methods that do NOT have static
on the first line; such methods are called instance methods. Here is the code so far, once we have
removed static from the first line of each of the two methods:
public class Clock
{
public int hour;
public int minutes;
public boolean AM;

public void setTime(Clock this, int theHour, int theMinutes,


boolean theAM)
{
this.hour = theHour;
this.minutes = theMinutes;
this.AM = theAM;
}

public void printClock(Clock this)


{
// print variables for clock
System.out.print("Time is " + this.hour + ":");
if (this.minutes < 10)
System.out.print("0");
System.out.print(this.minutes + " ");
if (this.AM == true)
System.out.println("AM.");
else // AM == false
System.out.println("PM.");
}
}
So, what is the difference between an instance method and a class method? Well, fundamentally,
they are the exact same thing – in both cases, we are simply making use of procedural abstraction,
i.e. calling a method, passing it some values, and perhaps getting a value back when the method
has completed. The differences between the two are syntax-related; instance methods make use of
slightly different syntax to accomplish the same things that class methods accomplish. However,
the use of that slightly different syntax will result in us thinking about instance methods in a
slightly different way than we think about class methods, and that slight change in our thought
process is what we are most concerned about. We’ll elaborate on that more in the next packet; for
now, just think of an “instance method” as a slightly different syntax for doing the same thing as
a “class method”.
One of the advantages an instance method has over a class method, is that we no longer
specifically need to list the Clock parameter in our method signatures. In fact, not only are we
allowed to not list it, but in fact, we are required to not list it; removing the static from the first
202

line of the method, means that the compiler will declare a Clock this parameter for us, and so we
should not specifically do so ourselves. Removing that parameter from the parameter list of both
methods, is our third change, and it leads to the following code:

public class Clock


{
public int hour;
public int minutes;
public boolean AM;

// There is a "Clock this" parameter here automatically, because


// setTime(...) is an instance method
public void setTime(int theHour, int theMinutes, boolean theAM)
{
this.hour = theHour;
this.minutes = theMinutes;
this.AM = theAM;
}

// There is a "Clock this" parameter here automatically, because


// printClock(...) is an instance method
public void printClock()
{
// print variables for clock
System.out.print("Time is " + this.hour + ":");
if (this.minutes < 10)
System.out.print("0");
System.out.print(this.minutes + " ");
if (this.AM == true)
System.out.println("AM.");
else // AM == false
System.out.println("PM.");
}
}
203

Now, with the above change, our Clock.java file is correct. Both methods in that file are
now correctly-written instance methods. The fourth change we need to make, happens in our
ClockTest.java file, where we actually call the instance methods. Not only is the instance method
syntax slightly different from the class method syntax, but also, the syntax for calling an instance
method, is slightly different from the syntax for calling a class method.
The difference in syntax, is related to our now-removed parameter in the methods in Clock.java.
Without that parameter, we should not be sending in an argument, since the argument list always
needs to match with the parameter list. That suggests we should change our method call to
setTime(...) (for example) from the following (which is what we have now):
Clock.setTime(home, 2, 15, true);
to the following:

Clock.setTime(2, 15, true);

Now, we have three arguments – two int values and one boolean value, in that order – and
those three arguments match our parameter list from the new version of setTime(...) in our
Clock.java file:
// There is a "Clock this" parameter here automatically, because
// setTime(...) is an instance method
public void setTime(int theHour, int theMinutes, boolean theAM)
{
this.hour = theHour;
this.minutes = theMinutes;
this.AM = theAM;
}
However, we still have a problem. Our this parameter in setTime(...) might be automatically
given, but we still need an “argument” for it. Specifically, we need to know if this should point
to the same object as home points to, or if instead it should point to the same object as office
points to. With class methods, we made this clear by passing home or office as an argument
in the argument list, but since with instance methods, the this parameter is not in the regular
parameter list, we also don’t want to list its argument in the regular argument list. So where do
we put the argument home or office?
The answer is, we move that argument to the front of the method call, and place a dot after it.
That is, we move to this syntax:
home.Clock.setTime(2, 15, true);
The argument home – whose value we want to copy into the automatic parameter this – gets
placed in the front of the method call. And the other arguments – whose values get copied into the
parameters we actually listed in the method’s parameter list – get placed in the actual argument
list within the parenthesis, as usual.
There is one more change left to make. We do not need both home and Clock in front of the
method call. We’ve already explained that home has to go there. But home not only points to the
object that we want this to point to, but in addition, since home is of type Clock, that tells the
compiler to look in the Clock class for this method, and so we do not need to explicitly list the
typename “Clock” after home in the method call. In fact, we don’t even have the option of leaving
it there if we want – since the extra use of the typename “Clock” is redundant, we have to remove
it. This leaves us with the final version of this method call:
204

home.setTime(2, 15, true);

And then the other three method calls in main() are likewise changed in this manner:

Clock.setTime(office, 7, 14, false); ----> office.setTime(7, 14, false);


Clock.printClock(home); ----> home.printClock();
Clock.printClock(office); ----> office.printClock();

And that change – which is our fourth and final change – gives us the following version of
ClockTest.java:

public class ClockTest


{
public static void main(String[] args)
{
// declare reference variables
Clock home;
Clock office;

// allocate objects and assign reference variables


home = new Clock();
office = new Clock();

// set the times on the clocks


home.setTime(2, 15, true);
office.setTime(7, 14, false);

// print the clocks


home.printClock();
office.printClock();

} // end main
} // end of class

which compiles together with the final version of Clock.java we had earlier:
205

public class Clock


{
public int hour;
public int minutes;
public boolean AM;

// There is a "Clock this" parameter here automatically, because


// setTime(...) is an instance method
public void setTime(int theHour, int theMinutes, boolean theAM)
{
this.hour = theHour;
this.minutes = theMinutes;
this.AM = theAM;
}

// There is a "Clock this" parameter here automatically, because


// printClock(...) is an instance method
public void printClock()
{
// print variables for clock
System.out.print("Time is " + this.hour + ":");
if (this.minutes < 10)
System.out.print("0");
System.out.print(this.minutes + " ");
if (this.AM == true)
System.out.println("AM.");
else // AM == false
System.out.println("PM.");
}
}

Now that those four changes have been made, we have converted our example over from using
class methods, to using instance methods. The two examples work basically the same way; the
instance method version just has slightly different syntax.
For example, consider this code snippet from our newest version of ClockTest.java:

home.printClock();

What we are doing here is invoking printClock off a Clock reference. Before we actually enter
the printClock method, our memory looks something like this:
206

_____________________________
| |
| main | ________
| | | |
| home --------|--------> |Clock |
| | |object|
| | |______|
| |
| |
|___________________________|

Once the method call actually begins, our memory looks something like this:

_____________________________
| |
| main | _______
| | | |
| home ----------|--------> |Clock |
| | |object|
| | |______|
| | ^
| ______________________|_______ |
| | | |
| | printClock this -----|-----|
|_____| |
| |
| |
|____________________________|

That is, this points to the same object that home points to. The reference variable home holds
the machine address where the Clock object is located (which is why we say home “refers” to the
object), and that machine address is copied into this, so that this now also holds the machine
address where the Clock object is located (which is why we say that this now “refers” to the
Clock object too). The class method version of this program worked the same way, except that (1)
we called the parameter in printClock(...) “c” instead of “this”, and (2) we actually sent home
as an argument in that case, to an actual parameter c, instead of having our argument in front of
the method call and our parameter automatically created by the compiler.
Of course, in these examples, this is of type Clock only because the instance method is in the
Clock class. If the instance methods were in the String class, the this in each instance method
would be of type String, for example. The type of an instance method’s this reference matches
the type the instance method is a part of (i.e. matches the class the instance method is in).
207

Taking advange of the this default use

One nice thing about the use of this is that it is often assumed by default. For example, our
printClock() instance method could also have been written as follows:

public void printClock()


{
System.out.print("Time is " + hour + ":");
if (minutes <= 9)
System.out.print("0");
System.out.print(minutes + " ");
if (AM == true)
System.out.println("AM.");
else // AM == false
System.out.println("PM.");
}

We can put the this. in front of hour, minutes, and AM if we want, but we don’t have to – if
we leave the this. off, the compiler assumes that we meant to put it there anyway, and so it will
put it in for us. For that reason, instance methods are usually written as you see directly above,
without the explicit use of this. in front of the instance variables of the class. After all, if the
compiler will put it in for you anyway, why bother typing it in yourself? That is another way that
using instance methods makes our life as programmers a little bit easier. (Now, in CS125, we will
always write this. where it is needed, just to remind you what is going on. However, you are not
required to do so yourself.)
The one exception to this would be if you have a parameter or local variable with the same
name as your instance variables:

public void printClockStrangeExample(int hour)


{
int minutes = 10;
System.out.print("Time is " + hour + ":");
if (this.minutes <= 9)
System.out.print("0");
System.out.print(minutes + " ");
if (AM == true)
System.out.println("AM.");
else // this.AM == false
System.out.println("PM.");
}

In the code above, when hour is printed to the screen, it will be the parameter hour, not the
instance variable hour. Likewise, when minutes is printed to the screen, it will be the local variable
minutes which we have assigned the value 10. When the compiler sees a variable in a method, the
order it checks things in is as follows:
208

1. First, it sees if this was a local variable declared in this method.

2. If not, it sees if it was a parameter variable for the method.

3. If it is not either of the above two, then, if the method is a class method, there are no other
options and the compiler will alert you to an error. If the method is an instance method,
however, there is one additional possibility – that the variable is an instance variable of the
class the method is in, and that we are trying to access that instance variable but just didn’t
bother to put a this. in front of the variable name.

The version of printClockStrangeExample below shows the above code, changed so that the
hour and minutes that are printed to the screen are the instance variables and not the parameter
and local variable. Note that we still can avoid putting a this. in front of AM, since there’s no
confusion there. But we need the this. in front of hour and minutes now to make it clear that we
want the instance variable version, rather than assuming the use of minutes is the local variable
(choice 1 in the list above) or assuming that the use of hour is the parameter variable (choice 2 in
the list above).

public void printClockStrangeExample(int hour)


{
int minutes = 10;
System.out.print("Time is " +
this.hour + ":");
if (this.minutes <= 9)
System.out.print("0");
System.out.print(this.minutes + " ");
if (AM == true)
System.out.println("AM.");
else // this.AM == false
System.out.println("PM.");
}

Note that having local variables with the same names as your parameters, or having local
variables or parameter variables with the same names as your instance variables, is NOT a good
idea, due to the fact that it can cause exactly the confusion we have described above. So this
issue tends to not really come up since usually you give your parameter variables and your local
variables different names than you give to your instance variables. And since this issue doesn’t
come up often, as a general rule you can just leave the this. off and the compiler will assume you
meant to put it in, and everything will be fine.
209

Instance variables in memory

The machine is able to access the instance variables of an object precisely because it knows the
starting address of the object. When you have a line of code such as:

home.printClock();

as we have discussed, the starting address of the object is what is located in the reference variable.
So, we know where the overall object begins.
So the compiler, when compiling the code for the method, defines all the accessing of an object
in terms of the starting address of the object. If some instance variable within the object (such
as minutes, for objects of type Clock) is always four cells from the beginning of the object, when
once you know the starting address of the object, you simply move four more cells downward and
there is the particular instance variable you are looking for. If the object begins at a60, then
your instance variable (such as minutes) is at a64. If your object instead begins at a10084, then
your instance variable that is four cells from the start, instead begins at a10088. So we need the
reference variables – they tell us the starting address of the object, and that is the only information
we are missing!
210

Lecture 18 : static versus non-static, and Constructors


Some more terminology

• instance – an object. Every object is an instance of a particular class.

• instantiation – the allocation of an object. We have a class, and we “allocate an object of


that class”, or “instantiate an instance of that class”.

• The two kinds of methods:

– instance method – does NOT have static on its first line; this method is tied to the
instance – i.e. you choose an instance, run the method on an instance and it operates
on the variables of that instance and no other, until that method call has completed.
printClock() from the last lecture is an instance method of the class Clock; when you
called the method printClock(...), you indicated what reference’s object it should
manipulate, by listing that reference before the dot in front of the method call, and then
the method call manipulated that object (i.e. its this reference pointed to that object).
If you do not specify which instance the method should manipulate, then you cannot
call the method (i.e. the method call is a syntax error unless you have a reference and
dot in front of it)
– class method – has static on its first line; this method is not tied to any particular
instance, but rather is just a helpful method that we’ve put in this class because it’s
related to this class. Keyboard.readInt() is a class method.

• The four kinds of variables:

– local variable – a variable declared using a variable declaration statement within the
curly braces of a method
– parameter variable – a variable “declared” in the parameter list of a method (the this
variable of an instance method gets included in this category, though the compiler de-
clares it automatically, without you needing to write code to do so)
– instance variable – a variable declared using a variable declaration statement within a
class but not within any method, and without the keyword static on the declaration
line; such a variable is tied to an instance – i.e. each instance of that class has its own
version of the instance variable, and two different instances will each have their own,
independent version of an instance variable, rather than sharing the same variable. In the
last three notes packets, hour, minutes, and AM are instance variables of the class Clock.
(In a sense, the indexed array cells of an array are also instance variables, though since
they are “named” with indices instead of names, we don’t usually call them “instance
variables”.)
– class variable – a variable declared using a variable declaration statement within a class
but not within any method, and with the keyword static on the declaration line; there
is one copy of this variable, that can be accessed from anywhere in the program, through
the class name itself – rather than one copy per instance, that gets accessed through a
reference to that instance. (We’ll talk more about class variables in a moment.)
211

So, we saw last time that instance methods and class methods basically did the same thing, just
via different syntax. So, why might you use one over the other? Well, basically, instance methods
place the “focus’ on your data instead of on the method. That is to say, there’s no real difference
in the machine between the two. They are just two different sets of syntax for accomplishing the
same thing – it’s just that the syntax for instance methods lets you think of a method as something
that runs on an object, rather than thinking of an object as something you send to a method. So
the use of instance methods can change the way you view your program – you tend to think of
your data first, rather than thinking about your subroutines first. That’s a subtle difference, so for
now, we’ll just say that sometimes class methods are more convenient, and other times instance
methods are more convenient. You won’t have to choose which is the better way in this course,
but you do want to understand the syntax for writing and using both kinds of methods. Generally,
though, we will make something an instance method if we can.
Another way to look at the instance method syntax is that it makes calling instance methods
consistent with using instance variables. If you have an instance variable or instance method, you
access it by using a reference to that instance (i.e. a reference to that object), followed by a dot,
followed by the variable or method. For example:

Clock c1 = new Clock();


c1.hour = 9;
c1.minutes = 53;
c1.AM = true;
c1.printClock();

If you have a class variable or class method, you access it by using the class name, followed
by a dot, followed by the variable or method. For example, in the Math class you might see the
following:

public static double PI = 3.1415926;


public static double cos(double x) {...}

and you’d use those things as follows:

double myVal = Math.PI;


double cosOfMyVal = Math.cos(myVal);

You can have classes with a mix of class variables and methods, and instance variables and
methods, but we won’t do that too often in this course – our classes will tend to have only “class
stuff” (i.e. “static stuff”) or only “instance stuff” (i.e. “non-static stuff”).
212

Constructors

Constructors are instance methods used to initialize an object when it is first created.

• Same name as class

• No return type

• When object is created, some constructor is always called. (what if you don’t write one?
More on that later.)

Let’s look at the Clock class again...but this time let’s leave out the instance method SetTime
(we could have kept it as well, but this way the class will still fit on one slide) and add a constructor
which does the same thing as SetTime did).

public class Clock


{
public int hour;
public int minutes;
public boolean AM;

public Clock(int theHour, int theMinutes,


boolean theAM)
{
this.hour = theHour;
this.minutes = theMinutes;
this.AM = theAM;
} // but you don’t *need* the this. part

public void printClock()


{
System.out.print("Time is " + this.hour + ":");
if (this.minutes < 10)
System.out.print("0");
System.out.print(this.minutes + " ");
if (this.AM == true)
System.out.println("AM.");
else // AM == false
System.out.println("PM.");
}
} // end of class
213

public class ClockTest


{
public static void main(String[] args)
{
// declare reference variables
Clock home;
Clock office;

// create "home" object;


// initialize "home" reference
home = new Clock(2, 15, true);

// create "office" object;


// initialize "office" reference
office = new Clock(7, 14, false);

// call instance method to print


// instance variables
home.printClock();
office.printClock();
}
}

The expression:

new Clock(2, 15, true)

or, in fact, any object allocation (except for an array), is a three-step process:

new Clock(2, 15, true) object is


--------- allocated
(1)

new Clock(2, 15, true)


------------------ object is
(2) initialized
using constructor

new Clock(2, 15, true)


---------------------- address of
| object in memory
address <------| (3) is returned

and then of course that returned address would be stored in a reference if the expression new
Clock(2, 15, true) were used in a line like this:

Clock c1 = new Clock(2, 15, true);


214

Method Overloading

You can have multiple constructors in one class. Having two methods with the same name (but
different parameter lists) is known as method overloading, since we are overloading the method name
with multiple definitions. If the name is the same, the compiler can only figure out which method
we want by comparing parameter lists, so the parameter lists must then be different. This can be
done for constructors or for any other methods. When you call the System.out.println(...)
methods, the same idea exists there – there are many different versions of that method, all with
different parameter lists. The method with no parameters, matches the case where you have no
arguments in your System.out.println() call. The method with one integer parameter, matches
the case where you have one integer argument for your method call. And so on.

public class Clock


{
public int hour;
public int minutes;
public boolean AM;

// a constructor
public Clock()
{
this.hour = 12;
this.minutes = 0;
this.AM = true;
}

// another constructor
public Clock(int theHour, int theMinutes,
boolean theAM)
{
this.hour = theHour;
this.minutes = theMinutes;
this.AM = theAM;
}

// a third constructor
public Clock(Clock c)
{
this.hour = c.hour;
this.minutes = c.minutes;
this.AM = c.AM;
}
215

// a non-constructor instance method


public void printClock()
{
System.out.print("Time is " + this.hour + ":");
if (this.minutes < 10)
System.out.print("0");
System.out.print(this.minutes + " ");
if (this.AM == true)
System.out.println("AM.");
else // AM == false
System.out.println("PM.");
}

// another non-constructor instance method


public void setTime(int theHour, int theMinutes, boolean theAM)
{
this.hour = theHour;
this.minutes = theMinutes;
this.AM = theAM;
}
} // end of class

public class ClockTest


{
public static void main(String[] args)
{
// declare reference variables
Clock home;
Clock office;
Clock car;

// create objects; initialize references


home = new Clock(2, 15, true);
office = new Clock();
car = new Clock(home);

// change time on "home" clock


home.setTime(8, 13, false);

// call instance method to print


// instance variables
home.printClock(); // Time is 8:13 PM.
office.printClock(); // Time is 12:00 AM.
car.printClock(); // Time is 2:15 AM.
}
}
216

Recall that before we introduced constructors, we still had lines like

home = new Clock();

This is because the system provides a default constructor if we don’t write one. This provided
constructor would be empty:

public Clock()
{
// nothing of value here
}

but at least there is some constructor to call, then. That is why the code in lecture packets 15
through 17 would compile – the compiler would have at least given us an empty, useless no-argument
constructor so that some constructor exists to call, since there has to be some constructor called
everytime a (non-array) object is allocated.
If there are any constructors given, no default is provided. So, if you want two constructors and
one of them is the no-argument constructor, you have to write the no-argument constructor. You
can’t write a 2-argument constructor and assume the no-argument one will be added by default.
You only get the no-argument constructor by default if you have no constructors at all in your
source code.
217

Lecture 19 : Access Permissions and Encapsulation


Access Permissions

Since we don’t actually need to access the three variables, let’s hide them away!

public class Clock


{
private int minutes;
private int hour;
private boolean AM;

public Clock()
{
this.hour = 12;
this.minutes = 0;
this.AM = true;
}

public Clock(int theHour, int theMinutes, boolean theAM)


{
this.hour = theHour;
this.minutes = theMinutes;
this.AM = theAM;
}

public void printClock()


{
System.out.print("Time is " + this.hour + ":");
if (this.minutes < 10)
System.out.print("0");
System.out.print(this.minutes + " ");
if (this.AM == true)
System.out.println("AM.");
else // AM == false
System.out.println("PM.");
}
}
218

• public means accessible to client code – the code which declares references to this type and
allocates objects of this type.

• private means accessible only to this class and its instance methods and instance variables

• (If instead of having public or private, you have no access permission listed at all, then
that implies package access. We will not be using that in this course – we will always have
some specific access permission keyword instead of leaving one out entirely.)

• The collection of method signatures for the public instance methods (along with documen-
tation telling the client what those methods do) is called the interface of a class. The code
which makes the interface work – the instance variables, the definitions for the public in-
stance method signatures, and any private instance methods you have written – is called
the implementation.

• By keeping the instance variables private, the implementation is hidden from the client! This
is called data hiding or encapsulation. We force the client to use only the public methods –
the interface – to access an object.

• The question is, why would we want to do this? More on that in a minute. First, an example.

public class ClockTest


{
public static void main(String[] args)
{
// declare reference variables
Clock home;
Clock office;

// allocate and initialize new clocks,


// point references to those clocks
home = new Clock(2, 15, true);
office = new Clock();

// print variables for clock at home


home.printClock();

// print variables for clock at office


office.printClock();

// these two lines will cause compiler


// errors because variables are private
home.AM = true;
office.minutes = 34;
}
}
219

What you are restricting is access to the name – the name of a variable or name of a method.
When we say that the line:

home.AM = true;

will cause compiler errors, we are not saying that home.AM does not exist. Quite the contrary –
home does indeed point us to a Clock object, and that Clock object does indeed have two integer
variables and a boolean variable, just like any other Clock object. However, those variables are
somewhere in memory. We have no idea where the object is, and we thus have no idea where its
variables are. The only way for us to get to those variables is by using Java syntax such as the
line above. The code we run knows where the object is, because of home, and it knows where AM is
within that object, because it knows the definition of Clock.
So there is no problem with using the line above, in theory. When we make our AM instance
variable private, we are saying, “have the compiler complain about this, for no other reason than
that we want the compiler to complain about it”. Nothing changes in memory as a result of using
private instead of public. All that changes is that now, instead of the compiler letting us do
whatever we want to the Clock object variables, the compiler now complains instead when we use
the Clock variables.
Imagine if we had said:

int x;

and then every time we wanted to use x, the compiler gave us an error message and said, “Sure, x
does exist, it is a variable, and it stores a value, but I don’t want you using x. Go away.” That’s
the idea of making something private.
Why do this? Why hide the implementation from the client so that the client cannot use it
directly?

1. Too much flexibility can be dangerous – why offer it if you don’t want the client to have it?

2. If we use instance variables directly, and change the collection of instance variables we use to
implement the class, then we have to find and change all code that used the old collection of
instance variables, and make it use the new collection of instance variables instead. Why put
ourselves in the position of having to do so much work?

3. A new programmer doing code maintenance has to understand a lot of code written using
various specific instance variables. Why put the maintenance programmer in that position?
220

How does hiding implementation solve these problems?

1. Too much flexibility can be dangerous – why offer it if you don’t want the client to have it?
If the client can only invoke the instance methods you provide with the class, but
cannot access the instance variables directly, then the client can only read and
alter the instance variables in the ways that you decide “make sense” ahead of
time.

2. If we use instance variables directly, and change the collection of instance variables we use to
implement the class, then we have to find and change all code that used the old collection of
instance variables, and make it use the new collection of instance variables instead. Why put
ourselves in the position of having to do so much work?
Client code uses only the interface. The interface must be well-designed and
thought out before we offer the class to the world. But once we do, then we can
change the implementation as much as we want but client code doesn’t break un-
less we change the interface (because client code doesn’t use the implementation
directly). (See the end of the packet for a re-implementation of the Clock class.)

3. A new programmer doing code maintenance has to understand a lot of code written using
various specific instance variables. Why put the maintenance programmer in that position?
The maintenance programmer can read code which uses only the method calls
which are part of the interface. These are given intuitive names to make code eas-
ier to understand. Thus client code focuses on setting big ideas into motion, and
the details are hidden in the implementation and the maintenance programmer
doesn’t necessarily need to know them.

• In addition, throw in one more useful idea of classes in general. If all your data is declared
in main, it is hard to reuse code in later programs. But if you write a class instead, in a new
program you can simply create new objects of that class – you’ve already written the class
and have it sitting in a file, easily used in any new program without having to copy-and-paste
or rewrite any code.

• So, those are four arguments for using classes instead of declaring all data in main and
declaring all methods in the class which holds main.

• The four advantages suggest using classes is a very good idea.

• The technique of using encapsulated classes is known as object-based programming, since the
focus is on deciding what types we need, and then writing classes to represent those types
and creating objects of those types to hold our data. What we have accomplished is data
abstraction – hiding the details of a data implementation away behind the scenes, and focusing
only on the big picture of what the data is meant to represent conceptually. This was the
same idea that we saw with procedural abstraction – in that case, we hid the details of how
a task was performed, and focused on the big picture of what particular task those details
ultimately accomplished. In our Clock example, we have hidden away the details of how a
Clock is implemented, and focused on the big picture of how we would want to use a Clock
(rather than how we would want to use integers and booleans).
221

public class Clock


{
private int hour;
private int minutes;

public Clock()
{
this.hour = 0;
this.minutes = 0;
}

public Clock(int theHour, int theMinutes, boolean theAM)


{
if (theAM == true)
{
if (theHour == 12)
this.hour = 0;
else
this.hour = theHour;
}
else // theAM == false
{
if (theHour == 12)
this.hour = 12;
else
this.hour = theHour + 12;
}

this.minutes = theMinutes;
}
222

public void printClock()


{
// print variables for clock
System.out.print("Time is ");
if (this.hour == 0)
System.out.print(12);
else if ((this.hour >= 1) && (this.hour <= 12))
System.out.print(this.hour);
else // ((this.hour >= 13) && (this.hour <= 23))
System.out.print(this.hour - 12);
System.out.print(":");
if (this.minutes < 10)
System.out.print("0");
System.out.print(this.minutes + " ");
if (this.hour < 12)
System.out.println("AM.");
else // this.hour >= 12
System.out.println("PM.");
}
}
223

Lecture 20 : Copying and Mutability


Copying an object in a constructor

Recall our code from the constructor lecture notes:

public class Clock


{
private int hour;
private int minutes;
private boolean AM;

// a constructor
public Clock()
{
this.hour = 12;
this.minutes = 0;
this.AM = true;
}

// another constructor
public Clock(int theHour, int theMinutes,
boolean theAM)
{
this.hour = theHour;
this.minutes = theMinutes;
this.AM = theAM;
}

// a third constructor
public Clock(Clock c)
{
this.hour = c.hour;
this.minutes = c.minutes;
this.AM = c.AM;
}
224

// a non-constructor instance method


public void printClock()
{
System.out.print("Time is " + this.hour + ":");
if (this.minutes < 10)
System.out.print("0");
System.out.print(this.minutes + " ");
if (this.AM == true)
System.out.println("AM.");
else // AM == false
System.out.println("PM.");
}

// another non-constructor instance method


public void setTime(int theHour, int theMinutes, boolean theAM)
{
this.hour = theHour;
this.minutes = theMinutes;
this.AM = theAM;
}
} // end of class

public class ClockTest


{
public static void main(String[] args)
{
// declare reference variables
Clock home;
Clock office;
Clock car;

// create objects; initialize references


home = new Clock(2, 15, true);
office = new Clock();
car = new Clock(home);

// change time on "home" clock


home.setTime(8, 13, false);

// call instance method to print


// instance variables
home.printClock(); // Time is 8:13 PM.
office.printClock(); // Time is 12:00 AM.
car.printClock(); // Time is 2:15 AM.
}
}
225

We pointed out at the time, that after the assignment of car was completed, the object that car
pointed to, was a copy of the object that home pointed to. You did not have two references pointing
to the same object; if we had wanted that, we would have assigned car as follows:

car = home;

Instead, we used the following line:

car = new Clock(home);

which called the following constructor:

// a third constructor
public Clock(Clock c)
{
this.hour = c.hour;
this.minutes = c.minutes;
this.AM = c.AM;
}

and thus assigned the object that this pointed to (which in the end, is the same object that car
points to), to hold the same values as the object that c points to (which is the same object that
home, the constructor argument, points to. You have two different objects, which hold the same
values.
If you are making a copy of an object like this, you need to be a bit more careful when reference
variables come into play. For example, consider our ExamScores class from Lecture Notes #16
(though we’ve changed the access permission on the instance variables here):

public class ExamScores


{
private int examNumber;
private double average;
private int[] scores;
}

If you write out a constructor to initialize a copy, like this:


226

public class ExamScores


{
private int examNumber;
private double average;
private int[] scores;

public ExamScores(ExamScores origVal)


{
this.examNumber = origVal.examNumber
this.average = origVal.average;
this.scores = origVal.scores;
}
}

Then we have a small problem. First of all, we want to make it clear that access permissions are not
the problem here – even though the instance variables are private, they can still be used directly
by name anywhere in the ExamScores class. So, it’s fine for the constructor to not only read and
write the variables in the object that this points to, but also, to read and write the variables
in the object that origVal points to. Instance methods of ExamScores can read and write any
ExamScores object they have a reference to. It is when we are outside of the ExamScores class that
the access permissions are relevant.
(We also want to make it clear that we would need other methods in this class, if we wanted it
to be a useful class. Even if we only show the one or two methods we are talking about, we would
want others as well.)
That said, the problem with the above constructor is that we are copying the address in
origVal.scores, into this.scores. Copying the value from one variable to another is fine when
you are copying a primitive type, such as with the other two assignments. Both examNumber vari-
ables hold their own copy of the same integer, and both average variables hold their own copy of
the same floating-point value. But, similarly, both scores variables hold their own copy of the same
address. But unlike with primitive-type variables, references lead to objects!! – if two reference
variables hold the same address, then they point to the same object!
This can lead to trouble if it is possible to alter the array at all as a client. For example:
227

public class ExamScores


{
private int examNumber;
private double average;
private int[] scores;

public ExamScores(ExamScores origVal)


{
this.examNumber = origVal.examNumber
this.average = origVal.average;
this.scores = origVal.scores;
}

public void initialize()


{
for (int i = 0; i < this.scores.length; i++)
this.scores[i] = 0;
}
}

If you make a copy using the given constructor, then both ExamScores objects point to the same
array. Now, if you call initialize(...) on one of the instances, the other instance gets initialized
as well – writing to the array cells of one ExamScores object, means writing to the array cells of
the other ExamScores object as well, since the two ExamScores objects point to the same array!!
This is known as a soft copy – we wanted to make a copy of an ExamScores object, but we made
the copy in a way that allowed both objects to share a piece of dynamic memory. As a result, if
we change that dynamic memory for one object, it’s changed for the other as well – meaning the
objects are not actually independent copies of each other. They are still tied together, since we
can’t change one without changing the other.
If we want a truly independent copy – a hard copy – then we need to copy the actual array
object, not just its location:
228

public class ExamScores


{
private int examNumber;
private double average;
private int[] scores;

public ExamScores(ExamScores origVal)


{
this.examNumber = origVal.examNumber
this.average = origVal.average;
this.scores = new int[origVal.scores.length];
for (int i = 0; i < this.scores.length; i++)
this.scores[i] = origVal.scores[i];
}

public void initialize()


{
for (int i = 0; i < this.scores.length; i++)
this.scores[i] = 0;
}
}

Now the two ExamScores objects are completely independent. The array references even point to
different arrays – though for now, those arrays hold the same primitive-type values. So, you can
change one object in any way you want, and it will have no effect on the other object. That is
what we prefer.
Note that you can sometimes get away with a “partial” soft copy. For example, what if we have
a name for our exam, rather than a number:

public class ExamScores


{
private String examName;
private double average;
private int[] scores;
}

In such a case, it would seem that the constructor for making a copy should be written like this:
229

public class ExamScores


{
private String examName;
private double average;
private int[] scores;

public ExamScores(ExamScores origVal)


{
this.examName = new String(origVal.examName);
this.average = origVal.average;
this.scores = new int[origVal.scores.length];
for (int i = 0; i < this.scores.length; i++)
this.scores[i] = origVal.scores[i];
}
}

Such code takes advantage of the “initialize as a copy” constructor that exists in the String class.
Actually, though, even though the above code is correct, we can also do this for that method:

public ExamScores(ExamScores origVal)


{
this.examName = origVal.examName;
this.average = origVal.average;
this.scores = new int[origVal.scores.length];
for (int i = 0; i < this.scores.length; i++)
this.scores[i] = origVal.scores[i];
}

Why is the soft copy okay for a String object? Because the String type is immutable. That is,
once a String object is created, none of the variables are public and none of the instance methods
allow any of the instance variables to be changed. Or in other words, it’s impossible to change a
String object once it is created. So, it’s safe for two ExamScores objects to have references to
the same String object – since you can’t change the String object through either ExamScores
object, you’d never have a situation where it was changed through one object and then as a result
was changed for the other object as well. The worst you could do is have one of the String
reference variables named scores, point to a different String object, but in that case, the other
ExamScores object’s scores variable could still point to the first String. Changing the scores
reference variable of one ExamScores object won’t change the other reference variable in the other
object.
In contrast, objects that are mutable – objects that can be changed – would need to be properly
copied. This is what we did with our array.
230
Part 3 : Algorithm Design and
Recursion

Learning the syntax of a language is all fine and good, but ultimately, it’s not the difficult part of
software design. You might have found it difficult, if you had not programmed before this course,
but that is to be expected – when you are learning your first programming language, you are also
learning the principles of programming at the same time, and that is what causes the difficulty! But
as you learn additional programming languages, you will find that being able to figure out, “here
is where I should print the variable x to the screen” is of far greater importance, and generally a
far harder task, then specifically remembering whether to accomplish this via the statement:

System.out.println(x); // how you print x in Java

or the statement:

std::cout << x << std::endl; // how you print x in C++

or whatever.
That is, understanding the steps that are needed for your procedure, is of far greater importance
than the particular syntax you use to code those steps in a particular language. You cannot write
code until you know what particular series of steps you want to code in the first place, and especially
as our problems get more complex, our solutions – the relevant collections of steps that when run,
solve our problems - also become more complex. Coming up with the necessary collection of steps
is thus a difficult task; in comparison, once you have the steps and are comfortable with the syntax
of a languge, coding the steps in that language is an easy task.
And thus we now turn our attention to the development of these collections of steps that solve
our problems – i.e. we turn our attention to the development of algorithms. When we develop
complex algorithms to solve complex problems, one of our most important design tools is a concept
known as recursion. In the next part of the course, we will be exhaustively studying the idea of
recursion, and seeing how this concept helps us design algorithms. In addition, we will learn various
algorithms and see how to code those algorithms using recursion. Finally, we will explore how to
take code developed using recursion, and optimize it so that the downsides of using recursion are
eliminated from the code.

231
232

Lecture 21 : Introduction to Algorithm Design and Recursion


Our focus now turns to algorithm design. An algorithm is a computational process for solving a
problem. This definition has three important parts:

• Computational – the process must be something a computer could work through in a step-
by-step manner, and all the necessary steps must be specified. Some things have no computa-
tional solutions. For example, even though you can detect that your program has an infinite
loop, there is no generalized way for the computer to do this – meaning, yes, in certain spe-
cial situations perhaps the computer can recognize there is an infinite loop...but usually those
special situations don’t apply and usually the computer cannot recognize an infinite loop in
general.

• Solving – the process must actually produce the desired solution. usually what is desired is
an exact solution to the problem; sometimes all that is wanted is a solution that is “close
enough” to optimal, where “close enough” has some formal definition.

• Problem – the process must solve the general problem, not just a specific instance of it. If
you want to return the sum of five numbers, just returning the integer literal “2” is not an
accurate algorithm, even though that solution does work in some cases.

Algorithm design

Our main focus for much of the next few weeks will be on algorithm design. That is, we will
be concerned with the creation of new algorithms first and foremost, as opposed to worrying about
how to make the algorithm run as fast as possible, in a particular language, on a particular machine.
Algorithms are mathematical, computational things. An algorithm is a process, a description of how
to solve a problem. We can discuss whether the algorithm is sufficiently detailed to be translated
into code, and we can discuss whether the algorithm solves our problem or not – all without needing
to discuss actual code.
233

Recursion

Recursion is the language of algorithm design. It’s not a new power in addition to what you
have, but rather, a new way of looking at a problem, one that allows you to often focus on the major
portion of the solution without worrying about every start-to-finish detail. Recursion is when a
solution formula, or a method, is defined in terms of itself.
For example (the example works better when it’s physically being given during lecture, but
what the hey), consider crossing a room to knock on a door. If you are right next to the door, you
can just knock on that door. If you are one step away, you take one step toward the door, and
then knock. If you are two steps from the door, you take two steps and then knock. And so on.
In general, if you are n steps from the door, take n steps and then knock on the door. This is the
loop solution, and is one you could already write:

// start out n steps from door


void EventuallyKnock(int n)
{
while (n > 0)
{
TakeOneStepTowardDoor();
n = n - 1;
}
KnockOnDoor();
}

Notice that this required visualizing the entire solution at once, from start to finish. Every detail
is accounted for by that loop. (Notice also that it makes no sense for n to be negative, so we’ll
assume for the rest of this discussion that n will always be non-negative.)
Now, instead, imagine that we handle this situation by referring to “simpler problems”. As
before, if you are right next to the door, just knock. Call that the “zero steps away” solution.

# of steps away solution to that problem


--------------- ------------------------
0 1) knock on door

Now, let’s cover up the solution to the “zero steps away” problem. This way, we’ll know we
have a solution; we just won’t know what it is.

# of steps away solution to that problem


--------------- ------------------------
0 [don’t look]

Now, if you are one step away, well, if you took just one step toward the door, you’d be zero
steps away and could run the “zero steps away solution”. What is that solution? Who cares?
Certainly, if we look back at the details we can read what the “zero steps away” solution was
(“knock on door”) but we don’t have to bother. We know that we had previously created a “zero
steps away” solution, and let’s just run that, whatever it is. So, our “one step away” solution is to
take one step, and then run the “zero steps away” solution.
234

# of steps away solution to that problem


--------------- ------------------------
0 [don’t look]

1 1) take a step
2) run 0-steps-away solution

Now, we can cover up the “one step away” solution as well.

# of steps away solution to that problem


--------------- ------------------------
0 [don’t look]

1 [don’t look]

So, what happens when we are two steps away? Well, now there’s a bit more work to do, but
we don’t really need to think about all of it. All we need to do is realize that if we take just one
step, we are now only one step away. And we have a solution for that problem already: the “one
step away” solution. Let’s not worry what the details are; it’s enough to just say, “we discovered
the one-step-away solution earlier, so just run that”. So, the “two steps away” solution is to take
one step, and then run the “one step away” solution, whatever it might be.

# of steps away solution to that problem


--------------- ------------------------
0 [don’t look]

1 [don’t look]

2 1) take a step
2) run 1-step-away solution

We can then cover this solution up as well:

# of steps away solution to that problem


--------------- ------------------------
0 [don’t look]

1 [don’t look]

2 [don’t look]

and then, in trying to solve the “three steps away” problem, we can note that we have a “two steps
away” solution, so we can simply take a step, and then run the “two steps away” solution, whatever
it might be.
235

# of steps away solution to that problem


--------------- ------------------------
0 [don’t look]

1 [don’t look]

2 [don’t look]

3 1) take a step
2) run 2-steps-away solution

In general, we have the following pattern:

# of steps away solution to that problem


--------------- ------------------------
0 1) knock on door

1 1) take a step
2) run 0-steps-away solution

2 1) take a step
2) run 1-step-away solution

3 1) take a step
2) run 2-steps-away solution

. .
. .
. .

n 1) take a step
2) run (n-1)-steps-away solution

The general rule is that if we are n steps away, the solution to our problem is to first take one
step, leaving us (n-1) steps away. Then, we can finish solving the problem, by running the solution
appropriate for being (n-1) steps away, since that is now our situation, thanks to us having just
taken one step. In every situation where we’re a positive number of steps away from the door, we
can solve that problem by taking one step – bringing us one step closer to the door – and then
treating our new situation as a new problem to be solved, and applying our rule all over again.
Thus to write the solution to this problem, we don’t have to have our code consider every detail
from the start to the finish of the problem. We only need the first step and the last step – that is,
we need to know how to finish the solution once we are zero steps away, and we need to know how
to start the solution, by reducing the problem to a smaller subproblem – the same problem, just on
fewer data values or smaller data values) – and then running the solution to that subproblem.
236

We could write things out formally as follows:

______
| TakeOneStepTowardDoor(); if n > 0
| EventuallyKnock(n-1);
EventuallyKnock(n) --|
| KnockOnDoor(), if n == 0
|_____

The first case is called the recursive case – the general case we use to solve most problems.
Since the problem can be solved in exactly the same way for almost every value of n – take one
step, and then run the solution to the next smaller problem – this recursive case is what we run
for almost every value of n. The recursive case is the case that is defined in terms of a solution
to a subproblem. But we can’t define every problem in terms of the solution to other problems;
we need to eventually stop or we’d forever be solving!! And so the second case is the base case.
This is the case whose answer is not defined in terms of a subproblem (i.e. whose answer is not
defined in terms of the same algorithm run on a a smaller data quantity or smaller data value).
EventuallyKnock(0) is not defined in terms of other solutions to EventuallyKnock. Whereas for
any n greater than 0, we define that solution partly in terms another solution to EventuallyKnock.
Describing things recursively provides the advantage of not having to describe every single
detail of the solution. We only need to explain two important things : how to reduce the current
problem to a smaller one (the recursive case), and how to solve the smallest possible problem (the
base case). All the details of figuring each solution out in turn is left to the definition itself. If
you want to know the answer to EventuallyKnocks(3), then by definition, you need to know
the solution to EventuallyKnocks(2), and to know that, you by definition need to know the
solution to EventuallyKnocks(1), and to solve that, you by definition need to know the solution
to EventuallyKnocks(0), which our definition already provides us.
We could then take that formal description above and create a recursive method from it:

void EventuallyKnock(int n)
{
if (n > 0)
{
TakeOneStepTowardDoor();
EventuallyKnock(n-1);
}
else
KnockOnDoor();
}

When we pass this method a positive value as an argument, the method will hit the recursive
case, and will call itself with a new argument. When a method calls itself, it is a recursive method.
(For example, if we sent in 10 as an argument, because we want to solve the problem of being 10
steps away, we will take a step, and then make the call EventuallyKnock(9);, which will pass 9
as an argument to the same method, and which will solve the problem of being nine steps away.
237

Not having to provide every little detail to handle the entire problem from start to finish in
one method call has three advantages – first, it is often easier to come up with solutions when you
don’t have to think all the way through every detail. For simple problems, this might not be the
case – for example, the loop version (called an iterative version) for the above problem probably
seems a bit clearer to you right now. But for more complex problems, it might not be at all clear
how to solve it with a loop, while the recursive solution in the same circumstances could be quite
obvious. Second, it makes solutions you do come up with easier to understand. Again, since the
above problem is very simple and since you are just learning recursion, this might not seem true
– but for more complex problems, describing just the first and last step makes for a more concise,
clear description than trying to trace out the calculation from start to finish. And third, those
solutions are also easier to prove correct, due to the proof technique of mathematical induction,
which you will learn in CS173.
(Very briefly – proving something via the use of mathematical induction involves proving both a
base case about that something, and proving a recursive case about it. For example, if you wanted
to prove that the sum of all positive integers from 1 through n was given by the formula
(n * (n+1))/2, you would first prove that the formula worked for n==1 (the base case), and then
you would prove that if the formula was true for some value n==k, then it must also be true for the
value n==k+1. Once you’ve proved that second statement, well, then since you’ve already proved
the formula is true for n==1, you know it’s true for n==2. And then since it’s true for n==2, it
must also be true for n==3. And then since it’s true for n==3, it must also be true for n==4. And
so on. That is the idea of proof by mathematical induction – as you can see, there’s a very close
relationship between that proof technique, and the idea of recursion we are discussing. And that
is why it is such a nice proof technique for proving that a recursive algorithm is correct. But as we
said above, you will learn more about this in CS173.)
238

The last problem was somewhat abstract. Let’s try again, but this time, with an actual real,
mathematically-based problem. You might recall the definition of “factorial”; “n factorial” (notated
n!) is the product of all the integers from 1 through n, inclusive:

// product of all numbers from 1 through n.


n! = n * (n-1) * (n-2) * (n-3) * ... * 2 * 1

We can write an iterative method (i.e. a method using loops) to calculate n!:

// Iterative version
public static int fac(int n)
{
int product = 1;
while (n > 0)
{
product = product * i;
n = n - 1;
}
return product;
}

Notice that, in the above code, if we had sent 0 in as the argument, we would get 1 back as the
return value. This is what we want to have happen; mathematically, 0! is defined to be 1. We do
not define factorial for negative values, and thus we will assume that there is always a non-negative
value passed as an argument to the parameter n.
Now, in this situation, we had the “...” in our definition of factorial above, to indicate “follow
the same pattern”, and we are assuming that anyone who looks at that definition can see that we
are subtracting a value one larger from n each time, and so the missing terms should continue in
that same pattern n-4, n-5, n-6, and so on.
We could also define fac(n) a different way, in terms of itself. If we are trying to calculate n!,
then consider all possible subproblems: 1!, 2!, etc., all the way up to (n-2)! and (n-1)!. Which
of these might help us in calculating n!? Well, if we have (n-1)!. we only need to multiply it by
n to get the final result! That is to say:

// product of all numbers from 1 through n.


n! = n * (n-1) * (n-2) * (n-3) * ... * 2 * 1
|___________________________________|
this part is (n-1)!

giving us the shortened formula:

n! = n * (n-1)!
239

Of course, we need a base case as well. Let’s take n==0 as our base case, since mathematically, we
define 0! to be equal to 1, and since we don’t define factorial for anything less than 0 anyway.
That gives us the following algorithm for calculating factorial:

|
n! = | n * (n-1)! if n > 0
|
(n>=0) | 1 if n == 0

For example, you would compute 5! as follows:

5! = 5 * 4!
/|\
|
4! = 4 * 3!
/|\
|
3! = 3 * 2!
/|\
|
2! = 2 * 1!
/|\
|
1! = 1 * 0!
/|\
|
0! = 1

Then once you know 0! is 1, you substitute 1 for 0! in the expression 1 * 0!...and that gives
you 1. So now that you know 1! is 1, you substitute 1 for 1! in the expression 2 * 1!...and that
gives you 2. And now that you know that 2! is 2, you substitute 2 for 2! in the expression 3 *
2!...and you get 6. And now that you know that 3! is 6, you substitute 6 for 3! in the expression 4
* 3!...and you get 24. And now that you know 4! is 24, you substitute 24 for 4! in the expression
5 * 4!...and you get 120. And now you know that 5! is 120.
You can write the above algorithm as a recursive method fac. If you are trying to compute n!
via the method call fac(n), you would compute (n-1)! via the method call fac(n-1), leading to
the following code:

// reminder: we are assuming n is non-negative


public static int fac(int n)
{
if (n > 0)
return n * fac(n - 1);
else // n == 0
return 1;
}
240

What we are going to do next is go step by step through the calculation of 4! using the recursive
method we just wrote. We start with a main() method that makes a call to fac(4):

_____________________________________________________________________
| main
| int x;
| x = fac(4); // we call the fac method
|
|____________________________________________________________________

Now, as with any method, when we call that method, we set up a “notecard” (some area of memory
inside the machine) to store the parameters and local variables for that method call. It’s no different
with recursive methods:

_____________________________________________________________________
| main
| int x;
| x = fac(4); // we call the fac method, and in doing so,
| // set up the "fac" notecard below:
|____________________________________________________________________
| fac (n: 4) // this is the notecard for the first call to
| (#1) // fac; 4 was passed to this method as an
| // argument, and thus is stored in the parameter "n"
|
|_____________________________________________________________________

Now that we’ve set up the fac notecard, we run the fac method on the data in the fac notecard.
Since n is not zero, we will take the recursive case, which requires we calculate (n − 1)!, i.e. 3!.

_____________________________________________________________________
| main
| int x;
| x = fac(4); // we call the fac method, and in doing so,
| // set up the "fac" notecard below:
|____________________________________________________________________
| fac (n: 4)
| (#1)
|
| need to calculate: fac(3), so we can use its value to
| complete the multiplication in the
| expression
|
| n * fac(n - 1)
| i.e.
| 4 * fac(3)
|__________________________________________________________________
241

So, we make another call to our fac method. That is, from the call fac(4), we will make the
call fac(3), and therefore we will start a second fac notecard, one where n is 3. This is perfectly
acceptable! In past method examples, when you made a method call, the method you were leaving
and the method you were starting were two different methods; in this case, they are the same
method. This does not matter. You can indeed have the same set of instructions run on two
different notecards. Since the notecards have different values for n, you’ll get different results.
If you’re having trouble understanding this, imagine if I write the expression:

(a + b) * (c - d)

on two different pieces of paper. I could give one of those pieces of paper to one student in CS125,
and I could give the other piece of paper to a different student in CS125. So, both students have
the same expression in front of them. Now, if I tell the first student “a is 4, b is 7, c is 2, and
d is 3”, the student could plug those values into the expression, and get the result, -11. I could
then tell the second student, “a is 5, b is 1, c is 9, and d is 6”, and that student could plug those
values into the expression, and get the result 18. Both students can evaluate the expression; they
have different values for a, b, c, and d, so they get different results – but you can indeed have two
different students evaluate the same expression. Similarly, you can have two different notecards
both setup to run the same method. If the values stored on the first notecard are different than the
equivalent values stored on the second nodecard (such as n being 4 versus n being 3), the method
calls will accomplish different work...but there’s no reason you can’t have two different notecards,
both of them being scratch areas for the same set of instructions, just like two different students
can both work on evaluating their own copy of the same expression.

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| need to calculate: fac(3), in order to calculate 4 * fac(3)
|
|__________________________________________________________________
| fac (n: 3)
| (#2)
|
| need to calculate: fac(2), so we can use its value to
| complete the multiplication in the
| expression
|
| n * fac(n - 1)
| i.e.
| 3 * fac(2)
|__________________________________________________________________
242

As that last picture showed, within the fac(3) call, we will find that we need a fac(2) call,
and so we will make that call:

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| need to calculate: fac(3), in order to calculate 4 * fac(3)
|
|__________________________________________________________________
| fac (n: 3)
| (#2)
|
| need to calculate: fac(2), in order to calculate 3 * fac(2)
|
|__________________________________________________________________
| fac (n: 2)
| (#3)
|
| need to calculate: fac(1), so we can use its value to
| complete the multiplication in the
| expression
|
| n * fac(n - 1)
| i.e.
| 2 * fac(1)
|__________________________________________________________________

From within the fac(2) call, we will need a fac(1) call, so we make that call:
243

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| need to calculate: fac(3), in order to calculate 4 * fac(3)
|
|__________________________________________________________________
| fac (n: 3)
| (#2)
|
| need to calculate: fac(2), in order to calculate 3 * fac(2)
|
|__________________________________________________________________
| fac (n: 2)
| (#3)
|
| need to calculate: fac(1), in order to calculate 2 * fac(1)
|
|__________________________________________________________________
| fac (n: 1)
| (#4)
|
| need to calculate: fac(0), so we can use its value to
| complete the multiplication in the
| expression
|
| n * fac(n - 1)
| i.e.
| 1 * fac(0)
|__________________________________________________________________

From within the fac(1) call, we will need a fac(0) call, so we make that call:
244

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| need to calculate: fac(3), in order to calculate 4 * fac(3)
|
|__________________________________________________________________
| fac (n: 3)
| (#2)
|
| need to calculate: fac(2), in order to calculate 3 * fac(2)
|
|__________________________________________________________________
| fac (n: 2)
| (#3)
|
| need to calculate: fac(1), in order to calculate 2 * fac(1)
|
|__________________________________________________________________
| fac (n: 1)
| (#4)
|
| need to calculate: fac(0), in order to calculate 1 * fac(0)
|
|__________________________________________________________________
| fac (n: 0)
| (#5)
|
| base case! (we have not returned yet)
|__________________________________________________________________

We make and pause four separate calls to fac, finally making our fifth call which is our base case.
245

Then, since the base case doesn’t need to make any further recursive calls, we can start returning
from there. The base case itself returns 1:

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| need: fac(3), in order to calculate 4 * fac(3)
|
|__________________________________________________________________
| fac (n: 3)
| (#2)
|
| need: fac(2), in order to calculate 3 * fac(2)
|
|__________________________________________________________________
| fac (n: 2)
| (#3)
|
| need: fac(1), in order to calculate 2 * fac(1)
|
|__________________________________________________________________
| fac (n: 1)
| (#4)
|
| need: fac(0), in order to calculate 1 * fac(0)
|
|__________________________________________________________________
| fac (n: 0)
| (#5)
|
| base case! ------> returns 1
|__________________________________________________________________
246

Therefore, the value 1 is returned back to the previous call and is substituted for “fac(1)”, the
method call expression that triggered the base case to be started:

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| need: fac(3), in order to calculate 4 * fac(3)
|
|__________________________________________________________________
| fac (n: 3)
| (#2)
|
| need: fac(2), in order to calculate 3 * fac(2)
|
|__________________________________________________________________
| fac (n: 2)
| (#3)
|
| need: fac(1), in order to calculate 2 * fac(1)
|
|__________________________________________________________________
| fac (n: 1)
| (#4)
|
| need: fac(0), in order to calculate 1 * fac(0)
| ------
|__________________________________________ /|\ __
|
base case returned 1; that is, |
the method call fac(0) returned 1...so now this expression
is replaced by the value 1
247

Now that we have a value for fac(0), we can finally complete the multiplication, since now we
have both operands:

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| need: fac(3), in order to calculate 4 * fac(3)
|
|__________________________________________________________________
| fac (n: 3)
| (#2)
|
| need: fac(2), in order to calculate 3 * fac(2)
|
|__________________________________________________________________
| fac (n: 2)
| (#3)
|
| need: fac(1), in order to calculate 2 * fac(1)
|
|__________________________________________________________________
| fac (n: 1)
| (#4)
|
| this method call is supposed 1 * 1
| to return 1 * fac(0) which ------ <---- so now this expression
| is 1 * 1 which is 1 evaluates to 1
|_____________________________________________________
248

Finally, we are supposed to return the result of that multiplication:

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| need: fac(3), in order to calculate 4 * fac(3)
|
|__________________________________________________________________
| fac (n: 3)
| (#2)
|
| need: fac(2), in order to calculate 3 * fac(2)
|
|__________________________________________________________________
| fac (n: 2)
| (#3)
|
| need: fac(1), in order to calculate 2 * fac(1)
|
|__________________________________________________________________
| fac (n: 1)
| (#4)
|
| this method call is supposed 1
| to return 1 * fac(0) which ------ <---- so now this value
| is 1 * 1 which is 1 is returned
|_____________________________________________________
249

When the fourth fac call returns 1, then the program returns back to the third fac call, and
now that the fac(1) call is over, it is replaced by its return value, which is 1:

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| need: fac(3), in order to calculate 4 * fac(3)
|
|__________________________________________________________________
| fac (n: 3)
| (#2)
|
| need: fac(2), in order to calculate 3 * fac(2)
|
|__________________________________________________________________
| fac (n: 2)
| (#3)
|
| need: fac(1), in order to calculate 2 * fac(1)
| ------
|__________________________________________ /|\ __
|
|
the method call fac(1) returned 1...so now this expression
is replaced by the value 1
250

And then this call works as the previous one did. First, the multiplication is completed:

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| need: fac(3), in order to calculate 4 * fac(3)
|
|__________________________________________________________________
| fac (n: 3)
| (#2)
|
| need: fac(2), in order to calculate 3 * fac(2)
|
|__________________________________________________________________
| fac (n: 2)
| (#3)
|
| this method call is supposed 2 * 1
| to return 2 * fac(1) which ------ <---- so now this expression
| is 2 * 1 which is 2 evaluates to 2
|_____________________________________________________
251

And then we were supposed to return the result of that multiplication:

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| need: fac(3), in order to calculate 4 * fac(3)
|
|__________________________________________________________________
| fac (n: 3)
| (#2)
|
| need: fac(2), in order to calculate 3 * fac(2)
|
|__________________________________________________________________
| fac (n: 2)
| (#3)
|
| this method call is supposed 2
| to return 2 * fac(1) which ------ <---- so now this value
| is 2 * 1 which is 2 is returned
|_____________________________________________________
252

When the third fac call returns 2, then the program returns back to the second fac call, and
now that the fac(2) call is over, it is replaced by its return value, which is 2:

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| need: fac(3), in order to calculate 4 * fac(3)
|
|__________________________________________________________________
| fac (n: 3)
| (#2)
|
| need: fac(2), in order to calculate 3 * fac(2)
| ------
|__________________________________________ /|\ __
|
|
the method call fac(2) returned 2...so now this expression
is replaced by the value 2

And then this call works as the previous two did. First, the multiplication is completed:

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| need: fac(3), in order to calculate 4 * fac(3)
|
|__________________________________________________________________
| fac (n: 3)
| (#2)
|
| this method call is supposed 3 * 2
| to return 3 * fac(2) which ------ <---- so now this expression
| is 3 * 2 which is 6 evaluates to 6
|_____________________________________________________
253

And then we were supposed to return the result of that multiplication:

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| need: fac(3), in order to calculate 4 * fac(3)
|
|__________________________________________________________________
| fac (n: 3)
| (#2)
|
| this method call is supposed 6
| to return 3 * fac(2) which ------ <---- so now this value
| is 3 * 2 which is 6 is returned
|_____________________________________________________

When the second fac call returns 6, then the program returns back to the first fac call, and
now that the fac(3) call is over, it is replaced by its return value, which is 6:

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| need: fac(3), in order to calculate 4 * fac(3)
| ------
|__________________________________________ /|\ __
|
|
the method call fac(3) returned 6...so now this expression
is replaced by the value 6
254

And then this call works as the previous three did. First, the multiplication is completed:

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| this method call is supposed 4 * 6
| to return 4 * fac(3) which ------ <---- so now this expression
| is 4 * 6 which is 24 evaluates to 24
|_____________________________________________________

And then we were supposed to return the result of that multiplication:

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| this method call is supposed 24
| to return 4 * fac(3) which ------ <---- so now this value
| is 4 * 6 which is 24 is returned
|_____________________________________________________

When the first fac call returns 24, then the program returns back to main(), and now that the
fac(4) call is over, it is replaced by its return value, which is 24, which then gets written into the
variable x by the assignment statement.

________________________________________________________
| main
| int x;
| x = fac(4);
| ---------
|________ /|\ __________________________________________
|
|
this expression is replaced by 24, since the
method call fac(4) has returned 24
255

That is how recursion works on the actual machine – as with any other method call, when you
make a recursive method call, your calculation cannot proceed until that method call is finished.
This is why the first fac call (the one where n==4) sits paused for a long time – until the fac(3)
call it makes, finally returns.
256

Lecture 22 : Subproblems
To write a recursive function f(x), you need to express f(x) in terms of the solution to one or
more subproblems – i.e. in terms of the solution to one or more similar, but smaller problems. For
example:

• If your parameters are integers, you might obtain a subproblem by reducing one or more of
the integers, and running the same code with those values for the parameters. For example,
in the factorial method from the last lecture, we took our one integer parameter and reduced
it by 1, and then calculated the factorial of that value. The original problem was to calculate
n!, and our subproblem was to calculate (n − 1)! – that is, our subproblem was to run the
exact same algorithm, just on a smaller value of n.
Perhaps for some other problem with an integer parameter, it would be more useful to reduce
the integer parameter by subtracting 2 from it, or dividing it by 3. Or if we have multiple
integer parameters, maybe only some of them get reduced in some way, or maybe all of them
get reduced in some way. In any case, we want to then run the same algorithm, but on that
set of “reduced” data, rather than the original data. That is our subproblem.
We will see a few additional examples with integer parameters, later in this lecture.

• If we are trying to count the number of items in some pile of items, we might break that pile
into a number of smaller piles, and make a recursive call to count the number of items in each
of those smaller piles. Then, once we have the number of items in each of the smaller piles, we
could add those values together to get the number of items in the original large pile. In this
case, since our problem is to count the number of items in the overall pile, the subproblem is
to count the number of items in one of the smaller piles – the same algorithm, just run on a
smaller collection of data. We will examine this idea further in lecture 23.

• For dealing with graphics or pictures, a subproblem could be just drawing a smaller piece of
the picture, or a smaller version of the picture. We will examine this idea further in lecture
24.

• If you have an array, a subproblem is the same algorithm run on a subarray. The idea of a
subarray is that you take the original array, but only look at part of it. So if the original
array has indices 0 through 9, perhaps the recursive call deals only with the cells from 1
through 9, or the cells from 0 through 8. Or perhaps the subarray is only half the size of the
original array; in such a case, our subarray might consist of the cells 0 through 4, or the cells
5 through 9. If we have a multidimensional array, then the subarray could contain all the
cells but one, or it could perhaps instead contain all the rows but one, or all the columns but
one.
The idea is that we need to define the low and high indices we are concerned with for each
dimension of the array. Our algorithm will then be designed to run only on the section of the
array that is defined by those indices. By increasing the low index for a particular dimension,
or decreasing the high index for that dimension, we can reduce the number of indices we are
working with for that particular dimension – meaning we have reduced the amount of data
we are dealing with, and our subproblem can be to run the same algorithm, on that smaller
amount of data. We will examine this idea further in lectures 25-27.
257

In any cases, coming up with the correct subproblem is one of the keys to designing a recursive
algorithm. The subproblem is the part that you can “assume is solved already”. For example,
when we wrote the factorial algorithm in the last lecture:

|
| n * (n-1)!, if n > 0
n! = |
| 1, if n == 0
(n>=0) |

we didn’t then write separate a separate algorithm to evaluate (n-1)!. We simply used (n-1)!
in our algorithm above, and assumed that when the value of (n-1)! was needed, its value would
“automatically” exist and could be substituted right into the expression n * (n-1)! above, without
there being a need for us to have to write any additional algorithms or add anything else to the
above algorithm. That is, when we wrote the above algorithm, we just assumed that the calculation
(n-1)! was already done and we could freely use the result of that calculation.
It works the same way for any recursive algorithm. The procedure for writing a recursive
algorithm is to:

1. Choose a subproblem.

2. Assume that your chosen subproblem could be solved just by reapplying the same recursive
algorithm you are writing for the original, larger problem. That is, assume you can just snap
your fingers at any time, and the work represented by the subproblem will have “magically”
been completed! Once you finish writing your algorithm, then in a sense, that’s what happens
– you make a recursive call to run the subproblem, and then wait; when that recursive call
returns, the subproblem has been completed while you were just sitting there waiting.

3. Figure out how having that subproblem solved, would help you solve the original problem.

4. Figure out what the base case should be, so that you can eventually stop breaking your
problem into a smaller subproblem.

The steps don’t have to go in that exact order – you might be able to figure out the base case
before you’ve got the subproblem figured out – but that’s the basic idea.
Part of why this process is difficult is that there are generally many possibilities to choose
from when selecting your subproblem, so you need to figure out which of those subproblems is
a useful one. There are many subproblems available for you to choose, for which having that
subproblem solved will be no help whatsoever in solving the larger, original problem. You want
to find a subproblem for which having it solved does make the original problem easier to solve. If
you’ve found a way to make use of a particular subproblem solution in order to solve the original
problem...then you’ve got the key information needed to write your recursive algorithm. However,
if you can’t come up with any way to make use of the solution to a particular subproblem, you
might have the wrong subproblem, and should consider a different one instead.
258

The first example we will look at in depth is exponentiation. That is, we want to calculate the
values of expressions such as 25 or 137 or 821 . More generally, we are trying to calculate:

baseexp

We’ll assume for this problem that base and exp are integers. Further more, we’ll assume that
exp ≥ 0, so that we don’t have to deal with fractional results, and we’ll assume that base ≥ 1 so
that we avoid having to deal with 00 in our algorithm.
For what values of x ≤ base and i ≤ exp can xi be used to calculate baseexp ? It sometimes
helps to start with a concrete example. We might consider, say, 35 , and try and figure out what
subproblem solution might help us calculate 35 . Some possible subproblems are:

• 25 , i.e. reduce base by one

• 34 , i.e. reduce exponent by one

• 24 , i.e. reduce base and exponent by one

• 23 , i.e. reduce base by one and reduce exponent by two

There are other possibilities as well, of course, but let us at least consider the ones above for a
moment. Of those subproblems above, the one that could help the most would be to decrease the
exponent but not the base. If we have 34 , we only need to multiply it by the base (3) again to get
35 . Trying to calculate 35 if given the value of 25 , 24 , or 23 wouldn’t be anywhere near as simple.
If you were to take the value 24 , for example, and try and use that value to help calculate 35 , you
would likely conclude pretty quickly that knowing 24 didn’t seem to be much help in calculating
35 , and therefore that the “lower both the base and the exponent by one” subproblem wasn’t much
help. But on the other hand, if you were to take the value 34 , then you’d probably see right away
how easy it was to calculate 35 if you already had the value of 34 – and thus you’d conclude that
the “lower the exponent by one” subproblem was indeed a useful one, since you’d already found a
way to use the result of that subproblem!
So, if we generalize our result above, we can calculate baseexp with the formula base ∗ baseexp−1 .
(You might even recognize that as a special case of the baseexp = basey ∗ baseexp−y exponent rule
you likely learned in whichever early math class first taught you about exponents.) However, even
though that formula works in most cases, it’s not all we need; for recursion, we need a base case
as well. The recursive case tells us how to solve any general problem in terms of a subproblem,
and the base case tells us when to stop breaking the problem into yet another subproblem. In the
case of exponentation, we said we’d allow our exponent to get as low as zero (anything less and our
results are no longer integers) and any positive number raised to the zero power is 1, so our base
case could be that when the exponent is 0, we return 1.
That gives us the following mathematical formula:

____________ 1, if exp == 0
exp |
base = |
| exp-1
(base >= 1, |___________ base * (base ), if exp > 0
exp >= 0)
259

The above is our algorithm. We could also have written the above out in some form of pseudocode,
or in English, but since it’s a mathematical computation, the above mathematical form is a nice
way to express the algorithm. The point, however, is that the above tells us precisely how to
calculate baseexp for any legal values of base and exp.

Implementing the exponentiation algorithm

Since we will need the values of base and exp in order to calculate baseexp , it would follow that
those two values should be arguments that we send to an exponentiation method, to be stored in
parameters. That would give us the following first line for our method (the method signature, plus
the public static tagged on to the beginning of the method):

public static int pow(int base, int exp)

We’ll name the method pow since that is short for “power” and we are calculating “base to the exp
power”.
Given the mathematical description of exponentiation that we had earlier:

____________ 1, if exp == 0
exp |
base = |
| exp-1
(base >= 1, |___________ base * (base ), if exp > 0
exp >= 0)

we can just convert this straight into a recursive method. Since we have two cases above, one where
exp == 0 and one where exp > 0, we will need a conditional to decide between those two cases:

public static int pow(int base, int exp)


{
if (exp == 0)
// [...we need to add some code here]
else // exp > 0
// [...we need to add some code here]
}

and then, in the exp == 0 case in our mathematical description, we say the answer is 1, so let us
return 1 from our method in that case:

public static int pow(int base, int exp)


{
if (exp == 0)
return 1;
else // exp > 0
// [...we need to add some code here]
}

and likewise, the exp > 0 case in the mathematical description above gives us the following as our
answer:
260

exp-1
base * (base )

Since baseexp−1 will just be converted to pow(base, exp-1), the recursive calculation in our math-
ematical description, translates to

base * pow(base, exp - 1)

and thus we have the following recursive method, which is now complete:

// implementation of our exponentiation algorithm


public static int pow(int base, int exp) // base >= 1; exp >= 0
{
if (exp == 0)
return 1;
else // exp > 0
return base * pow(base, exp - 1);
}

Note the comment at the end of the method signature – it’s a reminder of what values are legal
arguments for this method and what values are not. We might also have provided a more complete
method header comment for this method, that would have told us the same information:

// pow
// - parameters : base - the base of our exponentiation; we
// assume this value is >= 1
// : exp - the exponent to which we want to raise
// our base value; we assume this value is >= 0
// - return value : an integer holding the value of base raised
// to the exp power
// - calculates "base to the exp power" and returns that value
public static int pow(int base, int exp)
{
if (exp == 0)
return 1;
else // exp > 0
return base * pow(base, exp - 1);
}

If you make assumptions about the parameter values in your code, it’s generally a nice idea
to add a comment somewhere about that, so that other people using your code understand what
values are and aren’t legal to send as arguments to your code.
261

A run of the recursive method with base being 3 and exponent being 4 could start like this:

_____________________________________________________________________
| main
| int x;
| x = pow(3, 4); // we call the pow method, and in doing so,
| // set up the "pow" notecard below:
|____________________________________________________________________
| pow (base: 3) // this is the notecard for the first call to
| (#1) (exp: 4) // pow; 3 and 4 were passed to this method as
| // arguments, and are stored in the parameters
| // "base" and "exp", respectively
|_____________________________________________________________________

When we run the pow code on the data in the pow notecard, since exp is not zero, we will take
the recursive case, which requires we calculate baseexp−1 , i.e. 33 .

__________________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_________________________________________________________________
| pow (base: 3)
| (#1) (exp: 4)
|
| need to calculate: pow(3, 3), so we can use its value to
| complete the multiplication in the
| expression
|
| base * pow(base, exp - 1)
| i.e.
| 3 * pow(3, 3)
|
| hence the call to pow and the
| new notecard below this one, which
| stores the parameters for that call
|__________________________________________________________________

So, we make another call to our pow method. That is, from the call pow(3, 4), we will make
the call pow(3, 3), and therefore we will start a new notecard where the base and exp arguments
are both 3.
262

__________________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_________________________________________________________________
| pow (base: 3)
| (#1) (exp: 4)
|
| need to calculate: pow(3, 3), in order to calculate 3 * pow(3, 3)
|
|__________________________________________________________________
| pow (base: 3)
| (#2) (exp: 3)
|
| need to calculate: pow(3, 2), so we can use its value to
| complete the multiplication in the
| expression
|
| base * pow(base, exp - 1)
| i.e.
| 3 * pow(3, 2)
|
| hence the call to pow and the
| new notecard below this one, which
| stores the parameters for that call
|__________________________________________________________________

As you can see above, from within the pow(3, 3) call, we will decide that we need a
pow(3, 2) call, and so we will make that call:
263

__________________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_________________________________________________________________
| pow (base: 3)
| (#1) (exp: 4)
|
| need to calculate: pow(3, 3), in order to calculate 3 * pow(3, 3)
|
|_________________________________________________________________
| pow (base: 3)
| (#2) (exp: 3)
|
| need to calculate: pow(3, 2), in order to calculate 3 * pow(3, 2)
|
|__________________________________________________________________
| pow (base: 3)
| (#3) (exp: 2)
|
| need to calculate: pow(3, 1), so we can use its value to
| complete the multiplication in the
| expression
|
| base * pow(base, exp - 1)
| i.e.
| 3 * pow(3, 1)
|
| hence the call to pow and the
| new notecard below this one, which
| stores the parameters for that call
|__________________________________________________________________

From within the pow(3, 2) call, we will need a pow(3, 1) call, so we make that call:
264

__________________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_________________________________________________________________
| pow (base: 3)
| (#1) (exp: 4)
|
| need to calculate: pow(3, 3), in order to calculate 3 * pow(3, 3)
|
|_________________________________________________________________
| pow (base: 3)
| (#2) (exp: 3)
|
| need to calculate: pow(3, 2), in order to calculate 3 * pow(3, 2)
|
|__________________________________________________________________
| pow (base: 3)
| (#3) (exp: 2)
|
| need to calculate: pow(3, 1), in order to calculate 3 * pow(3, 1)
|
|__________________________________________________________________
| pow (base: 3)
| (#4) (exp: 1)
|
| need to calculate: pow(3, 0), so we can use its value to
| complete the multiplication in the
| expression
|
| base * pow(base, exp - 1)
| i.e.
| 3 * pow(3, 0)
|
| hence the call to pow and the
| new notecard below this one, which
| stores the parameters for that call
|__________________________________________________________________

From within the pow(3, 1) call, we will need a pow(3, 0) call, so we make that call:
265

________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_______________________________________________________
| pow (base: 3)
| (#1) (exp: 4)
|
| need: pow(3, 3), in order to calculate 3 * pow(3, 3)
|_______________________________________________________
| pow (base: 3)
| (#2) (exp: 3)
|
| need: pow(3, 2), in order to calculate 3 * pow(3, 2)
|_______________________________________________________
| pow (base: 3)
| (#3) (exp: 2)
|
| need: pow(3, 1), in order to calculate 3 * pow(3, 1)
|_______________________________________________________
| pow (base: 3)
| (#4) (exp: 1)
|
| need: pow(3, 0), in order to calculate 3 * pow(3, 0)
|______________________________________________________
| pow (base: 3)
| (#5) (exp: 0)
|
| base case! (we have not returned yet)
|____________

We make and pause four separate calls to pow, finally making our fifth call which is our base case.
266

Then, since the base case doesn’t need to make any further recursive calls, we can start returning
from there. The base case itself returns 1:

________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_______________________________________________________
| pow (base: 3)
| (#1) (exp: 4)
|
| need: pow(3, 3), in order to calculate 3 * pow(3, 3)
|_______________________________________________________
| pow (base: 3)
| (#2) (exp: 3)
|
| need: pow(3, 2), in order to calculate 3 * pow(3, 2)
|_______________________________________________________
| pow (base: 3)
| (#3) (exp: 2)
|
| need: pow(3, 1), in order to calculate 3 * pow(3, 1)
|_______________________________________________________
| pow (base: 3)
| (#4) (exp: 1)
|
| need: pow(3, 0), in order to calculate 3 * pow(3, 0)
|______________________________________________________
| pow (base: 3)
| (#5) (exp: 0)
| -----> returns 1
| base case!
|_______________________________________________________
267

Therefore, the value 1 is returned back to the previous call and is substituted for “pow(3, 0)”,
the method call expression that triggered the base case to be started:

________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_______________________________________________________
| pow (base: 3)
| (#1) (exp: 4)
|
| need: pow(3, 3), in order to calculate 3 * pow(3, 3)
|_______________________________________________________
| pow (base: 3)
| (#2) (exp: 3)
|
| need: pow(3, 2), in order to calculate 3 * pow(3, 2)
|_______________________________________________________
| pow (base: 3)
| (#3) (exp: 2)
|
| need: pow(3, 1), in order to calculate 3 * pow(3, 1)
|_______________________________________________________
| pow (base: 3)
| (#4) (exp: 1)
|
| need: pow(3, 0), in order to calculate 3 * pow(3, 0)
| ---------
|_______________________________________________ /|\ __
|
base case returned 1; that is, |
the method call pow(3, 0) returned 1...so now this expression
is replaced by the value 1
268

Now that we have a value for pow(3, 0), we can finally complete the multiplication, since now
we have both operands:

________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_______________________________________________________
| pow (base: 3)
| (#1) (exp: 4)
|
| need: pow(3, 3), in order to calculate 3 * pow(3, 3)
|_______________________________________________________
| pow (base: 3)
| (#2) (exp: 3)
|
| need: pow(3, 2), in order to calculate 3 * pow(3, 2)
|_______________________________________________________
| pow (base: 3)
| (#3) (exp: 2)
|
| need: pow(3, 1), in order to calculate 3 * pow(3, 1)
|_______________________________________________________
| pow (base: 3)
| (#4) (exp: 1)
|
| this method call is supposed 3 * 1
| to return 3 * pow(3, 0) which ------ <---- so now this expression
| is 3 * 1 which is 3 evaluates to 3
|_____________________________________________________
269

Finally, we were supposed to return the result of that multiplication:

________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_______________________________________________________
| pow (base: 3)
| (#1) (exp: 4)
|
| need: pow(3, 3), in order to calculate 3 * pow(3, 3)
|_______________________________________________________
| pow (base: 3)
| (#2) (exp: 3)
|
| need: pow(3, 2), in order to calculate 3 * pow(3, 2)
|_______________________________________________________
| pow (base: 3)
| (#3) (exp: 2)
|
| need: pow(3, 1), in order to calculate 3 * pow(3, 1)
|_______________________________________________________
| pow (base: 3)
| (#4) (exp: 1)
|
| this method call is supposed 3
| to return 3 * pow(3, 0) which ------ <--- so now this expression
| is 3 * 1 which is 3 is returned
|_____________________________________________________
270

When the fourth pow call returns 3, then the program returns back to the third pow call, and
now that the pow(3, 1) call is over, it is replaced by its return value, which is 3:

________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_______________________________________________________
| pow (base: 3)
| (#1) (exp: 4)
|
| need: pow(3, 3), in order to calculate 3 * pow(3, 3)
|_______________________________________________________
| pow (base: 3)
| (#2) (exp: 3)
|
| need: pow(3, 2), in order to calculate 3 * pow(3, 2)
|_______________________________________________________
| pow (base: 3)
| (#3) (exp: 2)
|
| need: pow(3, 1), in order to calculate 3 * pow(3, 1)
| ---------
|_______________________________________________ /|\ __
|
|
the method call pow(3, 1) returned 3...so now this expression
is replaced by the value 3
271

And then this call works as the previous one did. First, the multiplication is completed:

________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_______________________________________________________
| pow (base: 3)
| (#1) (exp: 4)
|
| need: pow(3, 3), in order to calculate 3 * pow(3, 3)
|_______________________________________________________
| pow (base: 3)
| (#2) (exp: 3)
|
| need: pow(3, 2), in order to calculate 3 * pow(3, 2)
|_______________________________________________________
|
| pow (base: 3)
| (#3) (exp: 2)
|
| this method call is supposed 3 * 3
| to return 3 * pow(3, 1) which ------ <--- so now this expression
| is 3 * 3 which is 9 evaluates to 9
|_____________________________________________________
272

And then we were supposed to return the result of that multiplication:

________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_______________________________________________________
| pow (base: 3)
| (#1) (exp: 4)
|
| need: pow(3, 3), in order to calculate 3 * pow(3, 3)
|_______________________________________________________
| pow (base: 3)
| (#2) (exp: 3)
|
| need: pow(3, 2), in order to calculate 3 * pow(3, 2)
|_______________________________________________________
|| pow (base: 3)
| (#3) (exp: 2)
|
| this method call is supposed 9
| to return 3 * pow(3, 1) which ------ <--- so now this value
| is 3 * 3 which is 9 is returned
|_____________________________________________________
273

When the third pow call returns 9, then the program returns back to the second pow call, and
now that the pow(3, 2) call is over, it is replaced by its return value, which is 9:

________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_______________________________________________________
| pow (base: 3)
| (#1) (exp: 4)
|
| need: pow(3, 3), in order to calculate 3 * pow(3, 3)
|_______________________________________________________
| pow (base: 3)
| (#2) (exp: 3)
|
| need: pow(3, 2), in order to calculate 3 * pow(3, 2)
| ---------
|_______________________________________________ /|\ __
|
|
the method call pow(3, 2) returned 9...so now this expression
is replaced by the value 9

And then this call works as the previous two did. First, the multiplication is completed:

________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_______________________________________________________
| pow (base: 3)
| (#1) (exp: 4)
|
| need: pow(3, 3), in order to calculate 3 * pow(3, 3)
|_______________________________________________________
|
| pow (base: 3)
| (#2) (exp: 3)
|
| this method call is supposed 3 * 9
| to return 3 * pow(3, 2) which ------ <--- so now this expression
| is 3 * 9 which is 27 evaluates to 27
|_____________________________________________________
274

And then we were supposed to return the result of that multiplication:

________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_______________________________________________________
| pow (base: 3)
| (#1) (exp: 4)
|
| need: pow(3, 3), in order to calculate 3 * pow(3, 3)
|_______________________________________________________
|| pow (base: 3)
| (#2) (exp: 3)
|
| this method call is supposed 27
| to return 3 * pow(3, 2) which ------ <--- so now this value
| is 3 * 9 which is 27 is returned
|_____________________________________________________

When the second pow call returns 27, then the program returns back to the first pow call, and
now that the pow(3, 3) call is over, it is replaced by its return value, which is 27:

________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_______________________________________________________
| pow (base: 3)
| (#1) (exp: 4)
|
| need: pow(3, 3), in order to calculate 3 * pow(3, 3)
| ---------
|_______________________________________________ /|\ __
|
|
the method call pow(3, 3) returned 27...so now this expression
is replaced by the value 27
275

And then this call works as the previous three did. First, the multiplication is completed:

________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_______________________________________________________
|
| pow (base: 3)
| (#1) (exp: 4)
|
| this method call is supposed 3 * 27
| to return 3 * pow(3, 3) which ------ <--- so now this expression
| is 3 * 27 which is 81 evaluates to 81
|_____________________________________________________

And then we were supposed to return the result of that multiplication:

________________________________________________________
| main
| int x;
| x = pow(3, 4);
|_______________________________________________________
|
| pow (base: 3)
| (#1) (exp: 4)
|
| this method call is supposed 81
| to return 3 * pow(3, 3) which ------ <--- so now this value
| is 3 * 27 which is 81 is returned
|_____________________________________________________

When the first pow call returns 81, then the program returns back to main(), and now that the
pow(3, 4) call is over, it is replaced by its return value, which is 81, which then gets written into
the variable x by the assignment statement.

________________________________________________________
| main
| int x;
| x = pow(3, 4);
| ---------
|________ /|\ __________________________________________
|
|
this expression is replaced by 81, since the
method call pow(3, 4) has returned 81
276

Another example we can consider, is adding together the digits of a non-negative integer. For
example:

Number Sum of digits


---------- -------------
0 0
4 4
92 11 (9 + 2)
305 8 (3 + 0 + 5)
45924 22 (4 + 5 + 9 + 4)
59480 26 (5 + 9 + 4 + 8 + 0)

What subproblem is helpful in this case? Well, once again, let’s try a concrete example. Imagine
we are trying to find the sum of the digits in the integer 51460 (which is 16, but we’ll pretend we
don’t know that yet). If our number is 51460, what smaller subproblem helps us out?

• 51459, i.e. (number − 1) 24

• 51457, i.e. (number − 3) 22

• 25730, i.e. (number/2) 17

Those are three possibilities, and none of them seem particularly promising – all of them have
a greater sum of all digits than the original sum.
What we really want to be doing is counting “fewer digits” for our recursive call:

sumOf(51460) == sumOf(5146) + 0

sumOf(5146) == sumOf(514) + 6

sumOf(514) = sumOf(51) + 4

sumOf(51) = sumOf(5) + 1

sumOf(5) == 5

Note that once our number drops below ten, we only have one digit, so the sum-of-digits of that
number is just itself.
The above looks nice, but how can be extract individual digits from a number? Well, we can
use the modulus and division operators:

num % 10 == the one’s place of the number (ex: 45926 % 10 == 6)


num / 10 == all *but* the one’s place of the number (ex: 45926 / 10 == 4592)

That gives us the following code:


277

public static int addDigits(int n) // assume n >= 0


{
if (n <= 9)
return n;
else
{
int onesPlace = n % 10;
int sumOfRest = addDigits(n/10);
return sumOfRest + onesPlace;
}
}
278

Lecture 23 : Picture Recursion


Today, we will attempt to draw some pictures using recursion. We will use recursion for these
particular pictures because they will have a “recursive nature” to them – that is, a similar-but-
smaller version of the picture, will be part of the larger picture, and so we could (potentially) treat
the drawing of the similar-but-smaller version of the picture, as a subproblem, and handle it with
a recursive call.
To start with, assume we have a method to print out the same character repeatedly, with no
newline at the end:

// assume that num is non-negative


public static void printRepeatedChar(char item, int num)
{
if (num > 0)
{
System.out.print(item);
printRepeatedChar(item, num - 1);
}
}

Certainly, you could write that using a loop as well, but as is the case for many of our examples,
our goal is to demonstrate recursion, so we’ll stick with the recursive version above.
So, for example, the call printRepeatedChar(’X’, 7) would print the following:
XXXXXXX
/|\
|
next character to print, goes right here
Seven of the ’X’ character are printed. That is how this method works.
Now, suppose we would like to draw a triangle of a given height, that borders the left side of
the monitor:
X
XX
XXX
XXXX
XXXXX
If we want to write a method to do this, that method would need to know the height of the triangle:
public static void drawTriangle(int height)
Now, having a negative height makes no sense at all, so we will at least assume the argument sent
to that height parameter is non-negative. We can allow a height of 0 if we want – in that case,
the method would do nothing (since there’s nothing to draw) and then return. On the other hand,
we could instead require a positive argument, and thus demand that if the client wants to draw a
triangle, they should send in a height that would enable something to be drawn. Either way is okay,
as long as we clearly state the method’s expectations for anyone who wants to use the method.
Our choice in these examples will be to only allow a positive height, i.e. we will NOT consider 0 a
legal argument to the method.
Finally, note that a triangle of height 5 contains a triangle of height 4 within it:
279

X
XX
XXX
XXXX

-------------------- above: triangle of height 4


below: the base we add to get a triangle of height 5

XXXXX

This suggests that one way to draw this picture, is to first draw a triangle of one smaller height,
and then to add the base to make it a triangle of the desired height. Above, to draw a triangle of
height 5, we first draw a triangle of height 4, and then add the base to make it a triangle of height
5.
That gives us the following code:

// rough draft #1
public static void drawTriangle(int height) // height >= 1
{
if (height > 1)
{
drawTriangle(height - 1); // draw smaller triangle
printRepeatedChar(’X’, height); // draw base
}
else // height == 1
{
printRepeatedChar(’X’, height); // prints one ’X’
}
}

Note that we passed height in as the argument to printRepeatedChar(...), since in our triangle
pictures, the width of the base was always equal to the height of the triangle.
We actually have an error in the code! If we pass in 5 to the height right now, we would end
up with the following picture:

XXXXXXXXXXXXXXX
^ cursor would be right here, after last ’X’

Since printRepeatedChar(...) doesn’t end a line, we need to end the lines ourselves. Once we
fix that, we get the following code:
280

// rough draft #2
public static void drawTriangle(int height) // height >= 1
{
if (height > 1)
{
drawTriangle(height - 1); // draw smaller triangle
printRepeatedChar(’X’, height); // draw base
System.out.println(); // start new line of triangle
}
else // height == 1
{
printRepeatedChar(’X’, height); // prints one ’X’
System.out.println(); // start new line of triangle
}
}
Finally, note that the two lines in the else case, also appear in the if-case (only the comment is
different); rather than duplicate that code in both cases, we could just pull it out of the conditional
altogether:
// rough draft #3
public static void drawTriangle(int height) // height >= 1
{
if (height > 1)
{
drawTriangle(height - 1); // draw smaller triangle
}
else // height == 1
{
;
}
printRepeatedChar(’X’, height); // draw base
System.out.println(); // start new line of triangle
}
and since the else case only has an empty statement now, it can be completely eliminated. Also,
we don’t need the curly braces for the if case, since there’s only one statement between them:
// final version
public static void drawTriangle(int height) // height >= 1
{
if (height > 1)
drawTriangle(height - 1); // draw smaller triangle

printRepeatedChar(’X’, height); // draw base


System.out.println(); // start new line of triangle
}
Next, suppose we would like to draw a pyramid instead of a triangle. For example, here is a picture
of a pyramid of height 5:
281

X
XXX
XXXXX
XXXXXXX
XXXXXXXXX

Note that the picture is basically just two triangles facing in opposite directions, pushed together
so that their vertical edges overlap:

X X X
XX XX XXX
XXX XXX --------> XXXXX
XXXX XXXX XXXXXXX
XXXXX XXXXX XXXXXXXXX
that means that the base of the pyramid, is equal to twice the base of a triangle of the same height,
minus one. We subtract one because the vertical edges overlap, and so we should only count that
once, not twice. Since the base of our triangle picture is equal to the height of our triangle picture,
the pyramid’s base – which is twice the triangle base, minus one – is equal to 2 * height - 1.
Note also that the top four lines of that picture, are also a pyramid – the shape is the same;
the picture is merely smaller.

X
XXX
XXXXX
XXXXXXX

And similarly, for that pyramid of height 4, its top three lines form a pyramid of height 3:

X
XXX
XXXXX

In general, we would like to describe a pyramid of height n, as being a pyramid of height n-1,
with a one-line base, just as we did for the triangle. For example, the pyramid of height 5 was just
a pyramid of height 4:
X
XXX
XXXXX
XXXXXXX
followed by drawing the bottom line, the base of the pyramid of height 5.
XXXXXXXXX
So it would seem like we can do the following:

DrawPyramid(int n)
1) Draw a Pyramid of height n-1
2) Draw the base of the pyramid of height n
282

As with the triangle, we will choose n==1 as our base case, and assume no one will send in an
argument that would result in nothing being printed. (That is, we’ll assume no one will pass in an
argument less than or equal to 0. Again, be aware that this is not the only way it could be done;
you could choose n==0 as the base case, too, if you wanted, and have the method simply print
nothing at all in that case.) This gives us the following code:

// rough draft #1
public static void DrawPyramid(int height) // height >= 1
{
if (height > 1)
{
DrawPyramid(height - 1);
printRepeatedChar(’X’, 2 * height - 1);
System.out.println();
}
else // height == 1
{
printRepeatedChar(’X’, 2 * height - 1); // could also hardcode 1
// for second argument
System.out.println();
}
}

However, there is an error with that code! If you run that with an initial argument of 5 you’ll get
this:

X
XXX
XXXXX
XXXXXXX
XXXXXXXXX

What went wrong? Well, since we have not discussed indentation at all, we are still assuming that
the pyramid that we print will border the left-hand side of the monitor. And thus, we have made
a mistake when choosing our subproblem. This is the pyramid of height 5:

X
XXX
XXXXX
XXXXXXX
XXXXXXXXX

and this is the pyramid of height 4, that we would expect if we passed 4 as the initial argument to
the method:

X
XXX
XXXXX
XXXXXXX
283

and that being the case, the pyramid of height 5 does NOT contain the pyramid of height 4. Rather,
the pyramid of height 5 contains this picture:

X
XXX
XXXXX
XXXXXXX

If you look carefully, you see that that triangle, is indented one space over from the triangle of
height 4 that we drew above it. That is the picture we want to draw recursively; we need our
recursive call to NOT assume the pyramid should be bordering the left-hand side of our monitor.
So the lesson here is to be careful – sometimes, the subproblem we think solves our problem isn’t
really the one that solves our problem. We were close here, but not exact – we assumed the picture
would magically be indented the right amount, when in reality, the computer will automatically
print adjacent to the left-hand side of the monitor unless we state otherwise.
The only way to send information to a method is via the parameters, so we will need to add an
extra parameter in our examples – a parameter that will store the amount of indentation we want.
Our problem is therefore converted from “print a pyramid of a given height” to “print a pyramid of
a given height and a given indentation”. Here is our code with the indentation modification made:

// rough draft #2
public static void DrawPyramid(int height, int indent) // height >= 1,
{ // indent >= 0
if (height > 1)
{
DrawPyramid(height - 1, indent + 1);
printRepeatedChar(’ ’, indent); // indent the requested amount
printRepeatedChar(’X’, 2 * height - 1);
System.out.println();
}
else // height == 1
{
printRepeatedChar(’ ’, indent); // indent the requested amount
printRepeatedChar(’X’, 2 * height - 1); // could also hardcode 1
// for second argument
System.out.println();
}
}

and as with the printing of the triangle, we can note that the two cases are the same except that
the if case has a recursive call before everything else. Therefore, we can just put that one line in
the conditional, and have everything else out of the conditional entirely:
284

// final version
public static void DrawPyramid(int height, int indent) // height >= 1,
{ // indent >= 0
if (height > 1)
DrawPyramid(height - 1, indent + 1); // draw smaller pyramid

printRepeatedChar(’ ’, indent); // indent the requested amount


printRepeatedChar(’X’, 2 * height - 1);
System.out.println();
}
285

If we want to draw an ’X’, we have a similar issue. Actually, first, we need to decide on the actual
problem:

* * * *
* * * *
* * * *
* * * *
* **
* * * *
* * * *
* * * *
* * * *

If we have an ’X’ of height nine, which of the above do we get? The example on the left has
the two lines of the ’X’ cross in the middle, but the example on the right does not. Futhermore,
do we allow an ’X’ of height ten, or no? If we do, we’d need to match it to our choice above, so we
get one of these two:

* * * *
* * * *
* * * *
* * * *
* **
* **
* * * *
* * * *
* * * *
* * * *

Let’s simplify it for now, and stick with only odd heights and odd widths, meaning we will implement
our ’X’ in the first upper left example above.
In that case, our goal is accomplished by printing the first line of the ’X’, then recursively
printing a smaller ’X’, and then printing the last line of the ’X’:

* * <----- first this line


* * -------|
* * |
* * |
* |--- this part is handled by a recursive call
* * |
* * |
* * ------|
* * <----- finally this line

But we will need to indent the recursive call’s output, so that the smaller ’X’ starts one space over
to the right. This gives us the following code:
286

public static void PrintX(int height, int indent)


{
if (height == 1)
{
printRepeatedChar(’ ’, indent);
System.out.println(’*’);
}
else
{
printRepeatedChar(’ ’, indent);
System.out.print(’*’);
printRepeatedChar(’ ’, height - 2);
System.out.println(’*’);

PrintX(height - 2, indent + 1);

printRepeatedChar(’ ’, indent);
System.out.print(’*’);
printRepeatedChar(’ ’, height - 2);
System.out.println(’*’);
}
}
As a final example, let us consider the drawing of a fan blade:
*****
****
***
**
*
**
***
****
*****
That is a fan blade whose blade widths are each 5...but within it, there is a fan blade whose
blade widths are each 4.
*****
**** ------------|
*** |
** |
* |---- here is a smaller fan blade
** |
*** |
**** -----------|
*****
So we can do the same sort of thing as in the previous two examples – we make a recursive call to
print the smaller picture, but we’ll need to be able to indent the smaller picture when we print it.
That gives us the following code:
287

public static void PrintFanBlade(int width, int indent)


{
if (width == 1)
{
printRepeatedChar(’ ’, indent);
System.out.println(’*’);
}
else // width > 1
{
printRepeatedChar(’ ’, indent);
printRepeatedChar(’*’, width);
System.out.println();
PrintFanBlade(width - 1, indent + 1);
printRepeatedChar(’ ’, indent + width - 1);
printRepeatedChar(’*’, width);
System.out.println();
}
}
288

Lecture 24 : Recursive Counting


Let’s consider another problem: grid traversal. Say you have a 2-D array grid
(0, 0) (0, 3)
_________________________
| | | |
| | | |
|______|________|________|
| | | |
| | | |
|______|________|________|
(2, 0) (2, 3)
Travelling only right or down (i.e. increasing numbers only), how many ways are there to get to
(2, 3)?
More generally...

(curR, curC)
_______
|
| ...

|
___|
(maxR, maxC)
How many ways are there to get from (curR, curC) to (maxR, maxC) travelling only right or
downward?
From every point, you have two choices.

(curR, curC)
-----------> (curR, curC + 1)
|
|
|
\|/

(curR + 1, curC)

Once you take one of those two choices, then you have a number of options from that point. For
example, if you move right, then there are a number of paths to take from (curR, curC + 1).
Likewise, if you move down, there are a number of paths to take from (curR + 1, curC). The
important point, however, is that those are the only two moves you can make. You either move
right and then explore from (curR, curC + 1), or move down and then explore from (curR + 1,
curC).
So, the total number of paths from (curR, curC) should be equal to the number of paths that
begin by travelling to the right, plus the number of paths that begin by travelling down. There are
289

no other paths to count because those two possible first moves cover everything you can do as your
first move, and thus cover all possible options.
That gives us the following so far – it’s still incomplete, but it’s a beginning:

public static int CountPaths(int curR, int curC, int maxR, int maxC)
{
return CountPaths(curR + 1, curC, maxR, maxC) +
CountPaths(curR, curC + 1, maxR, maxC);
}

Now, if we’ve passed up the maximum row, or maximum column, we can’t go back upward or to
the left to reach it again. So any point where curR > maxR or curC > maxC automatically has zero
paths to the solution. We’ve gone too far down, or too far to the right, at that point and so the
destination is now unreachable. That gives us the following modification to our code so far:

public static int CountPaths(int curR, int curC, int maxR, int maxC)
{
if ((curR > maxR) || (curC > maxC))
return 0;
else
return CountPaths(curR + 1, curC, maxR, maxC) +
CountPaths(curR, curC + 1, maxR, maxC);
}

So now we have a base case. The problem is, that since the base case always returns zero, the
recursive calls will produce, at best, zero, and so the function will always return 0. When do we
actually have a correct path that we can count?
And the answer there is, when we’ve reached the destination, we will count that as a correct
path. That is, if we always have to move to the right or downward, we can never stop, so we’ll
allow ourselves a third operation as well – if we are at the destination, we can stop and count that
as a path. That gives us the correct code:

public static int CountPaths(int curR, int curC, int maxR, int maxC)
{
if ((curR > maxR) || (curC > maxC))
return 0;
else if ((curR == maxR) && (curC == maxC))
return 1;
else
return CountPaths(curR + 1, curC, maxR, maxC) +
CountPaths(curR, curC + 1, maxR, maxC);
}
290

Another example is making change. You have various coin amounts. What is the maximum
numbers of ways you can have a given quantity of money?
Consider: Say you had 62 dollars, and could use at most, 10 dollar bills, and as small as pennies.
You have the following possibilities for money:

10 dollar bills
5 dollar bills
2 dollar bills // yes, such things exist!
1 dollar bills
50 cent pieces
dimes
nickels
pennies

Now, if we want to know how to make 62 dollars out of the above money, we could say, well,
first, let’s ask how many 10 dollar bills we have? It’s possible we have none. It’s possible we have
1. It’s also possible we have 2, or 3, or 4, or 5, or 6. But those are the only possibilities. We can’t
have fewer than 0, and if we have more than 6, we already have 70 dollars and that is too much.
So, if we add together all combinations that total 62 dollars, where we have no 10 dollar
bills, plus all combinations that total 62 dollars, where we have exactly 1 ten dollar bill, plus all
combinations that total 62 dollars where we have exactly 2 ten dollar bills, and so on, up to all
combinations of 62 dollars where we have exactly 6 ten dollar bills, then that total will be the total
number of possible ways to have 62 dollars using the above money types – every combination has
been represented exactly once in our total since every combination has either exactly 0, or exactly
1, or exactly 2, or exactly 3, or exactly 4, or exactly 5, or exactly 6 ten dollar bills.
The only problem is, okay, let’s imagine we want to count all combinations that have exactly 1
ten dollar bill. How do we do that? Well, if we have one ten dollar bill, that means the other 52
dollars needs to come from money that is NOT ten dollar bills! Otherwise, we’d secretly be adding
additional ten dollar bills! That is, the rest of what’s left over needs to come from the rest of the
list of money:

5 dollar bills
2 dollar bills // yes, such things exist!
1 dollar bills
50 cent pieces
dimes
nickels
pennies

That gives us the following:

0 tens, plus 62 dollars without using 10 dollar bills


1 ten, plus 52 dollars without using 10 dollar bills
2 tens, plus 42 dollars without using 10 dollar bills
3 tens, plus 32 dollars without using 10 dollar bills
4 tens, plus 22 dollars without using 10 dollar bills
5 tens, plus 12 dollars without using 10 dollar bills
6 tens, plus 2 dollars without using 10 dollar bills
291

That should cover every possibility. We decide how many 10 dollar bills we want, and then
force ourselves to make the rest of the change from the lower dollar amounts, to guarantee we don’t
accidentally add any additional 10 dollar bills. Add up the total size of each category above and
you have your answer.
In other words, we can say that the original problem was to “make 62 dollars using at most bills
worth 10 dollars”. In that case, the subproblems whose solutions we add together for our answer,
are:

0 tens, plus 62 dollars using at most bills worth 5 dollars


1 ten, plus 52 dollars using at most bills worth 5 dollars
2 tens, plus 42 dollars using at most bills worth 5 dollars
3 tens, plus 32 dollars using at most bills worth 5 dollars
4 tens, plus 22 using at most bills worth 5 dollars
5 tens, plus 12 dollars using at most bills worth 5 dollars
6 tens, plus 2 dollars using at most bills worth 5 dollars

Once we are down to our lowest money type – pennies in this case – we just return 1, since
if I say “make X dollars using only pennies”, then there is exactly one way to do that – a pile of
pennies large enough to total X dollars.
In both this problem and the paths problem, the key is to divide your collection of items you
are counting, into piles, such that:

• every item is in at least one pile

• no item is in more than one pile

That is to say, divide your collection into piles such that every item in the collection is in exactly one
of the new piles. Then, you recursively count those piles, and add those counting results together
to get your total answer.
292

Lecture 25 : Subarrays – Recursion on Data Collections


When dealing with recursion on arrays, the subsequent recursive call usually needs to run on a
portion of the original array. For example, if our goal is to print the entire array, the goal of the
recursive call would be to print part of the array. If our goal were to find the minimum value in the
array, the goal of the recursive call would be to find the minimum value of part of the array. As
with our earlier recursion examples, the recursive call in these examples is solving a subproblem –
performing the same work, on a smaller amount of data. In the case of an array, this often means
that we perform the same work on a piece of the original array, rather than the entire original
array.
How can we obtain this ”piece of the original array”? One way would be to copy parts of the
original array to a new array. For example, if we had an array indexed from 0 through 9, and the
recursive call is designed to deal with everything but the first cell, we could copy cells 1 through
9 of the original array to a new array. Since we are copying nine cells there, the new array must
be of size 9 – meaning it will be indexed from 0 through 8. So we are copying cells 1 through 9
of the original array, to cells 0 through 8 of the new array. And then in the next step, we could
copy cells 1 through 8 of that new array, to cells 0 through 7 of an even newer array. And then, in
the next step, we could copy cells 1 through 7 of that array into cells 0 through 6 of a still-newer
array. And so on.
If we do this, each step has one fewer cell than the step before it. However, this results in a lot
of time spent copying and a lot of memory needed for the new array we create at each step. So,
rather than recopy all the relevant values to a new array, we can simply use a pair of indices lo
and hi to indicate what portion of the original array we are dealing with, and by changing lo and
hi, we can change the portion of the original array that a particular method call cares about.
Going back to the previous example, if our original array were indexed from 0 through 9, then
we could have lo hold the value 0, and hi hold the value 9. Our code could then be designed to
deal with the cells with indices lo through hi, inclusive. Since lo holds the value 0 and hi holds
the value 9, then code would then be dealing with the cells indexed from 0 through 9, inclusive,
exactly the way we want. When it comes time to deal with a smaller part of that array, we could
increase lo by one. Now, lo holds the value 1 and hi still holds the value 9, and thus our code
deals with the cells with indices 1 through 9 inclusive. Next, we could increase lo again, to 2, and
now our code will deal with the cells with indices 2 through 9, inclusive. If we increase lo again,
our code will be deal with the cells with indices 3 through 9 inclusive. Each step deals with one
fewer cell than the step before, just as in our earlier example. But this time, to go from one step
to the next, we only need to increase lo by 1; we don’t need to create a new array or copy values
from one array to the next.
Portions of the original array – especially when lo and hi are used to indicate the low and high
indices of that portion of the original array – are known as subarrays, and by changing the values
of lo and hi we can change what subarray we are dealing with.
293

In order to be able to change lo and hi for each call, we will need to have them as parameters
to which we can pass arguments:

ex: void DoSomething(int[] arr, int lo, int hi)


{
if (recursive case condition is true)
{
// ...other code here, and then
// somewhere in here, we make a recursive call
DoSomething(arr, lo + 3, hi / 2);
}
}

If the original call to the pseudocode above had as arguments the integers 0 and 9, then the next
call has arguments 3 (lo + 3 is 3 if lo is 0) and 4 (hi divided by 2 is 4 if hi is 9). That next
call then operates on the part of the array from cell 3 through cell 4; it is quicker than copying the
array and easier to deal with as well.
As an example, let us consider printing an array. If we want to print the array from cell lo
through hi, some of the possible subproblems are:

• printing the array from lo+1 through hi; (then you only need to print arr[lo] yourself; the
rest is handled by the recursive call)

• printing the array from lo through hi-1; (then you only need to print arr[hi] yourself; the
rest is handled by the recursive call)

• printing the array from lo through the middle of the subarray, i.e. from lo through
(lo + hi)/2; (then you need to find some way to print the cells with indices
((lo + hi)/2) + 1 through hi yourself, since the recursive call didn’t print any of those
cells)

Let’s build an algorithm out around the first subproblem, printing everything from lo+1 through
hi. (Technically, each of the three subproblems above could serve as the basis for an algorithm; the
one we get from this subproblem will be the most straightforward, though.)
If we want to print the cells in order by index, starting with the lowest-indexed cell, then we need
to print arr[lo] on our own, before recursively printing everything with indices lo + 1 through
hi. Thus our algorithm (minus the base case, which we’ll add in a minute), looks like this:

To print the array cells indexed lo through hi of an array "arr":


1) Print out arr[lo]
2) Recursively, print the array cells indexed lo + 1 through hi of "arr"

If initially lo was 0 and hi was 9, then the first call is trying to print the values at indices 0
through 9, and does so by printing out the value at arr[0], and then makes a recursive call to
print out the values at indices 1 through 9. And that call works by printing out arr[1] (the new
value of lo in this call is 1) and then making a recursive call to print out the values at indices 2
through 9. And that call works by printing out arr[2] (the new value of lo in this call is 2) and
then making a recursive call to print out the values at indices 3 through 9. And so on.
294

Once lo is greater than hi, there are no cells in that range (i.e. an empty subarray) and nothing
left to print. So that can be our base case:

To print the array cells indexed lo through hi of an array "arr":


if (lo <= hi) // we have at least one cell in our subarray
{
1) Print out arr[lo]
2) Recursively, print the array cells indexed lo + 1 through hi of "arr"
}
else // we have no cells in our subarray
; // there is nothing to do

and that gives us the following code:

// prints all values of arr whose indices are in range lo...hi inclusive,
// in order from lowest to highest index
public static void print(int[] arr, int lo, int hi)
{
if (lo <= hi)
{
System.out.println(arr[lo]);
print(arr, lo+1, hi);
}
// else you do nothing
}

That’s all!
If you had chosen to use lo...hi - 1 as the subarray instead, then you need to make the
recursive call first (thus printing all values with indices lo through hi - 1, inclusive) and then
print the value at index hi after the recursive call has completed:

// prints all values of arr whose indices are in range lo...hi inclusive,
// in order from lowest to highest index
public static void print(int[] arr, int lo, int hi)
{
if (lo <= hi)
{
print(arr, lo, hi - 1);
System.out.println(arr[hi]);
}
}

But either one of those versions of the code will print the values with indices lo through hi
inclusive, from the lowest-indexed value to the highest-indexed value.
295

Now, perhaps the clients of this method don’t want to bother having to pass in indices as
arguments. Perhaps a client would simply like to say, “print the array” and have it be assumed
that the request means, “print the entire array, from index 0 through the highest index of the
array”. That is, perhaps the client would like to make the following method call:

print(arr);

rather than the method call:

print(arr, 0, arr.length - 1);

Unfortunately, right now, the second method call would be required. Since our method needs the
lo and hi parameters in order for the recursion to work, it requires that the client send in some
initial values as arguments to those parameters. The flexibility this allows, is nice – it means the
client could also do this if they wanted to only print part of the array:

// one example of an alternate method call that could be made


print(arr, 3, arr.length - 5);

but even though the client could choose to pass in any pair of bounding indices as the second and
third arguments, 0 and arr.length - 1 are likely to be the most common arguments. That is,
the most common situation is that the client wants to print the entire array; situations where the
client only wants to print part of the array, would probably be less common.
So, one possibility is to write another method:

public static void print(int[] arr)


{
// some code goes here
}

to which the client could simply send the array reference as an argument, and not the indices as
well. The “glue” between this method and the recursive one we wrote earlier, is that the above
method can call the recursive one, with the most common arguments!

public static void print(int[] arr)


{
print(arr, 0, arr.length - 1);
}

Such a method is known as a wrapper method. A wrapper method does exactly as the name implies
– it “wraps” around an already existing method that does something similar – by making a call
to that already existing method – and thus provides a different interface to that method. In the
case of our print methods, clients can either call the recursive print(...) directly, with three
arguments, or they can call non-recursive print(...) with one argument, and that method can
call the recursive print(...) with three argumebts. Clients have a choice of how they want to
activate the printing of their array, but in both cases, it’s the three-parameter print(...) that
does most of the work.
Wrapper methods are usually relatively simple; the call to the longer, more interesting method
is generally the most important statement of the wrapper method, and only a little bit of work gets
done before or after that statement. In the wrapper method above, the work that gets done is to
296

select initial arguments to the recursive method’s lo and hi parameters, and to calculate what the
second of those two index arguments should be (i.e. to calculate arr.length - 1). That “set-up
work” – the choosing of the initial values of lo and hi – only needs to happen once, so it should
not be in the call to the recursive method itself. Instead, we do that work in a wrapper method,
before the call to the recursive method, and then once the call to the recursive method is made and
returned from, this wrapper method has nothing else to do.
In other cases, the wrapper method might do a little bit more work before the call to the longer,
more interesting method, and it might even do some work after that method call returns as well.
But generally, there is only a little bit of work, at most, to do before or after that method call.
Wrapper methods don’t tend to be very complex; they basically exist to help set up a situation
where the more interesting method can get called.
(Note that naming both methods the same – such as print in our examples above – is fine. This
is an example of method overloading, which we discussed back when we introduced constructors.
As long as the methods have different parameter lists, they are distinguishable by the compiler
even if they have the same name.)
297

As another example, let’s consider the detection of palindromes. A palindrome is a word or


number that reads the same forward and backwards, such as RACECAR or 18481. We can consider
a sequence to potentially be a palindrome as well. For example, these two sequences:
18, 49, 21, 49, 18

18, 49, 21, 21, 49, 18


are both sequences that read the same forward and backward, and thus could be considered palin-
dromes. More generally, if p is some palindrome sequence, then x, p, x, where x is some single
value, will be a palindrome sequence as well. We see that in the first sequence above, for example
– 49, 21, 49 is a palindrome, and if we put 18 at the beginning and end of that sequence, we
still have a palindrome. This suggests that if you are given a sequence such as 18, 49, 21, 49,
18, one useful question might be to ask if the same value is listed first and last. If the first and
last values are the same, then perhaps a good second question to ask is, if everything but the first
and last values constitutes a palindrome – in this case, if 49, 21, 49 constitutes a palindrome. If
the first and last values are the same, and if everything but the first and last values constitutes a
palindrome, then our sequence matches the x, p, x pattern and would itself be a palindrome.
However, the following three sequences are NOT palindromes:
18, 49, 21, 49, 19

18, 49, 21, 22, 49, 18

18, 49, 21, 22, 49, 19


In the first sequence, the first and last values are not the same, so you have a different first
value when you read the sequence left-to-right versus right-to-left. Therefore the sequence is not
a palindrome. In the second sequence, the first and last values are the same, and in fact, the
second and second-to-last values are the same as well, but the middle sequence, 21, 22, is not a
palindrome. In the third sequence, we have both problems – the first and last values are not the
same, and the sequence between them (49, 21, 22, 49) is not a palindrome either. So, just as
we were discussing above, it would seem helpful to ask if the first and last values of a sequence are
the same, and to ask if the sequence between them is a palindrome. If the answer to either of those
questions is “no”, then we do not have a palindrome.
Now, since our overall intention is to ask if the entire *sequence* is a palindrome, and as part
of that task, we want to ask if some subsequence is a palindrome, well, there’s our recursion! So
far, we have this:

// we aren’t quite done yet...


public static boolean isPalindrome(int[] arr, int lo, int hi)
{
if (arr[lo] != arr[hi])
return false; // if outer values are not same, not palindrome
else
return isPalindrome(arr, lo+1, hi-1); // check inner section
}

If the first and last values are not equal, we know we do not have a palindrome. Otherwise, our
answer relies on whether the middle section is a palindrome or not.
298

Of course, we eventually reach the point where the middle section is so small that it must be
a palindrome and we don’t really need to do any processing to figure that out. For example, if
the sequence is of size 0 or 1, then of course the sequence must be a palindrome If you have just
one value in a sequence, then it’s the same sequence forwards and backwards. And, if you have an
empty sequence, then it is empty whether you read it forwards or backwards. This gives us the
following code:

public static boolean isPalindrome(int[] arr, int lo, int hi)


{
if (lo >= hi)
return true; // size 0 and 1 collections read the same both directions
else if (arr[lo] != arr[hi])
return false; // if outer values are not same, not palindrome
else
return isPalindrome(arr, lo+1, hi-1); // check inner section
}

We could then write a wrapper method for this as follows:

public static boolean isPalindrome(int[] arr)


{
return isPalindrome(arr, 0, arr.length - 1);
}

In this case, even if the client wants to call the wrapper method, they still want to have a true
or false returned to them, so the wrapper method needs to return the value that the call to the
recursive method will generate. If instead, the client expected the wrapper method to print a
message, then the wrapper method could have been written like this instead:

public static boolean isPalindrome(int[] arr)


{
boolean result = isPalindrome(arr, 0, arr.length - 1);
if (result == true) // could also have just said "if (result)"
System.out.println("array holds a palindrome sequence");
else
System.out.println("array does not hold a palindrome sequence");
}

So, a wrapper method doesn’t even need to do exactly the same sort of thing as the method it
calls. In our print(...) example, both the wrapper method, and the method it called, printed
out values that were stored in the array. In the above case, however, the recursive method returns
a value based on whether the subarray is a palindrome, and the wrapper method instead prints a
message based on whether the subarray is a palindrome.
299

As a final example, let’s consider adding the diagonal elements of a two-dimensional array that
has the same number of rows and columns. In this case, we want four indices – two to indicate the
low and high row indices, and two to indicate the low and high column indices.

public static int addDiagonal(int[][] arr, int loR, int hiR, int loC, int hiC)
{
if ((loR > hiR) && (loC > hiC))
return 0;
else
return arr[loR][loC] + addDiagonal(arr, loR+1, hiR, loC+1, hiC);
}

You can get away with fewer parameters, if you like. Consider that an exercise for the reader.
In the above case, we might use a wrapper method to see if the matrix we are trying to add
the diagonal of is actually a square matrix. That error checking would not need to be done with
each recursive call – it would only need to be done once, before the recursion began. So, since it
is just some set-up work, we can put it in a wrapper method and then call the recursive method
from there.
For example, if we knew our matrix would contain only positive integers – and therefore we knew
that the sum could never be -1 along the diagonal – we might write a method like the following:

public static int addDiagonal(int[] arr)


{
if (arr.length != arr[0].length)
return -1;
else
return addDiagonal(arr, 0, arr.length-1, 0, arr[0].length-1);
}

Or in other words, ”if this is not a square matrix, return -1; otherwise return the actual sum of
the diagonal”. If we return -1, since we know that cannot be a legal sum, we know it must be an
error signal.
If the array could hold any integers, that is a bad approach, since if we get -1 back as a return
value, we’d have no way of knowing whether it was meant to signify an error, or if instead -1 was
the actual sum of a diagonal on a square array. So in that case, we might write a class as follows:

public class Result {


public boolean isSquare;
public int sumIfExists; // shouldn’t read this if isSquare == false
}

and then our wrapper method could do this:


300

public static Result addDiagonal(int[] arr)


{
Result pair = new Result();
if (arr.length != arr[0].length) {
pair.isSquare = false;
pair.sumIfExists = -1; // who cares? Client shouldn’t read this anyway
}
else
{
pair.isSquare = true;
pair.sumIfExists = addDiagonal(arr, 0, arr.length-1, 0, arr[0].length-1);
}
return pair;
}

In the error case, we would return a reference to a Result object where the isSquare variable is
false. The client, upon reading that false value in that variable, would know it was a signal that
the sum doesn’t really exist, and would not bother reading the sum variable. However, if there was
a true in the isSquare variable, then the client would know that it was indeed a square matrix,
and would read the sum variable to get the diagonal sum.
The point is that the setup code (the error check) and the cleanup code (taking the return value
of the recursive call – if we did make the recursive call – and writing it into the Result object)
each only need to be done once, so we will put them in a wrapper method, not the actual recursive
code where those lines would be repeated in every call.
301

Lecture 26 : Searching
Today, we will start by thinking back to lecture 1, where we were discussing searching for a penny
under a box, or searching a list of names for a particular name. We had said that, if all we knew
was that we had some collection of values to inspect, and there was no information we could take
advantage of to rule out any of the values, then we would need to inspect each and every one
of them. This is the problem we are going to discuss now. Specifically, since the only way we
know how to store a collection of information is in an array, we are going to search an array for a
particular value. We’ll start with an array of integers – and thus we are searching the array for a
particular integer – but later on we’ll talk about what we might have to worry about if the type
were different.
So, let’s imagine we are searching an array for the value 39. One way to choose the subproblem
is to have our recursive call simply be to search everything but the first cell for the value 39. That
is, we can check the first cell, and if it is 39, we are done, and if it is not 39, then we’ll recursively
search the rest of the array. For example:

---------------------------------------
A[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9
lo == 0, hi == 9

In the example above, we could check A[lo] (which is A[0] here). Since A[lo] is not 39, we
have not found our value yet. So, we could recursively search the rest of the array. That is, given
the index range lo...hi we did not find our value in A[lo], so we will recursively check the index
range lo+1...hi. That is, next we check the index range 1...9 (we’ll indicate this in the next
diagram with the shorter horizontal line above our array values).

-----------------------------------
A[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9
lo == 1, hi == 9

So now again we check A[lo], but this time since lo is 1, we are checking A[1]. And A[1] is
not our value 39, so we again recursively check the index range lo+1...hi. So our next recursive
step is over the index range 2...9.

-------------------------------
A[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9
lo == 2, hi == 9

We check A[lo] (which this time is A[2]) and that is not equal to our value 39, so we again
recursively check the index range lo+1...hi. So our next recursive step is over the range 3...9.
302

---------------------------
A[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9
lo == 3, hi == 9

We check A[lo] (which this time is A[3]) and that is not equal to our value 39, so we again
recursively check the index range lo+1...hi. So our next recursive step is over the range 4...9.

-----------------------
A[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9
lo == 4, hi == 9

And now, when we check A[lo], it is equal to our value 39. We have found a cell containing
39. There is no need for further work.

That is the basic idea behind our algorithm. We’ll check the first cell of our range, and if that
cell’s value is not equal to 39, then we’ll recursively check the rest of the range (everything in the
range except the first cell).
This raises a number of questions, and the answers to these questions will enable us to take our
rough sketch of what we want to do, and produce a complete algorithm from it, by filling in the
rest of the details.

• We have outlined how the recursive case should work, but what is the base case?

• What should be returned by our algorithm?

• How can we generalize our algorithm to search for integers other than 39? More interestingly,
what if we are not searching for integers at all? What if our array contains a different type,
and we are searching for a value of that type?

We will consider these questions in order.


First, when dealing with the base case, we have to ask ourselves, for what size subarray do we
know for sure whether the value exists or not? Well, if the subarray were of size 1, we would still
need to check that cell, but if the subarray were of size 0, then we are certain our value is not there,
because nothing is there. So, we could have lo > hi be the condition that would trigger the base
case – the condition that indicates our subarray defined by lo and hi is of size 0.
Next, we need to consider what to return. We are asking if 39 is in the array or not, and so
the answer is either “yes” or “no”. When you are limited to such answers, a boolean works very
nicely – you return true if the value is in the array and false if it is not in the array.
However, there might be a better choice. If all we care about is knowing whether the value is in
the array or not, a boolean is fine, but we might care about retrieving the value as well, or more
specifically, retrieving the cell it is in. For example, perhaps we want to know if 39 is in the array,
and, if it is, we want to later replace it with 40. In that case, we want to know not just that 39 is
in the array, but also, at what index it is located.
So instead of returning true or false, what we could do is return a pair of values: a boolean
and an integer. This sort of return type is often useful, because there are often situations where we
303

run a method on a collection of data but we only have a meaningful return value in certain cases
– such as our searching example above, where we can only return “the index where our value is
located” if the value actually exists in the array. In those cases, if we have no meaningful data to
send back, we have to send back some meaningless data of some kind (because something needs
to be returned), so returning the boolean along with the data means you can use the boolean to
check if you should bother to read your data or not.
Since we can only return one value, any attempt to return a “pair” must make use of an object
of some kind. For our search method, the following could be the necessary class:
304

public class ReturnPair


{
public boolean statusFlag; // true if found, false if not
public static int index; // meaningful only if the boolean is true
}

// returned result is therefore one of the following:


// (true, index_where_value_is_located)
// (false, meaningless_integer_that_we_will_not_bother_reading)

and then our search method would have a return type of ReturnPair and the method would have
to allocate a new ReturnPair() from inside the method, so that there was some object to return.
This is a way to get around the only-one-return-value restriction and, in doing so, return a collection
of information from the method – just make your “one value” that you returned, an address of a
dynamically-allocated object, rather than a value of primitive type.
However, though conceptually, returning this pair of information is what we want to do, we
actually can achieve this with only one integer and nothing else. We can take advantage of the fact
that arrays have a limited index range – namely, 0 through length-1, where length is whatever
the length of the array is. So, if you were to return a negative number, or some number greater
than or equal to the length of the array, then that return value could be checked by the client of
your algorithm. If the returned value is within the index range of the array, it would interpreted
as a “true” – i.e. the value is in the array – and the returned integer is then assumed to be the
index of the cell containing 39. On the other hand, if the returned value is NOT in the index range
of the array, then that is interpreted as a “false” – i.e. the value 39 is not in the array – and the
client knows not to use the returned index to access the array. We get the benefits of returning a
boolean (knowing whether the value is there or not) along with the benefits of knowing where the
value is, if it is there. And we can do this while still returning just one integer, rather than having
to create a ReturnPair object and return that. Since -1 is always outside the index range of any
array, it’s a nice return value to use for this purpose, and that’s what we’ll use.
Finally, we are not likely to always need to search for 39. More likely, the client will have some
particular value in mind to search for. And if it’s something the client specifies, then probably it
should be a parameter. We often refer to such a value – i.e. the value to be searched for – as a key
or search key. We could then compare the value in an array cell, to the parameter, rather than to
39 specifically.
Putting all of that together, leads to the algorithm expressed with the following Java code:
305

// LinearSearch
// - parameters : arr - the array to search
// : key - the value to search for
// : lo - the low index of this subarray
// : hi - the high index of this subarray
// - return value : array index or -1
// - if key is in A, returns index where it
// is located. Otherwise, returns -1.
public static int LinearSearch(int[] arr, int key, int lo, int hi)
{
if (lo > hi)
return -1;
else if (arr[lo] == key)
return lo;
else
return LinearSearch(arr, key, lo+1, hi);
}

Note that we could also write a wrapper method to call the above method, a wrapper method
that would only need from the client the things the client actually cared about – namely, the array
to be searched and the key to be searched for:

// LinearSearch
// - parameters : arr - the array to search
// : key - the value to search for
// - return value : array index or -1
// - if key is in A, returns index where it
// is located. Otherwise, returns -1.
public static int LinearSearch(int[] arr, int key)
{
return LinearSearch(arr, key, 0, arr.length-1);
}

In the recursive method, we have two base cases. If we actually find our value, we return a real
index, and if we reduce the subarray to size 0, we return a -1 to serve as a “not found” indicator.
-1 is not a real index, so if our search algorithm returns it, we know it is because the key was not
found in the array. But if we have not found our key and yet there is more of an array to the right
of our current low cell, then we should search that array – hence the point of the recursive call.
As algorithms get more complex – though admittedly, this algorithm is not terribly complex
– we sometimes like to have only one return statement in our algorithm, so that there are not
multiple exit points to keep track of. If we wanted to make such a modification above, it’s a simple
matter to just store the returned value in a variable and return it at the end:
306

public static int LinearSearch(int[] arr, int key, int lo, int hi)
{
int returnVal;
if (lo > hi)
returnVal = -1;
else if (arr[lo] == key)
returnVal = lo;
else
returnVal = LinearSearch(arr, key, lo+1, hi);

return returnVal;
}

The code would be similar, but not identical, if we were dealing with different types. For
example, if we were searching for a String, we can’t use == to compare String objects for equality,
but must instead use the equals instance method:

public static int LinearSearch(String[] arr, String key, int lo, int hi)
{
if (lo > hi)
return -1;
else if (arr[lo].equals(key))
return lo;
else
return LinearSearch(arr, key, lo+1, hi);
}

We might also search on part of an object. For example, what if our Clock class from the object-
oriented-programming lectures, had an instance method public int getHour() that returned the
hour? In that case, perhaps we then wish to search a collection of Clock objects for one that is set
to a particular hour. In that case, the key would be of type int, even though the array itself was
of type Clock instead of int:

public static int LinearSearch(Clock[] arr, int key, int lo, int hi)
{
if (lo > hi)
return -1;
else if (arr[lo].getHour() == key)
return lo;
else
return LinearSearch(arr, key, lo+1, hi);
}

And if we were searching for a double, well, recall that computer arithmetic can introduce rounding
errors. So, if we tried something like this:
307

public static int LinearSearch(double[] arr, double key, int lo, int hi)
{
if (lo > hi)
return -1;
else if (arr[lo] == key)
return lo;
else
return LinearSearch(arr, key, lo+1, hi);
}

we might not get correct results because we might have done the computations to fill the array
one way, and gotten a value such as 24.839523176, and yet, when we computed our key before
passing it into this method, we might have tried doing the same computation, but put the steps in
a different order, and thus had a different sort of rounding error and ended up with 24.839523172
instead. On MP1, we said it was okay to have those sort of differences in the final decimal place like
that. But if we tried doing an exact comparison, as with the code above, well, those two numbers
are, technically, different, and thus our comparison-for-equality (==) will evaluate to false. So, if
we want to compare floating-point values for equality, we generally want to see if they are equal to
within some acceptable error range, rather than seeing if they are precisely equal:

public static int LinearSearch(double[] arr, double key, int lo, int hi)
{
if (lo > hi)
return -1;
else if ((arr[lo] > key - 0.000000005) && (arr[lo] < key + 0.000000005))
return lo;
else
return LinearSearch(arr, key, lo+1, hi);
}

Now, we will claim we have a match, as long as A[lo] is within 0.000000005 of key.
All these examples follow the same basic algorithm – we just have different code to implement
the “equality” comparison, depending on exactly what sort of comparison we are trying to do.
308

Now, this algorithm – however we implement it – doesn’t take into account the possibility that
the search key occurs multiple times in the array. If there were duplicate instances of the search
key – for example, if we are searching for 39 and it occurs five times in the array – what can we
do about that? Well, in many cases, handling duplicates requires just a small modification of your
non-duplicate algorithm. However, there are also often a few ways that duplicates can be handled,
and so before we can modify the non-duplicate algorithm to deal with duplicate-handling, we need
to decide exactly how we want to handle duplicates in the first place. A few of the possibilities are
listed below:

• return the index of the first occurence (i.e. occurence with lowest index) of the key in the
array (return -1 if it never appears)

• return the index of the last occurence (i.e. occurence with highest index) of the key in the
array (return -1 if it never appears)

• return the number of times the key appears in the array

• return a new array holding as its values all indices where the key appears in the original array

The first possibility is what our first LinearSearch algorithm does already – that algorithm
was designed to start returning an index as soon as it found our search key, so if there were many
other instances of the search key in the array, we would never see them since we wouldn’t even
inspect those cells. Likewise, the second possibility could be implemented simply by having our
current LinearSearch algorithm search from hi to lo rather than from lo to hi – if we are trying
to find the last (i.e. rightmost) instance of our search key, well, that’s just the first one we will see
if we start our search from the right instead.
The third possibility, though – counting the number of occurences – will require that we always
look over every cell of the array. If there were some cell we did not inspect, well, our total might
be wrong, depending on what our assumption was. If we assumed the value in that cell was indeed
our search key, and it wasn’t, our total would be too big by 1. On the other hand, if we assumed
it wasn’t equal to our search key, and it was, then our total would be too small by 1. So we need
to look at every cell.
So instead of just inspecting the cell, as in the regular LinearSearch, we will want to inspect
the cell, and also add one to a total if the value in the cell is equal to our search key. The base
case is then in charge of initializing the total, since in the base case we have no instances of the
key in this subarray – it follows from that, that the base case should return 0 (the total number of
occurences of our key in the subarray of size 0).

public static int CountingLinearSearch(int[] arr, int key, int lo, int hi)
{
if (lo > hi)
return 0;
else if (arr[lo] == key)
return 1 + CountingLinearSearch(arr, key, lo + 1, hi);
else
return CountingLinearSearch(arr, key, lo+1, hi);
}

Note that in both the case where arr[lo] is equal to our key, and the case where it isn’t, we still
need to make our recursive call. This is because we need to count how many times the key occurs
309

in the other cells, regardless of whether the key is in arr[lo] or not. So, we could rearrange that
code a bit so at least the same method call doesn’t appear twice in the code:

public static int CountingLinearSearch(int[] arr, int key, int lo, int hi)
{
if (lo > hi)
return 0;
else
{
int total = CountingLinearSearch(arr, key, lo + 1, hi);
if (arr[lo] == key)
return 1 + total;
else
return total;
}
}

Either form is acceptable. And, as with the earlier, non-duplicate version, we could also have
arranged this so that we have only one return statement, if we wanted:

public static int CountingLinearSearch(int[] arr, int key, int lo, int hi)
{
int returnValue = 0;
if (lo <= hi)
{
returnValue = CountingLinearSearch(arr, key, lo + 1, hi);
if (arr[lo] == key)
returnValue = returnValue + 1;
}
return returnValue;
}

which looks much less like the original LinearSearch but is still correct.
If the way we want to handle duplicates is to return an array containing the indices where the
duplicates are located, then we’ve got a few issues to worry about there as well. The big conceptual
issue is what to return if we have an unsuccessful search, and here we have two options. Our return
type must be “integer array reference” (i.e. int[]), if we are returning an array of integers – all
the array indices will be integers, so if we want to return an array of indices, we are returning an
array of integers. And if our return type is an integer array reference, we can always return null if
there is no array to return. The upside to that is, we don’t need to allocate an array object if there
is an unsuccessful search. The downside is, we have no object returned so we need a conditional to
treat the unsuccessful search as a special case:
310

// Example client code calling a hypothetical version of linear


// search that returns an array of all indices where search key
// is located. Our code below would be calling some wrappper
// method around the recursive version, not the recursive version itself.
int[] result = LinearSearchForDuplicates(arr, key);
if (result == null)
System.out.println("There were 0 occurences of the key.);
else
System.out.println("There were " + result.length + " occurences of the key.");

Sometimes, having to deal with the lack of an array object as a separate case can become trouble-
some. So, we also have the option of returning an array of size 0, instead of returning null:

// in our hypothetical LinearSearchForDuplicates method, replace


// return null;
// with
// return new int[0];

Now, the client code doesn’t need to make a special check to see if the returned reference is null
– we know whatever is returned, will always be a reference to an array, even in the case of an
unsuccessful search. And hence, we can do this:

// Example client code calling a hypothetical version of linear


// search that returns an array of all indices where search key
// is located. Our code below would be calling some wrappper
// method around the recursive version, not the recursive version itself.
int[] result = LinearSearchForDuplicates(arr, key);
System.out.println("There were " + result.length + " occurences of the key.");

and not bother to continually check to see if our returned array even exists.
The other issue that would need dealing with would be the increasing of the size of the array
to be returned, as we found more and more occurences of our search key. You could either put a
loop inside your recursive algorithm, to copy values to a new array, or else you could write a helper
method (whether recursive or not) to accomplish the same task, and then call that method from
your algorithm. Either way, if we intend to return an array of exactly the right size, we’ll need to
be changing the array size as we find additional occurences of the search key. We will leave that as
an exercise for you to try.
311

We can also consider another search problem: finding the minimum value in an array. More
specifically, we want to return the index where the minimum value is located.
In this case, it is difficult to even define the problem if the subarray we are searching is of size
0. You could search an empty collection (you would not find your value), and you could print an
empty collection (you would print nothing), but how can you “return the minimum value of an
empty collection”? There’s nothing to return!
So, you have two options:

• You could simply say that finding the minimum of a collection only makes sense if you
actually have values in the collection – and as a result, design your method to assume that
the parameter subarray has size at least 1. Subarrays of size 0 won’t be allowed.

• Alternatively, since we are returning the index of the minimum, rather than the minimum
itself, we could have a special case that returns -1 in cases where the subarray is of size 0.

We will do things the first way – we’ll simply define “find the minimum” to be an operation that
makes no sense on subarrays of size zero.
Now, finding the minimum value in an array can be solved recursively. If the array is of size 1,
then certainly that one cell is the minimum, so return its index. Otherwise, find the minimum of
the rest of the cells (i.e. all the cells but the first one) recursively. Then, compare that result to
the first cell’s value. If the first cell’s value is lower than the minimum of the remaining cells, then
by definition it is lower than all the remaining cells, and thus is the overal minimum. On the other
hand, if the minimum of the remaining cells is also less than the first cell, then the minimum of
the remaining cells is the minimum of all the cells.

public static int findMinimum(int[] arr, int lo, int hi)


{
if (lo == hi)
return lo;
else
{
int locOfMinOfRest = findMinimum(arr, lo + 1, hi);
if (arr[lo] <= arr[locOfMinOfRest])
return lo;
else
return locOfMinOfRest;
}
}
312

Lecture 27 : Sorting
As you might expect, sorting a collection of values means to arrange the elements in a specific order
– usually in increasing order (though not always). More generally, there could be duplicate values,
so in that case we’d say that we are (usually) arranging values non-decreasing order, i.e. if our
values are: x1 , x2 , x3 , ..., xn , then we want to arrange them in some specific order xi1 , xi2 , xi3 , ...,
xin , so that:
xi1 ≤ xi2 ≤ xi3 ≤, ..., ≤ xin

Sorting is one of the most common operations done on data, and as a result, it is a problem that
has been exhaustively studied. We will be studying a number of sorting algorithms this semester
and talking about their advantages and disadvantages. Right now, though, we are merely going to
look at a few sorting algorithms, not perform any serious analysis of them.
First, we should turn our attention to swapping; if we are trying to rearrange values inside an array
so that they are in order, then we will probably often need to exchange the position of two values
in the array, from being in the wrong order to being in the correct order.
To swap the values at two indices, from a different method, we will pass in the array itself, and
the two indices. Our swap(...) method will then read the two values at those indices and write
each of them into the other cell.

public static void swap(int[] arr, int i, int j)

It is not enough to say

public static void swap(int[] arr, int i, int j)


{
arr[i] = arr[j];
arr[j] = arr[i];
}

because in the first line, we erase the value in arr[i] by overwriting it with the value in arr[j].
So, the end result of the above code will be that the value which is stored in arr[j] to start with
gets written into arr[i] and then copied back into arr[j] as well. And we have lost the original
arr[i] for good.
So, instead, we need to set up a temporary variable to store arr[i] so that we don’t lose it.
Then, we can store arr[j] in arr[i], and finally we can write the value in the temporary variable
into arr[j]. At this point, our swap is complete.

public static void swap(int[] arr, int i, int j)


{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
313

Given this swap(...) method, and the findMinimum(...) method from the last lecture, we
can now design an algorithm known as selection sort. What does selection sort do? Well, one way
you might sort things in real life is to find the smallest value in your collection and set it aside.
Then, you are left with a pile of the remaining values – all of which come after that smallest value
that you set aside – and you would need to sort that collection. Once you sort the remaining values,
you just place the sorted collection after that smallest value which you had set aside, and you are
done.
We already know how to find the smallest value in an array – that is what our findMinimum(...)
method will do. The way we can “set the smallest value aside” once we’ve found it, is to swap it
to the beginning of the array. For example, if this was our array:

arr[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

then findMinimum(arr, 0, 9) would return the value 3, since that is where the minimum (6) is
located. Now, we can pull 6 out of the collection by moving it to the front of all the other remaining
values, and the easiest way to do that is to just swap it with 93, the value currently at the front of
the collection:

arr[i] 6 11 71 93 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

It’s not a problem to move 93 to the middle of the array, since neither 93, nor the values in the
other cells indexed 1 through 9, have been sorted yet anyway. We could also have figured out some
way to shift the values between the beginning of the array, and 6, to the right, to make room for 6:

arr[i] 6 93 11 71 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

but that can potentially take much longer (more array writes have to be done in that case), and
for no benefit – once we have moved 6 to the front of the array, we don’t care how the values in
cells indexed 1 through 9 are arranged – since we don’t know for sure they are sorted, we’re going
to sort them anyway. Which unsorted orderings they end up in along the way doesn’t make any
difference.
So, since shifting doesn’t give us any benefit in this case, and will take longer, we prefer to
swap. And that gives us the following basic outline for selection sort:
314

public static void selectionSort(int[] arr, int lo, int hi)


{
if (recursive case)
{
// (1) Find index of minimum value of subarray
int minLocation = findMinimum(arr, lo, hi);

// (2) Swap minimum value into first cell of


// subarray (sometimes lo will equal minLocation;
// in that case, this is then a useless swap, but
// also a harmless one
swap(arr, lo, minLocation);

// (3) recursively sort the remainder of the array


selectionSort(arr, lo + 1, hi);
}
else // base case
// what do we do here?
}

Now, as far as the base case goes, well, once we have only one value left, there’s nothing to do.
Any subarray of only one cell is sorted, by definition, since there’s no other values with which our
one value can be mis-ordered. And similarly, an empty subarray is also sorted, by definition. That
means the base case would simply be an empty statement, and so we don’t even need to bother
coding that. That gives us our final selection sort code:

public static void selectionSort(int[] arr, int lo, int hi)


{
if (lo < hi) // subarray is at least size 2
{
int minLocation = findMinimum(arr, lo, hi);
swap(arr, lo, minLocation);
selectionSort(arr, lo + 1, hi);
}
}

We could also write a selectionSort wrapper method that will take only the array as an argument:

public static void selectionSort(int[] arr)


{
selectionSort(arr, 0, arr.length - 1);
}
315

Here is an example run of the algorithm, on a sample array of size 10. It is assumed we have made
the call selectionSort(arr, 0, 9); that has started off the following example.

arr[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Start of selectionSort recursive call #1, where we are


sorting the subarray with indices 0 through 9.

We are attempting to sort the subarray with indices 0 through 9. The minimum value over
those indices is 6, which is located in cell 3. So, swap the value in cell 3 with the value at our low
index – namely, index 0 – so that the minimum is placed in the first cell in our subarray. Now, all
that remains is to recursively sort the values in the subarray with indices 1 through 9, and we will
be done.

arr[i] 6 11 71 93 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Start of selectionSort recursive call #2, where we are


sorting the subarray with indices 1 through 9.

We are attempting to sort the subarray with indices 1 through 9. The minimum value over
those indices is 11, which is located in cell 1. So, swap the value in cell 1 with the value at our low
index – namely, index 1 – so that the minimum is placed in the first cell in our subarray. (Note
that this doesn’t really do anything, since the minimum has stayed in the same cell. But in general,
the minimum will be somewhere other than the first cell in our subarray, so we would have that
swap in our code, and in the cases where the miniumum happens to already be in the first cell of
our subarray, the swap is useless and thus wastes a bit of time, but does no harm.) Now, all that
remains is to recursively sort the values in the subarray with indices 2 through 9, and we will be
done.

arr[i] 6 11 71 93 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Start of selectionSort recursive call #3, where we are


sorting the subarray with indices 2 through 9.

We are attempting to sort the subarray with indices 2 through 9. The minimum value over
those indices is 23, which is located in cell 8. So, swap the value in cell 8 with the value at our low
index – namely, index 2 – so that the minimum is placed in the first cell in our subarray. Now, all
that remains is to recursively sort the values in the subarray with indices 3 through 9, and we will
be done.
316

arr[i] 6 11 23 93 39 67 41 88 71 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Start of selectionSort recursive call #4, where we are


sorting the subarray with indices 3 through 9.
We are attempting to sort the subarray with indices 3 through 9. The minimum value over
those indices is 39, which is located in cell 4. So, swap the value in cell 4 with the value at our low
index – namely, index 3 – so that the minimum is placed in the first cell in our subarray. Now, all
that remains is to recursively sort the values in the subarray with indices 4 through 9, and we will
be done.
arr[i] 6 11 23 39 93 67 41 88 71 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Start of selectionSort recursive call #5, where we are


sorting the subarray with indices 4 through 9.
We are attempting to sort the subarray with indices 4 through 9. The minimum value over
those indices is 41, which is located in cell 6. So, swap the value in cell 6 with the value at our low
index – namely, index 4 – so that the minimum is placed in the first cell in our subarray. Now, all
that remains is to recursively sort the values in the subarray with indices 5 through 9, and we will
be done.
arr[i] 6 11 23 39 41 67 93 88 71 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Start of selectionSort recursive call #6, where we are


sorting the subarray with indices 5 through 9.
We are attempting to sort the subarray with indices 5 through 9. The minimum value over
those indices is 54, which is located in cell 9. So, swap the value in cell 9 with the value at our low
index – namely, index 5 – so that the minimum is placed in the first cell in our subarray. Now, all
that remains is to recursively sort the values in the subarray with indices 6 through 9, and we will
be done.
arr[i] 6 11 23 39 41 54 93 88 71 67
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Start of selectionSort recursive call #7, where we are


sorting the subarray with indices 6 through 9.
We are attempting to sort the subarray with indices 6 through 9. The minimum value over
those indices is 67, which is located in cell 9. So, swap the value in cell 9 with the value at our low
index – namely, index 6 – so that the minimum is placed in the first cell in our subarray. Now, all
that remains is to recursively sort the values in the subarray with indices 7 through 9, and we will
be done.
317

arr[i] 6 11 23 39 41 54 67 88 71 93
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Start of selectionSort recursive call #8, where we are


sorting the subarray with indices 7 through 9.

We are attempting to sort the subarray with indices 7 through 9. The minimum value over
those indices is 71, which is located in cell 8. So, swap the value in cell 8 with the value at our low
index – namely, index 7 – so that the minimum is placed in the first cell in our subarray. Now, all
that remains is to recursively sort the values in the subarray with indices 8 through 9, and we will
be done.

arr[i] 6 11 23 39 41 54 67 71 88 93
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Start of selectionSort recursive call #9, where we are


sorting the subarray with indices 8 through 9.

We are attempting to sort the subarray with indices 8 through 9. The minimum value over
those indices is 88, which is located in cell 8. So, swap the value in cell 8 with the value at our low
index – namely, index 8 – so that the minimum is placed in the first cell in our subarray. (Again,
this is a swap that happens to be useless, but harmless.) Now, all that remains is to recursively
sort the values in the subarray with indices 9 through 9, and we will be done.

arr[i] 6 11 23 39 41 54 67 71 88 93
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Start of selectionSort recursive call #10, where we are


sorting the subarray with indices 9 through 9.

We are attempting to sort the subarray with indices 9 through 9. In this case, our array is down
to only one element, since the bounding indices are identical. Therefore, there is nothing to sort –
certainly if you have only one element, it is in the “proper order” since there is nothing to be out
of order with – and so we could simply return. In our selection sort code, the lo < hi condition
would be false in this case.
Note that right before we return, we actually have 10 method calls to selectionSort active.
The method main() (or whatever) called selectionSort the first time, which called selectionSort
a second time, which called selectionSort a third time, and so on. We’ve never returned from
any of those selectionSort calls. But now that we’ve completed our base case, we will start to
return from those calls. There is never any more work to do when we return to a previous call – the
recursive call is the last work each call does – and so basically at this point call #10 just returns
to call #9, call #9 immediately returns to call #8, call #8 immediately returns to call #7, and so
on, until finally call #2 returns to call #1, and at last, call #1 returns back to main() (or returns
back to whatever called it, anyway). The array was completely sorted at the end of the base case
call (call #10); the rest was just ending the methods we had started, one by one, so that we could
return back to whatever method first called selectionSort(...) to begin with.
318

arr[i] 6 11 23 39 41 54 67 71 88 93
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Final sorted array (same as array right before


returning from selectionSort call #10)
319

Next, let us consider what is involved in inserting a new value into an already sorted array, in
sorted order. We can assume we already have k sorted values, indexed from lo through
lo + k - 1. For example, if lo were 0, and k were 12, then we have 12 sorted values indexed from
0 through 11 (0 + 12 - 1 == 11). If we want to write a new value into that array, we need to
make space for it – and thus we’d need to copy everything into an array whose length was one cell
larger than our current array:

// assume arr is a reference to our current array

// create a new array, temp, whose length is one greater


int[] temp = new int[arr.length + 1];

// copy values from arr into temp


for (int i = 0; i < arr.length; i++)
temp[i] = arr[i];

// now, the reference arr should point to our new array,


// meaning that nothing will point to the old array anymore,
// so the system collects it and eliminates it. Fortunately,
// we have copied all the values to our new array, so we don’t
// lose any of our values.
arr = temp;

That code doesn’t play a role in what we are about to discuss; we are just reminding you how you
could “add an array cell to the end of your array”. When we are done, we have k sorted values,
stored in indices 0 through k - 1 of an array of size k + 1. This leaves the last cell – the new cell,
the cell at index k – empty and available for a new value.
For example, if this had been our initial array:

arr[i] 6 11 17 23 39 41 47 54 67 71 88 93
------------------------------------------------------
i 0 1 2 3 4 5 6 7 8 9 10 11

then the above code will expand the size of the array by one (by copying the values – in their
current, sorted order – into a new, larger array):

arr[i] 6 11 17 23 39 41 47 54 67 71 88 93
-----------------------------------------------------------
i 0 1 2 3 4 5 6 7 8 9 10 11 12

Once we have everything copied to the larger array, we then have room for our new value! That’s
where the fun begins – let’s copy our new value into the last cell of the array – the extra cell we
just created. For example, if we had wanted to insert 70 into our above array of size 12, we first
increase the size 12 array to a size 13 array, and then we write 70 into that new, last cell:

arr[i] 6 11 17 23 39 41 47 54 67 71 88 93 70
-----------------------------------------------------------
i 0 1 2 3 4 5 6 7 8 9 10 11 12
320

That might not be where the new value belongs! Certainly, above, if we want the entire array to
be sorted, 70 belongs between 67 and 71, not after 93. But we will put the new value at the end
just for a moment; since right now, that is the new, empty, available cell.
And that gives us the following situation (in general, our subarray would be indexed from lo
through hi, not 0 through 12):

arr[i] |<------- this entire section is sorted ----->| newValue


-----------------------------------------------------------
i lo lo+1 lo+2 . . . . . . hi-1 hi

That is the setup we want to discuss – a sorted collection of values, with one new value at the end
that is not necessarily in the correct order. And, as we described further above, if we want to insert
a value into a sorted array, we can reach this setup point, by expanding the size of the sorted array
by one cell, and then putting the new value in that new cell at the end of the array.
So, now we have this situation from above:

arr[i] |<------- this entire section is sorted ----->| newValue


-----------------------------------------------------------
i lo lo+1 lo+2 . . . . . . hi-1 hi

and we would now like to move newValue to the correct spot in the array. In the case of our
concrete example:

arr[i] 6 11 17 23 39 41 47 54 67 71 88 93 70
-----------------------------------------------------------
i 0 1 2 3 4 5 6 7 8 9 10 11 12

we would like to move 70 to its proper place between 67 and 71. So, imagine smaller versions of
this problem – i.e. imagine the same setup, just on smaller arrays:

// for example (hi in this picture is smaller than hi in the


// above picture)
arr[i] |<- this entire section is sorted ->| newValue
-------------------------------------------------
i lo lo+1 lo+2 . . . . . . hi-1 hi

That would be the sort of problem a recursive call would solve – still inserting a new value on the
right into a sorted section on the left...only the sorted section is smaller. So which subproblem
would help? Well, what if the subarray you send to the recursive call is only one cell smaller?

// current picture:
arr[i] |<------- this entire section is sorted ----->| newValue
-----------------------------------------------------------
i lo lo+1 lo+2 . . . hi-3 hi-2 hi-1 hi

// subarray one smaller in size, that we send to recursive call:


arr[i] |<---- this entire section is sorted --->| newValue
--------------------------------------------------------
i lo lo+1 lo+2 . . . hi-3 hi-2 hi-1
321

If we had some way of converting the top picture into the bottom one, we have our basic recursive
step. If in the top picture, newValue is at cell hi, then if we can move it to cell hi-1, while still
keeping everything from lo through hi-2 sorted, then the cells from lo to hi-1 now form our
subarray that we can send to the recursive call.
For example, if this were our original problem:

arr[i] 6 11 17 23 39 41 47 54 67 71 88 93 70
-----------------------------------------------------------
i 0 1 2 3 4 5 6 7 8 9 10 11 12

Then what we are looking for, is some way of getting this situation:

arr[i] |<---- this entire section is sorted ---->| 70 ???


-----------------------------------------------------------
i 0 1 2 3 4 5 6 7 8 9 10 11 12
|_______________________________________________|
send this to recursive call

How could we obtain that situation with minimal work? And what about the value to the right
of 70? What should it be?
Well, in our example, 93 is greater than 70, so it should be to the right of 70 anyway. So why
not swap them?
For example, if this were our original problem:

// original problem...
arr[i] 6 11 17 23 39 41 47 54 67 71 88 93 70
-----------------------------------------------------------
i 0 1 2 3 4 5 6 7 8 9 10 11 12

// ...but 93 > 70, so swap the two values...


arr[i] 6 11 17 23 39 41 47 54 67 71 88 70 93
-----------------------------------------------------------
i 0 1 2 3 4 5 6 7 8 9 10 11 12

// ...and now we have subarray for recursive call!


arr[i] 6 11 17 23 39 41 47 54 67 71 88 70 93
-----------------------------------------------------------
i 0 1 2 3 4 5 6 7 8 9 10 11 12
|______________________________________________|

Since our algorithm is supposed to “insert new value at far right into sorted section to the left”,
then if we trust that it works, we could now run that algorithm recursively, and “insert new value at
index 11 into sorted section from indices 0 through 10 inclusive”. We solved the original problem,
“insert new value at index 12 into sorted section from indices 0 through 11 inclusive”, by swapping
the values at indices 12 and 11, leaving us with the problem, “insert new value at index 11 into
sorted section from indices 0 through 10 inclusive”, which we are claiming we can solve recursively.
Our overall problem is to insert 70 into the sorted section of values from 6 through 93; once we
322

swap 70 and 93, we can make a recursive call to that would insert 70 into the sorted section of
values from 6 through 88.
Now, that would not work in all cases. What if instead of our new value being 70, it is 95?:

// original problem...
arr[i] 6 11 17 23 39 41 47 54 67 71 88 93 95
-----------------------------------------------------------
i 0 1 2 3 4 5 6 7 8 9 10 11 12

// ...but 93 > 70, so swap the two values...


arr[i] 6 11 17 23 39 41 47 54 67 71 88 95 93
-----------------------------------------------------------
i 0 1 2 3 4 5 6 7 8 9 10 11 12

// ...and now we have subarray for recursive call!


arr[i] 6 11 17 23 39 41 47 54 67 71 88 95 93
-----------------------------------------------------------
i 0 1 2 3 4 5 6 7 8 9 10 11 12
|______________________________________________|

In this case, we could insert 95 into the sorted section from 6 through 88, but even once that is
done, we have 93 to the right and that is less than 95 – which we’ve now moved to its left – so
there’s at least part of the final array that will be out of order. So something’s gone wrong here.
Let’s take a look at that original problem closely.

// original problem...
arr[i] 6 11 17 23 39 41 47 54 67 71 88 93 95
-----------------------------------------------------------
i 0 1 2 3 4 5 6 7 8 9 10 11 12

In this case, our new value, 95, is greater than the value to its left, 93. Remember that everything
to the left of 95 is sorted – and thus 93 is the greatest value in the sorted range. If 95 is greater
than that, it must also be greater than everything else in the sorted range...meaning 95 should be
placed after the entire sorted range – exactly as it is right now!
Or in other words – if our new value is greater than everything in the sorted range, we can
leave it right where it is. And it’s easy to tell if the new value is greater than everything in the
sorted range – just compare it to the value on its left, which is the greatest value in the sorted
range. Above, since 95 is greater than 93, we know that 95 is also greater than everything that 93
is greater than.
This is one of our base cases, since once we establish that the new value is the greatest of the
values we are dealing with, we know it is correctly placed (as it is at the end) and so we don’t need
to move any of the values anymore. So, so far, we have the following code:
323

// assume lo...hi-1 is a sorted range, and hi holds the new value


public static void insertInOrder(int[] arr, int lo, int hi)
{
if (arr[hi] >= arr[hi - 1]) // last value is greatest
; // we don’t need to do anything, as we said above
else // arr[hi] < arr[hi - 1]
{
swap(arr, hi - 1, hi);
insertInOrder(arr, lo, hi - 1); // the subproblem we discussed earlier
}
}

Now, the above code assumes that we have two values to compare; we might not. If the sorted
section to the left was completely empty, we can’t compare arr[hi] and arr[hi-1] since arr[hi-1]
would not exist!

// all we have here is arr[hi], not arr[hi-1]


arr[i] newValue
-------------------------------------------------
i lo == hi

If your original sorted array was empty, as it is above, then no matter what the new value is,
placing it in cell lo is the correct way to go. You’re inserting one new value into a subarray with
one (empty) cell. It’s the “trivial” case, where there’s nothing else there yet and thus nothing that
your new value could be out of order with. That’s our other base case. So, let’s add that to our
code:

// assume lo...hi-1 is a sorted range, and hi holds the new value


public static void insertInOrder(int[] arr, int lo, int hi)
{
if (lo == hi) // subarray is of size 1
; // we don’t need to do anything
else if (arr[hi] >= arr[hi - 1]) // last value is greatest
; // we don’t need to do anything
else // lo < hi and arr[hi] < arr[hi - 1]
{ //
swap(arr, hi - 1, hi); // 1) swap last two values
insertInOrder(arr, lo, hi - 1); // 2) run the subproblem
}
}

Note that we are assuming the array is of at least size 1. If our new value is in one of the cells,
the array has to be at least size 1. So we don’t need to check for lo > hi. Furthermore, note that
we can eliminate the first two cases, since they don’t actually do anything – all they have is empty
statements. This gives us our final version of insertInOrder(...):
324

// assume lo...hi-1 is a sorted range, and hi holds the new value


public static void insertInOrder(int[] arr, int lo, int hi)
{
if ((lo < hi) && (arr[hi] < arr[hi - 1]))
{
swap(arr, hi - 1, hi); // 1) swap last two values
insertInOrder(arr, lo, hi - 1); // 2) run the subproblem
}
// else new value is already properly inserted; do nothing
}

If we ran this algorithm on our original example array, we’d need four insertInOrder(...)
calls total, including the base case:

// original problem: lo == 0, hi == 12

arr[i] 6 11 17 23 39 41 47 54 67 71 88 93 70
-----------------------------------------------------------
i 0 1 2 3 4 5 6 7 8 9 10 11 12

// 70 < 93, so swap, and recursive call has lo == 0, hi == 11

arr[i] 6 11 17 23 39 41 47 54 67 71 88 70 93
-----------------------------------------------------------
i 0 1 2 3 4 5 6 7 8 9 10 11 12

// 70 < 88, so swap, and recursive call has lo == 0, hi == 10

arr[i] 6 11 17 23 39 41 47 54 67 71 70 88 93
-----------------------------------------------------------
i 0 1 2 3 4 5 6 7 8 9 10 11 12

// 70 < 71, so swap, and recursive call has lo == 0, hi == 9

arr[i] 6 11 17 23 39 41 47 54 67 70 71 88 93
-----------------------------------------------------------
i 0 1 2 3 4 5 6 7 8 9 10 11 12

// 70 > 67, so we’ve hit our base case and we are done

We can use this algorithm as the basis for another sorting algorithm, called insertion sort. The
insertion sort algorithm works in a slightly different manner than selection sort did. Both sorting
algorithms build an increasingly-larger sorted array as the algorithm proceeds. The difference is
that selection sort builds the array by selecting a specific value (the minimum) from the unsorted
values, and then sorting the remainder of the unsorted values, whereas insertion sort will sort most
of the values first, and then insert the last value into the sorted collection. That is, in selection
sort, the recursive call is the last thing done; in insertion sort, it is the first thing done.
325

Description of insertion sort algorithm:

If the array is of size 1, do nothing. Otherwise


recursively sort all but the last cell of this subarray.
Then, insert the value in the last cell, into the sorted
collection to its left.

The code for this algorithm, basically just needs to check if the array size is greater than 1, and if
so, make two method calls – the first, a recursive call to insertionSort(...), and the second, a
call to the insertInOrder(...) method we wrote earlier:

public static void insertionSort(int[] arr, int lo, int hi)


{
if (lo < hi)
{
insertionSort(A, lo, hi - 1);
insertInOrder(A, lo, hi); // everything from lo through hi-1 is
// sorted; arr[hi] is "new value"
}
}

Here is an example, on an array of size 10 (the beginning of this gets a little repetitive, but I wanted
to be complete so that everyone could see every detail if they wanted):

arr[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Recursive call #1
Array as we start to sort elements at indices 0 through 9

Since the array is of size greater than 1, we decide to sort the values at indices 0 through 8
first. Once this is done, we can then insert the value arr[9] into its proper place among the sorted
elements in cells 0 through 8. But first we must recursively sort cells 0 through 8!!

arr[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Recursive call #2
Array as we start to sort elements at indices 0 through 8

Since the subarray is of size greater than 1, we decide to sort the values at indices 0 through 7
first. Once this is done, we can then insert the value arr[8] into its proper place among the sorted
elements in cells 0 through 7. But first we must recursively sort cells 0 through 7!!
326

arr[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Recursive call #3
Array as we start to sort elements at indices 0 through 7

Since the subarray is of size greater than 1, we decide to sort the values at indices 0 through 6
first. Once this is done, we can then insert the value arr[7] into its proper place among the sorted
elements in cells 0 through 6. But first we must recursively sort cells 0 through 6!!

arr[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Recursive call #4
Array as we start to sort elements at indices 0 through 6

Since the subarray is of size greater than 1, we decide to sort the values at indices 0 through 5
first. Once this is done, we can then insert the value arr[6] into its proper place among the sorted
elements in cells 0 through 5. But first we must recursively sort cells 0 through 5!!

arr[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Recursive call #5
Array as we start to sort elements at indices 0 through 5

Since the subarray is of size greater than 1, we decide to sort the values at indices 0 through 4
first. Once this is done, we can then insert the value arr[5] into its proper place among the sorted
elements in cells 0 through 4. But first we must recursively sort cells 0 through 4!!

arr[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Recursive call #6
Array as we start to sort elements at indices 0 through 4

Since the subarray is of size greater than 1, we decide to sort the values at indices 0 through 3
first. Once this is done, we can then insert the value arr[4] into its proper place among the sorted
elements in cells 0 through 3. But first we must recursively sort cells 0 through 3!!
327

arr[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Recursive call #7
Array as we start to sort elements at indices 0 through 3

Since the subarray is of size greater than 1, we decide to sort the values at indices 0 through 2
first. Once this is done, we can then insert the value arr[3] into its proper place among the sorted
elements in cells 0 through 2. But first we must recursively sort cells 0 through 2!!

arr[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Recursive call #8
Array as we start to sort elements at indices 0 through 2

Since the subarray is of size greater than 1, we decide to sort the values at indices 0 through 1
first. Once this is done, we can then insert the value arr[2] into its proper place among the sorted
elements in cells 0 through 1. But first we must recursively sort cells 0 through 1!!

arr[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Recursive call #9
Array as we start to sort elements at indices 0 through 1

Since the subarray is of size greater than 1, we decide to sort the values at indices 0 through 0
first. Once this is done, we can then insert the value arr[1] into its proper place among the sorted
elements in cells 0 through 0. But first we must recursively sort cells 0 through 0!!

arr[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Recursive call #10


Array as we start to sort elements at indices 0 through 0

Now that the array is down to size 1, that is our base case. Note that right now we have ten
different method calls active at once! From main(), we called insertionSort, and from there we
called insertionSort a second time, and from there we called insertionSort a third time, and
so on. We’ve never returned from any of the insertionSort calls, because the first instruction in
each call was to call insertionSort again on a smaller array. But now we will start to return to
them one by one and do more work. For example, we are in recursive call #10 right now, but in a
moment we will finish call #10, and return to call #9 to finish it up. Once we finish call #9 up, we
will then return to call #8 and finish it up. And so on – one by one we will return to the previous
328

recursive call and finish it, until finally we are returning from the very first insertionSort call
back to main(), and at that point the array will be sorted and we will be finished.
This recursive call #10 that we are in now is the case where making a further recursive call
would either be pointless or would not make any sense. The subarray with indices 0 through 0 is
already sorted – it is just one element and that element is of course in its proper order, since there
is nothing to be out of order with. So, we are done with this method call (in the code, the
lo < hi condition would be false here, since lo == hi) and we return from recursive call #10,
back to recursive call #9.

arr[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Array after returning to recursive call #9.

At this point, we have finished recursive call #10, which sorted the elements at indices 0 through
0, and we are going to insert the value at index 1 into the sorted subarray consisting of the elements
at indices 0 through 0. This results progressively swapping 11 with the element to its left until
either 11 is less than the element to its left, or there are no more elements to 11’s left. Since arr[0]
is 93, we swap 11 with 93 and then 11 is at the far left so we stop.

arr[i] 11 93 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Array right before returning from recursive call #9.


Thus, also array right after returning to recursive call #8.

And now that we have finished recursive call #9, note that we have sorted the elements in cells
0 through 1, as was the point of this method call. Now, when we return from call #9 back to call
#8, the subarray consisting of cells 0 through 1 is sorted, and we need to insert the value in cell 2
into that sorted subarray in sorted order. Again, that consists of swapping our value – 71 in this
case – with the value to its left until either the element to its left is less than 71, or there are no
elements to the left. Here, we swap 71 with 93, but note that 11 is less than 71, and so we don’t
swap those two. And thus we are done with recursive call #8.

arr[i] 11 71 93 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Array right before returning from recursive call #8.


Thus, also array right after returning to recursive call #7.

And now that we have finished recursive call #8, note that we have sorted the elements in cells
0 through 2, as was the point of this method call. Now, when we return from call #8 back to call
#7, the subarray consisting of cells 0 through 2 is sorted, and we need to insert the value in cell
3 into that sorted subarray in sorted order. Again, that consists of swapping our value – 6 in this
case – with the value to its left until either the element to its left is less than 6, or there are no
elements to the left. Here, we swap 6 with 93, and then 71, and then 11, and then realize there are
no more elements to the left of 6, and so we stop. And thus we are done with recursive call #7.
329

arr[i] 6 11 71 93 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Array right before returning from recursive call #7.


Thus, also array right after returning to recursive call #6.

And now that we have finished recursive call #7, note that we have sorted the elements in cells
0 through 3, as was the point of this method call. Now, when we return from call #7 back to call
#6, the subarray consisting of cells 0 through 3 is sorted, and we need to insert the value in cell 4
into that sorted subarray in sorted order. Again, that consists of swapping our value – 39 in this
case – with the value to its left until either the element to its left is less than 39, or there are no
elements to the left. Here, we swap 39 with 93, and then 71, and then realize that 11 is less than
39 and so we stop. And thus we are done with recursive call #6.

arr[i] 6 11 39 71 93 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Array right before returning from recursive call #6.


Thus, also array right after returning to recursive call #5.

And now that we have finished recursive call #6, note that we have sorted the elements in cells
0 through 4, as was the point of this method call. Now, when we return from call #6 back to call
#5, the subarray consisting of cells 0 through 4 is sorted, and we need to insert the value in cell 5
into that sorted subarray in sorted order. Again, that consists of swapping our value – 67 in this
case – with the value to its left until either the element to its left is less than 67, or there are no
elements to the left. Here, we swap 67 with 93, and then 71, and then realize that 39 is less than
67 and so we stop. And thus we are done with recursive call #5.

arr[i] 6 11 39 67 71 93 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Array right before returning from recursive call #5.


Thus, also array right after returning to recursive call #4.

And now that we have finished recursive call #5, note that we have sorted the elements in cells
0 through 5, as was the point of this method call. Now, when we return from call #5 back to call
#4, the subarray consisting of cells 0 through 5 is sorted, and we need to insert the value in cell 6
into that sorted subarray in sorted order. Again, that consists of swapping our value – 41 in this
case – with the value to its left until either the element to its left is less than 41, or there are no
elements to the left. Here, we swap 41 with 93, and then 71, and then 67, and then realize that 39
is less than 41 and so we stop. And thus we are done with recursive call #4.
330

arr[i] 6 11 39 41 67 71 93 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Array right before returning from recursive call #4.


Thus, also array right after returning to recursive call #3.

And now that we have finished recursive call #4, note that we have sorted the elements in cells
0 through 6, as was the point of this method call. Now, when we return from call #4 back to call
#3, the subarray consisting of cells 0 through 6 is sorted, and we need to insert the value in cell 7
into that sorted subarray in sorted order. Again, that consists of swapping our value – 88 in this
case – with the value to its left until either the element to its left is less than 88, or there are no
elements to the left. Here, we swap 88 with 93, and then realize that 71 is less than 88 and so we
stop. And thus we are done with recursive call #3.

arr[i] 6 11 39 41 67 71 88 93 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Array right before returning from recursive call #3.


Thus, also array right after returning to recursive call #2.

And now that we have finished recursive call #3, note that we have sorted the elements in cells
0 through 7, as was the point of this method call. Now, when we return from call #3 back to call
#2, the subarray consisting of cells 0 through 7 is sorted, and we need to insert the value in cell 8
into that sorted subarray in sorted order. Again, that consists of swapping our value – 23 in this
case – with the value to its left until either the element to its left is less than 23, or there are no
elements to the left. Here, we swap 23 with 93, and then 88, and then 71, and then 67, and then
41, and then 39, and then finally we realize that 11 is less than 23 and so we stop. And thus we
are done with recursive call #2.

arr[i] 6 11 23 39 41 67 71 88 93 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Array right before returning from recursive call #2.


Thus, also array right after returning to recursive call #1.

And now that we have finished recursive call #2, note that we have sorted the elements in cells
0 through 8, as was the point of this method call. Now, when we return from call #2 back to call
#1, the subarray consisting of cells 0 through 8 is sorted, and we need to insert the value in cell 9
into that sorted subarray in sorted order. Again, that consists of swapping our value – 54 in this
case – with the value to its left until either the element to its left is less than 54, or there are no
elements to the left. Here, we swap 54 with 93, and then 88, and then 71, and then 67, and then
realize that 41 is less than 54 and so we stop. And thus we are done with recursive call #1.
331

arr[i] 6 11 23 39 41 54 67 71 88 93
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Array right before returning from recursive call #1.

At this point, the array is sorted, and we return from recursive call #1 back to main().
And that’s insertion sort! The algorithm is named “insertion sort” because it basically consists
of running an “insert new value into a sorted collection in sorted order” procedure over and over
again, for each new value we see. We have a collection of size 1. Then we insert a new value into
that collection, in sorted order, so that we have a sorted collection of size 2. Then we insert a new
value into that collection, in sorted order, so that we have a sorted collection of size 3. Then we
insert a new value into that collection, in sorted order, so that we have a sorted collection of size
4. And so on. This is in contrast to “selection sort”, where we kept “selecting” the smallest value,
out of the unsorted collection of values we still had left.
332

It works in either direction!

While we have described the two sorting algorithms we have seen so far, in their “standard”
direction, please note that if we had done things from the other direction, it’s still effectively the
same idea, and thus the same algorithm.
For example, for selection sort, we kept selecting the minimum of what was left and moving it
to cell lo, and then sorting lo+1...hi. Instead, we could always select the maximum of what was
left, and move it to cell hi, and then sort lo...hi-1. It’s the same basic idea, just from the other
direction:

public static int findMaximum(int[] arr, int lo, int hi)


{
if (lo == hi)
return lo;
else
{
int locOfMaxOfRest = findMaximum(arr, lo + 1, hi);
if (arr[lo] >= arr[locOfMaxOfRest]) // arr[lo] is max value
return lo;
else // arr[locOfMaxOfRest] > arr[lo] --> recursive result is max value
return locOfMaxOfRest;
}
}

public static void selectionSort(int[] arr, int lo, int hi)


{
if (lo < hi)
{
int maxLocation = findMaximum(arr, lo, hi);
swap(arr, hi, maxLocation);
selectionSort(arr, lo, hi - 1);
}
}

However, the “official” version of selection sort, and the one we’ll use for this course, is the one
where you select the minimum each time.
Similarly, for insertion sort, rather than always insert a new value into the sorted range to its
left, we could always insert a new value into the sorted range to its right. We start by modifying
insertInOrder(...) to assume the “new value” is at the far left, and that you are inserting it
into a sorted range to the right:
333

// inserts "new value" at arr[lo] into sorted range from


// arr[lo+1] through arr[hi]
public static void insertInOrder(int[] arr, int lo, int hi)
{
if (lo == hi) // if one cell, you are done
;
else if (arr[lo] <= arr[lo + 1]) // if arr[lo] is lowest, leave it
;
else // lo < hi and arr[lo] > arr[lo + 1]
{
swap(arr, lo + 1, lo); // arr[lo + 1] is lowest, swap...
insertInOrder(arr, lo + 1, hi); // ...then insert ‘‘new value’’ into
// smaller sorted subarray to the right
}
}

Of course, that code can have the base case code eliminated, just as with our earlier version of
insertInOrder(...), and then we just rework the insertionSort(...) algorithm in the same
way:

// inserts "new value" at arr[lo] into sorted range from


// arr[lo+1] through arr[hi]
public static void insertInOrder(int[] arr, int lo, int hi)
{
if ((lo < hi) && (arr[lo] > arr[lo + 1]))
{
swap(arr, lo + 1, lo); // arr[lo+1] is lowest, swap...
insertInOrder(arr, lo + 1, hi); // ...then insert ‘‘new value’’ into
// smaller sorted subarray to the right
}
// else, arr[lo] is where it belongs; do nothing
}

public static void insertionSort(int[] arr, int lo, int hi)


{
if (lo < hi)
{
insertionSort(arr, lo + 1, hi);
insertInOrder(arr, lo, hi); // everything from lo+1 through hi is
// sorted; arr[lo] is "new value"
}
}

However, the “official” version of insertion sort, and the one we’ll use for this course, is the one we
went through in detail earlier.
334

Lecture 28 : Tail Recursion and Loop Conversion


What we will do today is discuss the two types of recursion. The first is called tail recursion. Tail
recursion is when the recursive call is the last computation you do in the recursive case of your
algorithm. The only things you can do after your recursive call returns, are return with nothing,
or perhaps return with an already-calculated value.
Many of the recursive methods we have already seen, are tail-recursive. One example, is our
method for printing out the cells of an array:
// prints all values of arr whose indices are in range lo...hi inclusive,
// in order from lowest to highest index
public static void print(int[] arr, int lo, int hi)
{
if (lo <= hi)
{
System.out.println(arr[lo]);
print(arr, lo + 1, hi);
}
// else you do nothing
}
Above, once you call print(...) recursively, you never need to do anything in that particular
recursive call again. That is, after the print(...) call returns, the method itself returns (the
compound statement ends, and thus the if-statement ends, and thus you reach the closing curly
brace of the method, and thus return from the method).
Another example of a tail-recursive method would be our linearSearch(...) method:
public static int linearSearch(int[] arr, int key, int lo, int hi)
{
int returnVal;
if (lo > hi)
returnVal = -1;
else if (arr[lo] == key)
returnVal = lo;
else
returnVal = linearSearch(arr, key, lo+1, hi);

return returnVal;
}
In the case of linearSearch(...), you do not have a return value of void, like the print(...)
method did. Instead, we are returning a value of type int. So, once the recursive call returns, we
still need to execute an assignment statement (to write the return value into the variable returnVal)
and then we need to execute a return statement (to return the variable returnVal). That is okay,
however, since once the recursive call returns a value to us, all we do after that, is copy the value
into a local variable, and then return it. We don’t actually perform any additional computation,
nor do we copy the value to places outside the scope of this method. We just copy the value from
being the result of a recursive-method-call expression, into returnVal, and then we copy that value
from returnVal, to being the return value of this method. So the above code is basically just a
longer form of this code:
335

public static int linearSearch(int[] arr, int key, int lo, int hi)
{
if (lo > hi)
return -1;
else if (arr[lo] == key)
return lo;
else
return linearSearch(arr, key, lo+1, hi);
}

In that case, as soon as the recursive call returns, we immediately turn around and return that
value, without having written it into any variables in-between. The fact that in the first version,
we wrote the result of the recursive call into a variable before we returned it, doesn’t change the
fact that the code is tail-recursive, since the assignment statement could easily be eliminated, as
we see in the second version above.
Note that even if we re-wrote linearSearch(...) so that the cases were in a different order,
it would still be a tail-recursive method:

// an alternate way to code linearSearch


public static int linearSearch(int[] arr, int key, int lo, int hi)
{
if (lo <= hi)
{
if (arr[lo] != key)
return linearSearch(arr, key, lo + 1, hi);
else
return lo;
}
else // lo > hi
return -1;
}

In that case, once the recursive call returns, we would immediately run a return statement, and so
even though the actual line of code that triggers the recursive call, is near the top of the method,
you still are done with the method once that recursive call returns. Where the recursive call is
placed on the page doesn’t have any particular affect on whether a recursive method is tail-recursive
or not; the issue is how much computation is performed once the recursive call is over. And, in
both the print(...) and linearSearch(...) examples, no additional computation needs to be
done once the recursive call returns.
336

The second kind of recursion is known as forward recursion. This is recursion where you do
need to do computation work after you return from the recursive call. An example was the factorial
method from earlier, where we needed to still do a multiplication once we returned from the recursive
call:

public static int fac(int n) // n >= 0


{
if (n > 0)
return n * fac(n - 1);
else // n == 0
return 1;
}

Basically, if a recursive method is not tail-recursive, then it is forward-recursive, and vice-versa.


Our focus today will be on tail-recursive methods. They are of special interest to us, because
the fact that they are tail-recursive makes them very easy to convert into loop-based methods! The
reason for this is, nothing significant gets done once you start returning from the recursive calls, so
if you could somehow “end the recursive process” right at the base case, you’d be fine.
For example, consider our print(...) method again. Let’s assume arr points to an array of
size 4, and so we’ve sent 0 and 3 as the initial arguments to lo and hi. Here’s how the method
calls would look, as we made our way down to the base case:
337

__________________________________________________________________
| main
| print(arr, 0, 3);
|
|_________________________________________________________________
| print (arr: [pts to array])
| (#1) (lo: 0) (hi: 3)
|
| prints arr[0], then calls print(arr, 1, 3);
|
|__________________________________________________________________
| print (arr: [pts to array])
| (#2) (lo: 1) (hi: 3)
|
| prints arr[1], then calls print(arr, 2, 3);
|
|__________________________________________________________________
| print (arr: [pts to array])
| (#3) (lo: 2) (hi: 3)
|
| prints arr[2], then calls print(arr, 3, 3);
|
|__________________________________________________________________
| print (arr: [pts to array])
| (#4) (lo: 3) (hi: 3)
|
| prints arr[3], then calls print(arr, 4, 3);
|
|__________________________________________________________________
| print (arr: [pts to array])
| (#5) (lo: 4) (hi: 3)
|
| lo > hi, so this is base case
|
|__________________________________________________________________

Now, all that is left, is to return from each of the method calls. There is no more work to do. In
fact, if while we are in the base case, the earlier method notecards “mysteriously vanished”:
338

__________________________________________________________________
| main
| print(arr, 0, 3);
|
|_________________________________________________________________
|
|
| \ | /
| - POOF -
| / | \
|
|__________________________________________________________________
| print (arr: [pts to array])
| (#5) (lo: 4) (hi: 3)
|
| lo > hi, so this is base case
|
|__________________________________________________________________

it doesn’t affect the running of our code at all; all the work is already done. We could just return
from the base case back to main(), and be done.
Contrast that, with a call to fac(4):
339

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
| fac (n: 4)
| (#1)
|
| need to calculate: fac(3), in order to calculate 4 * fac(3)
|
|__________________________________________________________________
| fac (n: 3)
| (#2)
|
| need to calculate: fac(2), in order to calculate 3 * fac(2)
|
|__________________________________________________________________
| fac (n: 2)
| (#3)
|
| need to calculate: fac(1), in order to calculate 2 * fac(1)
|
|__________________________________________________________________
| fac (n: 1)
| (#4)
|
| need to calculate: fac(0), in order to calculate 1 * fac(0)
|
|__________________________________________________________________
| fac (n: 0)
| (#5)
|
| base case! (we have not returned yet)
|__________________________________________________________________

In this case, we need to return to the previous method calls, in order to complete the multipli-
cations. If while we are in the base case, the other method notecards “mysteriously vanish”:
340

__________________________________________________________________
| main
| int x;
| x = fac(4);
|_________________________________________________________________
|
|
| \ | /
| - POOF -
| / | \
|
|__________________________________________________________________
| fac (n: 0)
| (#5)
|
| base case! (we have not returned yet)
|__________________________________________________________________

then we are left only with our statement return 1;, and cannot complete the rest of the work –
since we do not have the other method notecards anymore, we can’t complete the multiplications.
If we return from the base case, directly back to main(), then main() would think that the value
of fac(4) is 1, and of course, that’s not true.
That is the reason we care if a method is a tail-recursive method or a forward-recursive method.
Because there is no work done in a tail-recursive method once the recursive call is completed, it
means we effectively have finished all the work we need to do, once we finish the base case; in a
forward-recursive method, we will still be doing work as we return from each of the other non-base-
case method calls as well.
The reason this is relevant, is as follows: if we won’t ever need the earlier method notecards
again, then there is no reason to save them. That is, right now, when we start the print(...)
method, we make the first print(...) call, and from there, make the second print(...) call:
341

__________________________________________________________________
| main
| print(arr, 0, 3);
|_________________________________________________________________
| print (arr: [pts to array])
| (#1) (lo: 0) (hi: 3)
|
| prints arr[0], then calls print(arr, 1, 3);
|__________________________________________________________________
| print (arr: [pts to array])
| (#2) (lo: 1) (hi: 3)
|
| prints arr[1], then calls print(arr, 2, 3);
|__________________________________________________________________

But if we won’t ever “need” the first print(...) notecard again, then why save it? The only
reason it is hanging around, is so that we can eventually return to it, but once we do return to it,
we will immediately leave it and return back to main(). So, why make an entirely new notecard
for call #2, when we have this notecard sitting around for call #1 that we’ll never really need to
make detailed use of anymore? Why not just convert the first notecard into the second one, by
increasing lo right there on the first notecard?

FROM:
__________________________________________________________________
| main
| print(arr, 0, 3);
|_________________________________________________________________
| print (arr: [pts to array])
| (#1) (lo: 0) (hi: 3)
|
| prints arr[0], then calls print(arr, 1, 3);
|__________________________________________________________________

TO:
__________________________________________________________________
| main
| print(arr, 0, 3);
|_________________________________________________________________
| print (arr: [pts to array])
| (#2) (lo: 1) (hi: 3)
|
| prints arr[1], then calls print(arr, 2, 3);
|__________________________________________________________________

And similarly, we can keep converting that notecard for each subsequent call. First, we make call
#3 from call #2:
342

FROM:
__________________________________________________________________
| main
| print(arr, 0, 3);
|_________________________________________________________________
| print (arr: [pts to array])
| (#2) (lo: 1) (hi: 3)
|
| prints arr[1], then calls print(arr, 2, 3);
|__________________________________________________________________

TO:
__________________________________________________________________
| main
| print(arr, 0, 3);
|_________________________________________________________________
| print (arr: [pts to array])
| (#3) (lo: 2) (hi: 3)
|
| prints arr[2], then calls print(arr, 3, 3);
|__________________________________________________________________

and next we make call #4 from call #3:

FROM:
__________________________________________________________________
| main
| print(arr, 0, 3);
|_________________________________________________________________
| print (arr: [pts to array])
| (#3) (lo: 2) (hi: 3)
|
| prints arr[2], then calls print(arr, 3, 3);
|__________________________________________________________________

TO:
__________________________________________________________________
| main
| print(arr, 0, 3);
|_________________________________________________________________
| print (arr: [pts to array])
| (#4) (lo: 3) (hi: 3)
|
| prints arr[3], then calls print(arr, 4, 3);
|__________________________________________________________________
343

and finally, we make call #5 from call #4:

FROM:
__________________________________________________________________
| main
| print(arr, 0, 3);
|_________________________________________________________________
| print (arr: [pts to array])
| (#4) (lo: 3) (hi: 3)
|
| prints arr[3], then calls print(arr, 4, 3);
|__________________________________________________________________

TO:
__________________________________________________________________
| main
| print(arr, 0, 3);
|_________________________________________________________________
| print (arr: [pts to array])
| (#5) (lo: 4) (hi: 3)
|
| lo > hi, so we are done
|__________________________________________________________________

At each step, rather than make a new method notecard for a new method call, we simply convert
the notecard we already have (representing the old call), into the notecard we need (representing
the new call). This works because we never need the old notecard again. So, it’s okay to write over
all the data for the old notecard, with new data for the new notecard. If we did need the old data
again (like in fac(...), when each non-base-case call needs to take a returned value and perform
a multiplication with it), then we can’t do this. But for tail-recursive methods, we will never need
the old notecard again, so “erasing” it by writing over its data with data for the new notecard, is
fine.
In the last picture above, the print(...) call representing the base case of the method, could
then return straight back to main(), and our work is done.

What we have just done here is “invent” the loop!

That is, the above is all a loop is: you are making a recursive call, but rather than start a new
notecard underneath the old one, you simply convert the existing (old) notecard into the new one.
When we actually set up each notecard, what a recursive call does it to calculate some new values
for the parameters (the expressions that serve as the arguments are these new values), and then
a new notecard, with new versions of the parameters, is created, and those argument values are
copied into those new versions of the parameters:
344

call from previous method: print(arr, lo + 1, hi);


. --- ------ --
. | | \
. | | \
. \|/ \|/ \|/
public static void print(int[] arr, int lo, int hi)
{

Instead, we are now going to copy those arguments into the existing versions of the parameters.
That is, rather than copy arr, lo + 1, and hi, into new versions of the parameters arr, lo, and hi
in the next call, instead copy them into the versions of those parameters in this call, via assignments
statements:

this is the call we ‘‘want to’’ make: print(arr, lo + 1, hi);

// param = expression;
arr = arr;
lo = lo + 1;
hi = hi;

(yes, we know assigning a variable


to itself, as we did with arr and
hi, is useless; it doesn’t do any harm,
either, so we’ll keep it there for now
and get rid of it a bit later)

and then, just as we would copy the arguments into new parameters, and then repeat the algorithm
(by starting the method again on a new notecard), we will here, copy the arguments into the existing
parameters,a and then repeat the algorithm, via a loop:

loop
{
...
arr = arr;
lo = lo + 1;
hi = hi;
// now we repeat everything in loop all over again, with these
// new values stored in the parameters
}
345

That is, we are changing our recursive case from this:

if (lo <= hi)


{
System.out.println(arr[lo]);

print(arr, lo + 1, hi); // copy arr, lo + 1, and hi, into new


// version of parameters, and repeat
}

to this:

while (lo <= hi) // same condition


{
System.out.println(arr[lo]);

// param = argument; // copy arr, lo + 1, and hi, into current


arr = arr; // version of parameters, and repeat
lo = lo + 1;
hi = hi;
}

That is the core idea behind converting a tail-recursive method into a loop-based method. We want
to keep repeating the method code over and over and over, but rather than continually making new
method calls to accomplish this, we stay in the method call we are already in, and just repeat the
method code via a loop. The glue that holds the loop version together is that we still need to “copy
the arguments into the parameters” – we just copy the arguments into the current parameters
that are already a part of our current notecard, rather than creating a new notecard to hold the
parameters.
If we have a recursive method organized as follows:

recursiveMethod(params)
{
if (recursiveCase)
{
code we run before recursive call
recursiveMethod(args);
}
else // base case
{
base case code
}
}

then the loop-based version would rearrange the code as follows:


346

loopBasedMethod(params)
{
while (recursiveCase)
{
code we run before recursive call
params = args;
}
base case code
}

where the line “params = args;” is a stand-in for all the assignments you need to write each of
the arguments to the recursive call, back into the current method call’s parameters.
For example, consider the print(...) method again:

public static void print(int[] arr, int lo, int hi)


{
if (lo <= hi)
{
System.out.println(arr[lo]);
print(arr, lo + 1, hi);
}
// else you do nothing
}

First of all, the “base case code” is basically nothing (or, if you wish, an empty statement). So
when we write our loop, we’ll have nothing after the loop finishes. The condition that indicates
that we should enter the recursive case is lo <= hi, so that is the same condition that indicates
we enter our loop. And, the only code in the recursive case that is performed before the recursive
call, is the printing out of arr[lo]. That gives us the following rough draft of loop-based code:

// rough draft #1
public static void print(int[] arr, int lo, int hi)
{
while (lo <= hi)
{
System.out.println(arr[lo]);
// params = args;
}
// in the base case, we did nothing
}

and for the “params = args;” section of the code, we are writing the recursive call’s arguments
(arr, lo + 1, and hi) into the parameters (arr, lo, and hi):
347

// rough draft #2
public static void print(int[] arr, int lo, int hi)
{
while (lo <= hi)
{
System.out.println(arr[lo]);
arr = arr;
lo = lo + 1;
hi = hi;
}
// in the base case, we did nothing
}

And finally, two of those assignments just assign a variable to itself, so we can get rid of those:

// final loop-based version


public static void print(int[] arr, int lo, int hi)
{
while (lo <= hi)
{
System.out.println(arr[lo]);
lo = lo + 1;
}
// in the base case, we did nothing
}

and now we have a loop-based version of print(...).


As a final tweak, we can recall our wrapper method:

public static void print(int[] arr)


{
print(arr, 0, arr.length - 1);
}

rather than call a second method (our loop-based method) from the wrapper method, we can
simply merge the two methods together if we like. (If you still wanted the method with three
parameters, then you would not want to do this.) Rather than pass 0 and arr.length - 1 to
parameters lo and hi, we could simply make lo and hi local variables, and initialize them to 0
and arr.length - 1, respectively.
348

public static void print(int[] arr)


{
// these two lines are the new ones
int lo = 0;
int hi = arr.length - 1;

while (lo <= hi)


{
System.out.println(arr[lo]);
lo = lo + 1;
}
}

Since we never change hi, we don’t even need that variable to begin with – we can just substitute
arr.length - 1 for hi in the while-loop condition:

public static void print(int[] arr)


{
int lo = 0;
while (lo <= arr.length - 1)
{
System.out.println(arr[lo]);
lo = lo + 1;
}
}

and we are done! Of course, the above code might seem a bit more familiar if we replace the
variable name lo with the variable name i:

public static void print(int[] arr)


{
int i = 0;
while (i <= arr.length - 1)
{
System.out.println(arr[i]);
i = i + 1;
}
}

That’s pretty much the standard code for printing out an array from beginning to end, using a
while-loop.
349

As a second example, consider linearSearch(...):

public static int linearSearch(int[] arr, int key, int lo, int hi)
{
if (lo > hi)
return -1;
else if (arr[lo] == key)
return lo;
else
return linearSearch(arr, key, lo + 1, hi);
}

First of all, let’s rewrite the method in the form:

recursiveMethod(params)
{
if (recursiveCase)
{
code we run before recursive call
recursiveMethod(args);
}
else // base case
{
base case code
}
}

that we mentioned earlier. We hit the recursive case whenever the first two cases are both false
– that is, whenever lo <= hi and arr[lo] != key. So, that’s our recursive condition:

public static int linearSearch(int[] arr, int key, int lo, int hi)
{
if ((lo <= hi) && (arr[lo] != key))
return linearSearch(arr, key, lo + 1, hi);
else // lo > hi or arr[lo] == key
{
if (lo > hi)
return -1;
else // lo <= hi and arr[lo] == key
return lo;
}
}
350

Now applying our loop-version template:

loopBasedMethod(params)
{
while (recursiveCase)
{
code we run before recursive call
params = args;
}
base case code
}

gives us the following:

// rough draft #1
public static int linearSearch(int[] arr, int key, int lo, int hi)
{
while ((lo <= hi) && (arr[lo] != key))
{
// the statement
// return linearSearch(arr, key, lo + 1, hi);
// is converted into ‘‘params = args;’’ form:
arr = arr;
key = key;
lo = lo + 1;
hi = hi;
}
// code from base case appears below:
if (lo > hi)
return -1;
else // lo <= hi and arr[lo] == key
return lo;
}
}

and since lo is the only one of the four parameters that actually gets reassigned, we can eliminate
the other three meaningless assignments:
351

// rough draft #2
public static int linearSearch(int[] arr, int key, int lo, int hi)
{
while ((lo <= hi) && (arr[lo] != key))
{
lo = lo + 1;
}
// code from base case appears below:
if (lo > hi)
return -1;
else // lo <= hi and arr[lo] == key
return lo;
}
}

and thus, we can eliminate the compound statement curly braces as well, if we want:

// final loop-based version


public static int linearSearch(int[] arr, int key, int lo, int hi)
{
while ((lo <= hi) && (arr[lo] != key))
lo = lo + 1;

// code from base case appears below:


if (lo > hi)
return -1;
else // lo <= hi and arr[lo] == key
return lo;
}
}

And now we have our loop-based version of linear search! As with print(...), we can also
combine this with the wrapper method if we wanted to. This would be the wrapper method for
linearSearch(...):

public static int linearSearch(int[] arr, int key)


{
return linearSearch(arr, key, 0, arr.length - 1);
}

so by combining the two, we get:


352

public static int linearSearch(int[] arr, int key)


{
// these two lines are new
int lo = 0;
int hi = arr.length - 1;

while ((lo <= hi) && (arr[lo] != key))


lo = lo + 1;

if (lo > hi)


return -1;
else // lo <= hi and arr[lo] == key
return lo;
}
}

and after replacing hi with a hard-coded arr.length - 1 in the code, and after replacing lo
with i, we get the following code:

public static int LinearSearch(int[] arr, int key, int lo, int hi)
{
int i = 0;
while ((i <= arr.length - 1) && (arr[i] != key))
i = i + 1;

if (i > arr.length - 1) // we ran past end of array


return -1;
else // i <= arr.length - 1 and arr[i] == key // we found value in array
return i;
}
}

which should look familiar, being the sort of loop-based linear search you might have written earlier.
As a final example, we can take the insertInOrder(...) code from the last lecture packet:

// assume lo...hi-1 is a sorted range, and hi holds the new value


public static void insertInOrder(int[] arr, int lo, int hi)
{
if ((lo < hi) && (arr[hi] < arr[hi - 1]))
{
swap(arr, hi - 1, hi); // 1) swap last two values
insertInOrder(arr, lo, hi - 1); // 2) run the subproblem
}
// else new value is already properly inserted; do nothing
}

and convert it the same way:


353

// rough draft #1
// assume lo...hi-1 is a sorted range, and hi holds the new value
public static void insertInOrder(int[] arr, int lo, int hi)
{
while ((lo < hi) && (arr[hi] < arr[hi - 1]))
{
swap(arr, hi - 1, hi); // 1) swap last two values
arr = arr;
lo = lo;
hi = hi - 1;
}
// now, new value is already properly inserted; do nothing
}

And after removing the two useless assignments, we get:

// final loop-based version


// assume lo...hi-1 is a sorted range, and hi holds the new value
public static void insertInOrder(int[] arr, int lo, int hi)
{
while ((lo < hi) && (arr[hi] < arr[hi - 1]))
{
swap(arr, hi - 1, hi); // 1) swap last two values
hi = hi - 1; // 2) run the subproblem
}
// now, new value is already properly inserted; do nothing
}
354

Lecture 29 : Accumulator Recursion


Recall that our factorial method was forward-recursive:

// Implementation #1 of the factorial algorithm


// This is the algorithm in forward-recursion form
public static int fac(int n) // n >= 0
{
if (n > 0)
return n * fac(n - 1);
else // n == 0
return 1;
}

We can’t just convert this to a loop and say n = n - 1 in place of the recursive call, because
we still need that original value of n to do the multiplication once we return from the recursive call.
So making a recursive function of this kind into a loop is not the simple process that it was with
a tail-recursive method. On the other hand, we know a loop-based factorial method is possible,
because we wrote one back in Lecture Notes #21. So how did that loop-based method come about?
How could we write a loop-based method if our factorial algorithm above cannot be converted into
a loop?
The link between the two is a type of recursion is known as accumulator recursion. Accumulator
recursion is a form of tail recursion; the recursive call will be the final work in the recursive case.
However, it’s tail recursion in which we have to add extra “unnecessary” parameters in order to be
able to write it in tail-recursive form. If we wrote it in forward-recursive form, these parameters
would not be needed, but in order to write it in tail-recursive form, we need to add extra parameters
that “accumulate” a result in some way so that we don’t need to do work as we return from the
recursive calls. That’s why it is known as accumulator recursion.
In the case of the factorial method, the only parameter we need for the calculation is the value
of n; as proof of this, we can simply look at the code above, which only has n as a parameter and
yet works perfectly! However, if we want to write a tail-recursive method for calculating factorial,
we will need to add an additional parameter that was not needed for the forward-recursive version.
Specifically, what we want to do is to add in a parameter that will accumulate the product of the
different values of n; we will thus add a second integer parameter to the factorial method. If we
have that second parameter:

public static int fac(int n, int productSoFar)

then our recursive case could multiply n by productSoFar before the recursive call is made, and
thus pass that value as an argument:

return n * fac(n - 1) -------> return fac(n - 1, n * productSoFar)

(old recursive case) (new recursive case)


355

That gives us the following code, which is the start of a new factorial method:

public static int fac(int n, int productSoFar) // n >= 0, productSoFar >= 1


{
if (n > 0)
return fac(n - 1, n * productSoFar);
// still need base case
}

What we are doing here is doing our multiplications before we make the recursive call...but it
does require passing an extra parameter to hold the product so far. Then, when we reach the base
case, we have the entire factorial product and can just return that:

// Implementation #2 of the factorial algorithm


// This is the algorithm in accumulator-recursion form
// The first call to this method should send the value 1 as the second argument
public static int fac(int n, int productSoFar) // n >= 0, productSoFar >= 1
{
if (n > 0)
return fac(n - 1, n * productSoFar);
else // n == 0
return productSoFar;
}

In order to correctly begin the recursion, we need to send an initial value to productSoFar, as well
as sending the initial value we want the factorial of, to n. Generally, we would initialize a product
variable to 1 (just as we would generally initialize a sum variable to 0), so to calculate the factorial
of k, we would no longer call fac(k), but rather, fac(k, 1).
We can see the difference between the two, on the table on the next page. For both examples,
we assume all the method calls down to the base case have been made, but that the base case has
not been returned from yet. On the left, we see the method notecards for the forward-recursive
version; on the right, we see the method notecards for the accumulator-recursive version.
356

Implementation #1 Implementation #2
-------------------- --------------------------
_________________ ________________________
| main | main
| int x; | int x;
| x = fac(4); | x = fac(4, 1);
| |
| |
|________________ |_________________________
| fac (n: 4) | fac (n: 4)
| (#1) | (#1) (productSoFar: 1)
| |
| need: fac(3) | need: fac(3, 4)
|_________________ |_________________________
| fac (n: 3) | fac (n: 3)
| (#2) | (#2) (productSoFar: 4)
| |
| need: fac(2) | need: fac(2, 12)
|____________ |__________________________
| fac (n: 2) | fac (n: 2)
| (#3) | (#3) (productSoFar: 12)
| |
| need: fac(1) | need: fac(1, 24)
|____________ |__________________________
| fac (n: 1) | fac (n: 1)
| (#4) | (#4) (product: 24)
| |
| need: fac(0) | need: fac(0, 24)
|____________ |__________________________
| fac (n: 0) | fac (n: 0)
| (#5) | (#5) (product: 24)
| |
| base case! | base case!
|____________ |___________________________

As we return from the various method calls, from #5 to #4 to #3 to #2 to #1, the forward-
recursive version needs to do some work – multiplications – before each return (except for the
return from the base case), but for the accumulator-recursive method, all the work is already done,
and we just keep returning the same value:
357

Implementation #1 Implementation #2
-------------------- --------------------------
_________________ ________________________
| main | main
| int x; | int x;
| x = fac(4); | x = fac(4, 1);
| |
| [24 written into x] | [24 written into x]
|________________ |_________________________
| fac (n: 4) | fac (n: 4)
| (#1) | (#1) (productSoFar: 1)
| ----> returns 24 |
| need: fac(3) | need: fac(3, 4) ----> returns 24
|_________________ |_________________________
| fac (n: 3) | fac (n: 3)
| (#2) | (#2) (productSoFar: 4)
| ----> returns 6 |
| need: fac(2) | need: fac(2, 12) ----> returns 24
|____________ |__________________________
| fac (n: 2) | fac (n: 2)
| (#3) | (#3) (productSoFar: 12)
| ----> returns 2 |
| need: fac(1) | need: fac(1, 24) ----> returns 24
|____________ |__________________________
| fac (n: 1) | fac (n: 1)
| (#4) | (#4) (product: 24)
| ----> returns 1 |
| need: fac(0) | need: fac(0, 24) ----> retursn 24
|____________ |__________________________
| fac (n: 0) | fac (n: 0)
| (#5) | (#5) (product: 24)
| ----> returns 1 |
| base case! | base case! ----> returns 24
|____________ |___________________________

Each recursive call in the accumulator-recursive version will result in the value of the argument
n - 1 being written back into the parameter n of the next call, and the value of the argument
n * productSoFar begin written into the parameter productSoFar of the next call.
358

Since accumulator recursion is just a form of tail recursion, that means that accumulator-
recursive algorithms can be converted into loops using the technique we already discussed:

public static int fac(int n, int productSoFar) // n >= 0, productSoFar >= 1


{
while (n > 0) // while our recursive-case condition is still met
{
// nothing was done in the recursive case, before the recursive call
// then, we have params = args;
n = n - 1;
productSoFar = n * productSoFar;
}
return productSoFar; // this was originally the base case code
}

We actually have a slight problem here; we are changing n, and then using n in the expression
n * productSoFar. Unfortunately, that multiplication expression expects the old value of n, not
the new one. When we had the line in recursive-call form:

fac(n - 1, n * productSoFar)

then both arguments were evaluated, using the old value of n. But now that we have two assignment
statements, rather than the passing of two arguments to two parameters, the effects of the earlier
assignments exist when we try to perform the later assignments. To get around this, we either need
a temporary variable:

public static int fac(int n, int productSoFar) // n >= 0, productSoFar >= 1


{
while (n > 0) // while our recursive-case condition is still met
{
// nothing was done before the recursive call
// the, we have params = args;
int tempNewN = n - 1;
productSoFar = n * productSoFar;
n = tempNewN; // now we can safely store the new value into n, since
// we are through using the old value of n
}
return productSoFar; // this was originally the base case code
}

or else we can simply reorder those two assignments, which is okay since the evaluation of the new
value of n, does NOT rely on the old value of productSoFar, unlike how the evaluation of the new
value of productSoFar relies on the old value of n. So, the code we get in that case is as follows:
359

public static int fac(int n, int productSoFar) // n >= 0, productSoFar >= 1


{
while (n > 0) // while our recursive-case condition is still met
{
// nothing was done before the recursive call
// the, we have params = args;
productSoFar = n * productSoFar;
n = n - 1;
}
return productSoFar; // this was originally the base case code
}

Finally, for the loop version, we don’t actually need to pass in a ”productSoFar” value. Rather
than requiring that the client send in a 1 as an argument to this parameter, we can simply make
it a local variable, and initialize it to 1 ourselves.

// Implementation #3 of the factorial algorithm


// This is the algorithm in loop-based form
public static int fac(int n) // n >= 0
{
int productSoFar = 1;
while (n > 0)
{
productSoFar = n * productSoFar;
n = n - 1;
}
return productSoFar;
}

The above code is basically the same code you saw in Lecture Notes #21 when we first presented
the loop-based version of factorial! Note that this means we cannot write the loop-based version,
without the use of the extra variable productSoFar. In order to write the algorithm in tail-
recursive (accumulator-recursive) form, you needed the extra variable (as a parameter, in that
case); without it, you could only write the algorithm in forward-recursive form. And the conversion
from tail-recursion to a loop-based version, cannot get rid of needed work like continually writing
into the productSoFar variable. Converting to a loop merely reorganizes when and where certain
computations get done, to make additional method notecards unnecessary; it can’t eliminate the
need for those computations, or for the variables that are used in those computations. So, while
we were able to make the productSoFar variable a local variable rather than a parameter variable
– a change of organization, not computation – we cannot eliminate that variable entirely.
That is the connection between our forward-recursive definition of factorial, and our loop version
of factorial. We add the extra variable productSoFar into our computation, and now that we have
access to that extra variable to use as an accumulator, we can convert from forward-recursion to
accumulator-recursion, and then to a loop-based method. Without that extra variable, we are
forced to perform our computation in a forward-recursive way, so that we can make use of the
saved values in different method notecards, to aid us in our computation.
360

Another example would be our exponentiation algorithm:

// Implementation #1 of the exponentiation algorithm


// This is the algorithm in forward-recursive form
public static int pow(int base, int exp)
{
if (exp == 0)
return 1;
else // exp > 0
return base * pow(base, exp - 1);
}

After we finish a recursive call, there is more work to do besides just returning a value – namely,
the multiplicaton of base and our recursive call result. So, this is a forward-recursive algorithm.
We can try and convert this to an accumulator recursion implementation using the same technique
we used for the factorial computation – instead of multiplying the base by our recursive result, we
could try multiplying the base by an existing product, and then passing that product as a third
parameter to our exponentiation method:

// Implementation #2 of the exponentiation algorithm


// This is the algorithm in accumulator-recursive form
// The first call to this method should send the value 1 as the third argument
public static int pow(int base, int exp, int productSoFar)
{
if (exp == 0)
return productSoFar;
else
return pow(base, exp - 1, base * productSoFar);
}

Note that the only differences between implementation #2 and implementation #1 are:

• We added a third parameter to #2

• We return that third parameter’s value instead of 1 in the base case

• We have a third argument – a multiplication result – passed as the argument to that third
parameter when we make our recursive call

Below we have the “method notecards” generated by both our implementations, before the point
where the base case returns. Note that the first two arguments are the same in both cases, for each
step – but the second implementation has a third parameter whose value steadily increases with
each method call, until by the time we reach the base case, that parameter’s value is our answer.
We calculate the third argument for each call by multiplying the base by the productSoFar.
361

Implementation #1 Implementation #2
-------------------- --------------------------
_________________ ________________________
| main | main
| int x; | int x;
| x = pow(3, 4); | x = pow(3, 4, 1);
| |
| |
|________________ |_________________________
| pow (base: 3) | pow (base: 3)
| (#1) (exp: 4) | (#1) (exp: 4)
| | (productSoFar: 1)
| need: pow(3, 3) | need: pow(3, 3, 3)
|_________________ |_________________________
| pow (base: 3) | pow (base: 3)
| (#2) (exp: 3) | (#2) (exp: 3)
| | (productSoFar: 3)
| need: pow(3, 2) | need: pow(3, 2, 9)
|____________ |__________________________
| pow (base: 3) | pow (base: 3)
| (#3) (exp: 2) | (#3) (exp: 2)
| | (productSoFar: 9)
| need: pow(3, 1) | need: pow(3, 1, 27)
|____________ |__________________________
| pow (base: 3) | pow (base: 3)
| (#4) (exp: 1) | (#4) (exp: 1)
| | (productSoFar: 27)
| need: pow(3, 0) | need: pow(3, 0, 81)
|____________ |__________________________
| pow (base: 3) | pow (base: 3)
| (#5) (exp: 0) | (#5) (exp: 0)
| | (productSoFar: 81)
| base case! | base case!
|____________ |___________________________

Next, we can indicate what gets returned as the method calls above return one by one (the base
case, of course, is the first method to return, then call #4, then call #3, then call #2, and finally
call #1). Note that in Implementation #1, we have to do the multiplications as we complete each
recursive call, since the else case for Implementation #1 was:

return base * pow(base, exp-1);

but in Implementation #2, we already did the needed multiplication before making the recursive
call, since the else case for Implementation #2 was:

return pow(base, exp - 1, base * productSoFar);

and so for Implementation #2 we are not doing any calculations as we return, but merely returning
the same value, 81, that we’ve already calculated as our solution:
362

Implementation #1 Implementation #2
-------------------- --------------------------
_________________ ________________________
| main | main
| int x; | int x;
| x = pow(3, 4); | x = pow(3, 4, 1);
| // once pow(3, 4) returns, | // once pow(3, 4, 1) returns,
| // 81 is stored in x | // 81 is stored in x
|________________ |_________________________
| pow (base: 3) | pow (base: 3)
| (#1) (exp: 4) | (#1) (exp: 4)
| ------> return 81 | (productSoFar: 1)
| need: pow(3, 3) | need: pow(3, 3, 3) ----> return 81
|_________________ |_________________________
| pow (base: 3) | pow (base: 3)
| (#2) (exp: 3) | (#2) (exp: 3)
| ------> return 27 | (productSoFar: 3)
| need: pow(3, 2) | need: pow(3, 2, 9) ----> return 81
|____________ |__________________________
| pow (base: 3) | pow (base: 3)
| (#3) (exp: 2) | (#3) (exp: 2)
| ------> return 9 | (productSoFar: 9)
| need: pow(3, 1) | need: pow(3, 1, 27) ----> return 81
|____________ |__________________________
| pow (base: 3) | pow (base: 3)
| (#4) (exp: 1) | (#4) (exp: 1)
| ------> return 3 | (productSoFar: 27)
| need: pow(3, 0) | need: pow(3, 0, 81) ----> return 81
|____________ |__________________________
| pow (base: 3) | pow (base: 3)
| (#5) (exp: 0) | (#5) (exp: 0)
| ------> return 1 | (productSoFar: 81)
| base case! | base case! ----> return 81
|____________ |___________________________
363

To convert this to a loop implementation, let’s first put the accumulator-recursive code into the
layout we mentioned in the last lecture packet:

recursiveMethod(params)
{
if (recursiveCase)
{
code we run before recursive call
recursiveMethod(args);
}
else // base case
{
base case code
}
}

That is, convert this:

public static int pow(int base, int exp, int productSoFar)


{
if (exp == 0)
return productSoFar;
else
return pow(base, exp - 1, base * productSoFar);
}

to this:

public static int pow(int base, int exp, int productSoFar)


{
if (exp > 0)
return pow(base, exp - 1, base * productSoFar);
else // exp == 0
return productSoFar;
}

by switching the if and else cases. Now, we can use our general tail-recursion-to-loop-implementation
technique to convert this into a loop implementation:
364

public static int pow(int base, int exp, int productSoFar)


{
while (exp > 0)
{
// There was code before recursive call.
// The statement
// return pow(base, exp - 1, base * productSoFar);
// is converted into the assignments below:
base = base;
exp = exp - 1;
productSoFar = base * productSoFar;
}
return productSoFar;
}

We don’t need to assign base to itself, so we can remove that assignment:

public static int pow(int base, int exp, int productSoFar)


{
while (exp > 0)
{
exp = exp - 1;
productSoFar = base * productSoFar;
}
return productSoFar;
}

And with that, we have a working loop implementation! Of course, we are still demanding that
the clients pass in a 1 as the third argument; we could make productSoFar a local variable, if we
wanted, in order to avoid making the clients pass in an initial value for that variable:

public static int pow(int base, int exp)


{
int productSoFar = 1;
while (exp > 0)
{
exp = exp - 1;
productSoFar = base * productSoFar;
}
return productSoFar;
}

Now, there is an important thing to take from this example – all three of our implementations
are implementing the exact same algorithm. In all three cases, we calculate an exponentiation by
multiplying the base, by an existing exponentiation with the same base and one smaller exp. And
as a result, in terms of everything but method calls, they all do the exact same work.
Consider the calculation of 34 , for example. Whether you look at the mathematical formula, or
any of the three code examples (the forward-recursive version, the accumulator-recursive version,
or the loop version), we always need to check the exponent to see if it’s zero or not, and if not, then
365

the recursive subproblem will have an exponent one lower in value. And so we will compare the
exponent to zero a total of five times – when the exponent is 4, when it is 3, when it is 2, when it
is 1, and when it is 0. Whether you look at the pure math, or any of the code examples, you will
need five comparisons as you go through the calculation.
Furthermore, for four of those cases – all the recursive cases – you will need to do a subtraction
(to get an exponent one lower than your current exponent) and a multiplication (to multiply the
base by some existing product). The base case doesn’t perform the multiplication or the subtraction,
but the recursive case does, so if you run the recursive case four times (when the exponent is 4,
when it is 3, when it is 2, and when it is 1), you will need to perform four subtractions and four
multiplications.
Go ahead and trace through the calculation of 34 using the pure math, and then each of the
three coded implementations. You will see that each time, we are performing five comparisons,
four multiplications, and four subtractions. It’s the same algorithm each time, so the same work is
being done each time. The same was true of the factorial algorithm earlier in this notes packet.
The one big difference is that the recursive versions use the extra assistance of method calls to
keep track of the values of the base and exponent, and the non-forward-recursive versions use an
extra variable to store a temporary product. But the basic computation is the same as we move
from recursive code to loop-based code. The fundamental calculations needed – the comparisons,
subtractions, and multiplications – don’t change no matter how we code the algorithm. If we had
a different algorithm, though, that could change – because then we’d be changing the fundamental
computational process we were using, and not just changing the way in which we coded it. We will
explore that idea, a bit later in the semester.
366

Lecture 30 : More Accumulator Recursion


In this packet, we present some more examples of accumulator recursion. Our first example will be
finding the minimum of a subarray.

// Implementation #1 of the findMinimum algorithm


// This is the algorithm in forward-recursion form
public static int findMinimum(int[] arr, int lo, int hi)
{
if (lo == hi)
return lo;
else
{
int locOfMinOfRest = findMinimum(arr, lo + 1, hi);
if (arr[lo] <= arr[locOfMinOfRest])
return lo;
else
return locOfMinOfRest;
}
}

Since it is the comparison we are doing after the recursive call, it is the comparison we want to
move before the recursive call. That is, we want our recursive case to be set up like this:

public static int findMinimum(int[] arr, int lo, int hi)


{
if (base case)
// do something
else
{
// compare arr[lo] to something

int locOfMinOfRest = findMinimum(arr, lo + 1, hi);


}
}

If we had a parameter that held our “index of the smallest value we’ve seen so far”, we could
compare to that, instead of the recursive result. (We don’t know what this parameter would be
initialized to, yet, but we’ll talk about that in a moment.) Note that the following code does not
correctly find the minimum! – we are now in the middle of modifying the code, and it won’t be
correct again until we are finished.
367

public static int findMinimum(int[] arr, int lo, int hi, int indexOfMinSoFar)
{
if (base case)
// do something
else
{
if (arr[lo] <= arr[indexOfMinSoFar])
// do something with lo
else // arr[lo] > arr[indexOfMinSoFar]
// do something with indexOfMinSoFar
int locOfMinOfRest = findMinimum(arr, lo + 1, hi);
}
}

Now, we need to actually do something in this conditional we have written. What we can do, is send
either lo or indexOfMinSoFar to the recursive call, as the argument for the new indexOfMinSoFar
parameter we have added:

public static int findMinimum(int[] arr, int lo, int hi, int indexOfMinSoFar)
{
if (base case)
// do something
else
{
int latestMinIndex;
if (arr[lo] <= arr[indexOfMinSoFar])
latestMinIndex = lo;
else // arr[lo] > arr[indexOfMinSoFar]
latestMinIndex = indexOfMinSoFar;

return findMinimum(arr, lo + 1, hi, latestMinIndex);


}
}

We are performing the comparison, and then passing the index of the minimum value to the next
recursive call. That call, in turn, will take that parameter index, and compare the value at that
index – the minimum to far – to that recursive call’s arr[lo]. The index of the smaller of those
two values, will then be passed along to the next recursive call. And so on. At the very end of this
procedure – i.e., when we reach the base case of the recursive method – we have only the last value
left. That is, lo == hi. In our forward-recursive method, we simply returned this value, since if
we had only one value, that value had to be the minimum. But now, when lo == hi, we have not
just the value at arr[lo], but also, the value at the parameter index indexOfMinSoFar. So, as our
base case work, we should compare those two values – the overall minimum would be the minimum
of those two values:
368

// Implementation #2 of the findMinimum algorithm


// This is the algorithm in accumulator-recursion form
public static int findMinimum(int[] arr, int lo, int hi, int indexOfMinSoFar)
{
if (lo == hi)
{
if (arr[lo] <= arr[indexOfMinSoFar)
return lo;
else
return indexOfMinSoFar;
}
else
{
int latestMinIndex;
if (arr[lo] <= arr[indexOfMinSoFar])
latestMinIndex = lo;
else // arr[lo] > arr[indexOfMinSoFar]
latestMinIndex = indexOfMinSoFar;

return findMinimum(arr, lo + 1, hi, latestMinIndex);


}
}

To begin the recursion, it’s necessary to have an initial value to send to indexOfMinSoFar. Pre-
sumably, our first comparison would be between arr[lo] and arr[lo + 1], so let’s send arr[lo]
as the initial value, and actually have the first recursive call run from lo + 1 through hi. That is,
we could have a wrapper method as follows:

public static int findMinimum(int[] arr) // assume arr.length >= 2


{
return findMinimum(arr, 1, arr.length - 1, 0);
}

If we’d like the wrapper method to handle the arr.length == 1 case as well, we could do this:

public static int findMinimum(int[] arr) // assume arr.length >= 1


{
if (arr.length == 1)
return 0; // only index that exists; must be the minimum
else
return findMinimum(arr, 1, arr.length - 1, 0); // works for arr.length >= 2
}
369

Now, we can convert our accumulator-recursive version into a loop-based version:

public static int findMinimum(int[] arr, int lo, int hi, int indexOfMinSoFar)
{
while (lo < hi)
{
int latestMinIndex;
if (arr[lo] <= arr[indexOfMinSoFar])
latestMinIndex = lo;
else // arr[lo] > arr[indexOfMinSoFar]
latestMinIndex = indexOfMinSoFar;

arr = arr;
lo = lo + 1;
hi = hi;
indexOfMinSoFar = latestMinIndex;
}
if (arr[lo] <= arr[indexOfMinSoFar)
return lo;
else
return indexOfMinSoFar;
}

Next, we can eliminate the latestMinIndex variable if we want:

public static int findMinimum(int[] arr, int lo, int hi, int indexOfMinSoFar)
{
while (lo < hi)
{
if (arr[lo] <= arr[indexOfMinSoFar])
indexOfMinSoFar = lo;
else // arr[lo] > arr[indexOfMinSoFar]
indexOfMinSoFar = indexOfMinSoFar;

arr = arr;
lo = lo + 1;
hi = hi;
indexOfMinSoFar = indexOfMinSoFar;
}
if (arr[lo] <= arr[indexOfMinSoFar)
return lo;
else
return indexOfMinSoFar;
}

and after removing the redundant assignments, we have:


370

// Implementation #3 of the findMinimum algorithm


// This is the algorithm in loop-based form
public static int findMinimum(int[] arr, int lo, int hi, int indexOfMinSoFar)
{
while (lo < hi)
{
if (arr[lo] <= arr[indexOfMinSoFar])
indexOfMinSoFar = lo;
lo = lo + 1;
}
if (arr[lo] <= arr[indexOfMinSoFar)
return lo;
else
return indexOfMinSoFar;
}

Finally, if we want, we can combine this with the wrapper method, by converting the last three
parameters to local variables:

public static int findMinimum(int[] arr)


{
int lo = 1;
int hi = arr.length - 1;
int indexOfMinSoFar = 0;
while (lo < hi)
{
if (arr[lo] <= arr[indexOfMinSoFar])
indexOfMinSoFar = lo;
lo = lo + 1;
}
if (arr[lo] <= arr[indexOfMinSoFar)
return lo;
else
return indexOfMinSoFar;
}
371

Next, we will do the same for insertionSort. Remember that we had two ways we could
approach insertionSort; first, we could have the recursive call run on lo...hi - 1, in which
case, insertInOrder assumes the sorted section is to the left:

public static void insertInOrder(int[] arr, int lo, int hi)


{
if ((lo < hi) && (arr[hi] < arr[hi - 1]))
{
swap(arr, hi - 1, hi); // 1) swap last two values
insertInOrder(arr, lo, hi - 1); // 2) run the subproblem
}
}

public static void insertionSort(int[] arr, int lo, int hi)


{
if (lo < hi)
{
insertionSort(arr, lo, hi - 1);
insertInOrder(arr, lo, hi);
}
}

But we also had a version that runs the recursive call on lo + 1...hi, and for which insertInOrder
assumes the sorted section is to the right:

public static void insertInOrder(int[] arr, int lo, int hi)


{
if ((lo < hi) && (arr[lo] > arr[lo + 1]))
{
swap(arr, lo + 1, lo); // 1) swap first two values
insertInOrder(arr, lo + 1, hi); // 2) run the subproblem
}
}

public static void insertionSort(int[] arr, int lo, int hi)


{
if (lo < hi)
{
insertionSort(arr, lo + 1, hi);
insertInOrder(arr, lo, hi);
}
}

Both of these are “insertion sort”, just from different directions. That said, of the two recursive
versions, the first is the more common one. We will convert that into an accumulator-recursive
version, and then into a loop.
372

We start by inserting arr[hi] somewhere other than into the sorted result of the recursive call –
i.e. we move the insertInOrder idea from being after the insertionSort recursive call, to before
the insertionSort recursive call:

public static void insertionSort(int[] arr, int lo, int hi)


{
if (lo < hi)
{
// insert arr[hi] somewhere else, since now we haven’t
// sorted the range lo...hi-1 yet
insertionSort(arr, lo, hi - 1);
}
else
// base case; do something here
}

Where can we insert arr[hi]? Well, as with any accumulator recursion, we’d need an extra
parameter of some kind. In this case, that parameter can be some other sorted collection. (Once
again, it’s not until our code is finished that it will actually work; our intermediate steps will just
be pseudocode.)

public static void insertionSort(int[] arr, int lo, int hi, sortedCollection)
{
if (lo < hi)
{
// insert arr[hi] into sortedCollection
insertionSort(arr, lo, hi - 1, sortedCollection);
}
else
// base case; do something here
}

When we reach just one value left (i.e. lo == hi), we will still have work to do. In the forward-
recursive version, we had only one value, and that was already sorted, so we just returned. However,
now we have that one value, plus the sorted collection that we sent along as an argument to our
new last parameter. So, what we need to do is combine our final value, with the collection we have
as a parameter. We could do this as follows:

• insert last value into sorted collection as well, creating a sorted collection of all the values
we’ve been worried about throughout all the recursive calls

• replace the entire original subarray, with the sorted collection into which we just inserted, as
the sorted collection is now the entire array that we want
373

So, we basically have this setup:

// pseudocode of recursive method:


public static void insertionSort(int[] arr, int lo, int hi, sortedCollection)
{
if (lo < hi)
{
// insert arr[hi] into sortedCollection
insertionSort(arr, lo, hi - 1, sortedCollection);
}
else // base case
{
// insert arr[hi] (which is the same as arr[lo]) into sorted collection
// make sorted collection our actual answer, i.e. copy it into this
// array somehow
}
}

// pseudocode of wrapper method:


public static void insertionSort(int[] arr)
{
insertionSort(arr, 0, arr.length - 1, emptyCollection);
}

Our initial value to the new parameter, will be an empty collection. Then, as we make our recursive
calls, we will insert arr[hi] into that collection, and then arr[hi - 1], and then arr[hi - 2],
and so on.
The last question to resolve is how to store this parameter collection and eventually copy it
back to our actual array. It might seem that we need an external array to store this collection, but
in fact, we can use the same array we are already manipulating. At any point in the algorithm, we
have a range lo...hi, where hi is less than or equal to our original value of hi:

original subarray:

<------------------------------------------>
--------------------------------------------
lo hi

after many recursive calls:

<--------------------> ????????????????????
--------------------------------------------
lo current original
value of value of
hi hi

The area marked by question marks still exists! We’ve just decremented hi enough via our various
recursive calls, that our current insertionSort call doesn’t cover that area. But those cells are
374

still there. So, why not use that area for our external collection? That is, we’ve already sent arr
as a parameter; let’s send our external collection simply by sending the index range of the “other”
part of arr, the part that this recursive call isn’t manipulating, but that the original recursive call
was manipulating?

extraLo extraHi
<--------------------> ????????????????????
--------------------------------------------
lo current original
value of value of
hi hi

Then, if we insert arr[hi] into this collection to the right, then the size of that collection grows
by one, as the size of the subarray our recursive call is manipulating, shrinks by one:

extraLo extraHi
<----------------> ????????????????????????
--------------------------------------------
lo current original
value of value of
hi hi

As our algorithm proceeds, lo and extraHi won’t change, hi gets decremented once with each
recursive call, and extraLo should also be decremented once with each recursive call, since it should
always be the next cell over from hi.
That gives us our final accumulator-recursive version, which we see on the next page. The
insertInOrder version that we use is the one that assumes the sorted range is on the right – since
when we attempt to insert arr[hi] into the range indicated by question marks above, that range
is to the right of arr[hi].
375

// wrapper method
// the last two arguments are the lo and hi, respectively, of our
// extra collection -- which are stored in the parameters extraLo and
// extraHi in our recursive code. Since this extra collection should be
// empty to start with, we have extraLo > extraHi
public static void insertionSort(int[] arr)
{
insertionSort(arr, 0, arr.length - 1, arr.length, arr.length - 1);
}

// this version of insertInOrder inserts the value at arr[lo]


// into the sorted collection in lo+1...hi
public static void insertInOrder(int[] arr, int lo, int hi)
{
if ((lo < hi) && (arr[lo] > arr[lo + 1]))
{
swap(arr, lo + 1, lo); // 1) swap first two values
insertInOrder(arr, lo + 1, hi); // 2) run the subproblem
}
}

// Implementation #2 of the insertionSort lgorithm


// This is the algorithm in accumulator-recursive form
// extraLo will always be one greater than hi; technically, we can
// eliminate extraLo since it’s not actually used anywhere
public static void insertionSort(int[] arr, int lo, int hi, int extraLo, int extraHi)
{
if (lo < hi)
{
insertInOrder(arr, hi, extraHi); // arr[hi] is inserted into the
// range hi+1...extraHi, i.e.
// into the range extraLo...extraHi
insertionSort(arr, lo, hi - 1, extraLo - 1, extraHi);
}
else // base case
{
insertInOrder(arr, hi, extraHi);
// now the entire range lo...extraHi is sorted and that’s the
// original range our array was supposed to sort
}
}
376

Converting this to a loop is then just a matter of using our usual technique:

public static void insertionSort(int[] arr, int lo, int hi, int extraLo, int extraHi)
{
while (lo < hi)
{
insertInOrder(arr, hi, extraHi);
arr = arr;
lo = lo;
hi = hi - 1;
extraLo = extraLo - 1;
extraHi = extraHi;
}
else // base case
{
insertInOrder(arr, hi, extraHi);
// now the entire range lo...extraHi is sorted and that’s the
// original range our array was supposed to sort
}
}

and then eliminating the redundant assignments,

public static void insertionSort(int[] arr, int lo, int hi, int extraLo, int extraHi)
{
while (lo < hi)
{
insertInOrder(arr, hi, extraHi);
hi = hi - 1;
extraLo = extraLo - 1;
}
else // base case
{
insertInOrder(arr, hi, extraHi);
// now the entire range lo...extraHi is sorted and that’s the
// original range our array was supposed to sort
}
}
377

Note that extraLo isn’t actually ever used anywhere, so we can get rid of that, too (we also could
have gotten rid of it in the accumulator-recursive version):

// Implementation #3 of the insertionSort lgorithm


// This is the algorithm in loop-based form
public static void insertionSort(int[] arr, int lo, int hi, int extraHi)
{
while (lo < hi)
{
insertInOrder(arr, hi, extraHi);
hi = hi - 1;
}
else // base case
{
insertInOrder(arr, hi, extraHi);
// now the entire range lo...extraHi is sorted and that’s the
// original range our array was supposed to sort
}
}

And you can see that the loop version, is basically just doing the same work that our “second”
version of insertion sort did. The version of insertion sort where the recursive call was on the range
lo+1...hi would, as the recursive calls returned, be sorting the right-hand side of the array. That’s
what our loop version does above.
And similarly, if you went through this process on that “second” version of insertion sort, you’d
get a loop version that sorted the left-hand side of the array, just as our “first” version of insertion
sort did on the return from the recursive call on the range lo...hi-1.
Or in other words, converting insertion sort from forward recursion to loop, reverses the direction
of the sorting. The forward-recursive version that sorts left-to-right, becomes a loop version that
sorts right-to-left, and the forward-recursive version that sorts right-to-left, becomes a loop version
that sorts left-to-right. All four implementations are “insertion sort”, though for both the forward-
recursive and the loop-based versions, we tend to more commonly see the left-to-right code, than
the right-to-left code.
378
Part 4 : Algorithm Analysis

Generally, it is not enough to simply have correct code. For example, if you wanted a program to
print the numbers from 1 to n, the following code would work:

for (int i = 1; i <= n; i++) {


int j = 1;
while (j < i)
j++;
System.out.println(j);
}

but it is very wasteful. Why write a loop to count j upwards from 1 to i, when you can just use i
and avoid the inner loop entirely?
In that case, part of the code is an outright waste, so it’s easier to realize that the above code is
not the best possible code. A much more difficult situation to think through, is a situation where
you have two well-written pieces of code that do the exact same thing in very different ways. For
example, we have seen two algorithms, selection sort and insertion sort, that both sort a subarray
of integers. Which algorithm is “faster”? Even if we code both algorithms as well as we possibly
can, they approach the problem in such different ways that it can be hard to compare them and
decide which algorithm is faster just by a quick glance. Deeper thought about the two algorithms
is needed.
In addition, it could perhaps be the case that both algorithms are good choices, just in different
circumstances. Perhaps one runs faster on large arrays, and the other one is faster for small arrays,
and thus the size of the array might determine your choice of algorithm. Or perhaps one algorithm
is faster but uses a lot of memory as it runs, and the other algorithm is slower but doesn’t use
much memory at all, and thus how much memory you have available might determine whether you
can afford to use the faster algorithm or not. We want to reason through various algorithms, and
be able to come up with some measurement of how much time the algorithm will take, and how
much memory the algorithm will use, so that we can compare two different algorithms in the ways
we described above.
In addition, ideally, these measurements would not depend on the particular machine, program-
ming language, etc. that was being used, since if an algorithm is the best choice on today’s machine,
it’s probably also the best choice if you get a machine that is 15% faster. What we want are results
that describe the actual mathematical properties of the algorithms – and ways of measuring the
algorithms so that we can obtain those results. For example, on the first day of the course, we
figured out that linear search on a collection of size n, would potentially need to look at all n values.
That’s something that’s true no matter what machine/language/OS you are using, and thus that’s
one of the results we would like to obtain about linear search and other such algorithms. That is
the topic of this fourth part of the course.

379
380

Lectures 31 and 32 : Introduction to Algorithm Analysis


In many situations, there are a number of algorithms we could use to solve a problem, and so we
need to choose which one to use. This choice could be based on a number of factors – for example,
the speed of the algorithm, how much memory it uses, or how much data we need to process. For
any given situation, some of those factors might be more important to us than others. Therefore,
we need a way to describe the time, speed, etc. requirements of an algorithm, so that we can easily
compare algorithms and choose the one that is best for our situation.
For the moment, let’s consider comparing the speed of two algorithms. If we have two algo-
rithms, A and B, for solving the same problem, and if algorithm A is faster than algorithm B, then
that is one possible reason for choosing algorithm A over algorithm B. The problem comes when
we try to define what “faster” means. One thing we could do is to take two different machines, and
run algorithm A on one machine, and run algorithm B on the second machine on the same data,
and time the algorithms with a stopwatch. If we do that, perhaps the following are the results we
get:
Algorithm A Algorithm B
on machine 1 on machine 2
------------- ------------
92.7 seconds 20.8 seconds
Now, at first glance, it might seem like Algorithm B is much faster. However, if we are going
to compare algorithms in this manner, we want to compare them in identical environments. For
example, maybe machine 1 is a seven-year-old machine, and once we run both algorithms on machine
2, we get the following results:
Algorithm A Algorithm B
------------- ------------
10.4 seconds 20.8 seconds
Now, the comparison is a bit more appropriate, since the two algorithms are running on the
same machine, the same operating system, etc.
However, things like processor speed and compiler optimizations (i.e. how good the translation
from high-level code to machine code was) can affect this result, and those things really don’t have
anything to do with the abstract description of an algorithm. So, we’d prefer a more mathemat-
ical way of comparing two algorithms, so that we can focus on the key details of the algorithm
without considering what platform things are running on, what compiler was used, or other such
implementation details that have nothing to do with the inherent properties of the actual solution
description itself. Even if the machine, operating system, compiler, etc. were the same for the
tests of algorithm A and B above, we could still get different results if we used a different compiler,
different machine, and different operating system. Above, algorithm B appears to take twice as
long to run as does algorithm A. Perhaps if a different machine were used, the gap would not be
so large. Or perhaps it would be larger.
We also have to take into account data size. Knowing how fast we can sort an array of 10
elements isn’t particularly useful information, because we aren’t always going to be sorting arrays
as small as 10 elements. On the other hand, if we can describe the running time as a function of a
general number of elements, n, then that is more useful. If we increase our data size from n to 2n,
then our running time function will give us the new running time if we substitute 2n for n in the
function.
So, perhaps the data above was for a set of 100 data items:
381

data size Algorithm A Algorithm B


----------- ------------- ------------
100 elements 10.4 seconds 20.8 seconds

Perhaps if we run the same two algorithms (again, using the same machine, same operating system,
same compiler, etc.) but this time we have 200 data items instead of just 100, perhaps we would
get the following results:

data size Algorithm A Algorithm B


----------- ------------- ------------
100 elements 10.4 seconds 20.8 seconds
200 elements 41.6 seconds 41.6 seconds

And with a few more data sizes, we might get:

data size Algorithm A Algorithm B


----------- ------------- ------------
50 elements 2.6 seconds 10.4 seconds
100 elements 10.4 seconds 20.8 seconds
200 elements 41.6 seconds 41.6 seconds
400 elements 166.4 seconds 83.2 seconds

Now, this is not unrealistic data; we certainly could have two algorithms that exhibit this perfor-
mance for the given data sizes. So the question is, why do we get this strange behavior? It seems
that for some data sizes, algorithm A takes longer, but for some other sizes algorithm B takes
longer. But actually, this behavior is not strange at all, and it illustrates the important concept
we want to discuss today. Look closely at the numbers in the column for algorithm B. As the data
size doubles each time – from 50, to 100, to 200, to 400 – the time the algorithm needs to run also
doubles – from 10.4, to 20.8, to 41.6, to 83.2. Every time the data set doubles, the running time of
the algorithm also doubles.
Now, look at the numbers for algorithm A. Every time the data size is doubled, the running
time of algorithm A increases by a factor of 4.
If you were to graph the performance of these two algorithms as a function of the number of
data elements, you would get a picture with a parabola (for Algorithm A) and a straight line (for
algorithm B). That is, we would get a quadratic function and a linear function.
That is the quality we are after. What we are discussing here is the notion of order of growth
– we want to know not how fast an algorithm is on one data set, but rather, how quickly the
performance degrades as we increase the size of the data set. (We can make a similar analysis for
memory usage.) If your time vs. data size graph is a quadratic function, as with Algorithm A, then
it means every time you increase the data size by a factor of k, you increase the running time by
a factor of k 2 . But if your time vs. data size graph is a linear function, as with algorithm B, then
it means every time you increase the data size by a factor of k, the running time also increases by
a factor of k. This would suggest that, as our data size gets larger and larger, the running time
of the algorithm with a quadratic order of growth, would get larger much faster than the running
time of the algorithm with a linear order of growth – and indeed, we can see that exact affect in
our data above. Eventually, for large enough data sets, every algorithm with a running time whose
order of growth is quadratic, will start to take longer than an algorithm with a running time whose
order of growth is linear.
382

So, we will start by considering operations that do not depend on n. Meaning, some parts of a
function take exactly the same amount of time whether n is 10 or 1000 or 1 million. For example:
• Declaring a variable would be such an operation. Now, if we declare 100 variables, certainly
that will take longer than if we declare 1, but if (for example) we are about to search an
array of elements using a for-loop, whether we are going to search 100 elements or 1 million
elements, declaring a single variable i to use to run the for-loop iteration will take the same
amount of time either way.

• A basic operation such as comparison, addition or subtraction, a logical operation, or assign-


ment would be such an operation For example, the time it takes to write one object location
to a reference variable (i.e. assignment) does not vary based on how many assignments you
eventually do. The time to do one addition doesn’t suddenly increase because you plan on
doing 10000 additions instead of 100.

• Array access is such an operation as well. At first, you might not think so. It would seem
that to get to cell (for example) 10, the machine first needs to move through cells 0 through
9. Therefore, it would seem that accessing a later cell takes longer than accessing an earlier
one.
But, actually, this is not the case. Arrays can given you (nearly) instant access to any cell
because in memory the cells are arranged one after the other. So, it is possible to hold the
starting address inside the array reference, and then use the calculation

cell address = starting address + index * typesize

to determine the starting address of the cell you want. For example, we said that variables
of type int take up 32 bits. So, if we allocate an array of 10 ints, those ten cells take up
ten consecutive 32-bit cells in memory. The first cell starts at some address in memory – the
starting address of the array – and then the other cells are located immediately after it, one
by one. But, if you use the calculation above to get the address of the first cell, you would
get:

address of A[0] = (starting address of A) + 0 * 32 bits


= (starting address of A) + 0
= starting address of A

and we just said above that the first cell was located at the start of the array. Likewise,

address of A[2] = (starting address of A) + 2 * 32 bits


= (starting address of A) + 64

And, if A[0] is located at the starting address of the array and takes up 32 bits, it would
follow that at bit 33 of the array, we would see the start of A[1]. And since that takes up
another 32 bits, it would follow that at bit 65 of array, we would see the start of A[2]. And
that is exactly what our calculation above tells us – that 64 bits after the starting address,
A[2] begins.
In other words, we can jump immediately to cell 2 by doing one multiplication and one
addition. And in general, we can get to any cell in the array via one multiplication and one
383

addition. This is because array cells appear consecutively in memory, and so if you want
A[i], that means (because we start indexing at 0) that you have to skip over i cells at (in
this case) 32 bits each, and so one multiplication tells you how many total bits to skip over,
and one addition of that bit total to the starting address tells you where the start of A[i]
must therefore be in memory. You do not need to traverse down the cells one by one – a
single multiplication and addition will give you your array cell each time, no matter how large
the array is and no matter which particular index you are looking up. So, array cell access
time does not depend on the size of the array in any way.

Operations like those above are said to take constant time, because their running time is the same
constant value regardless of the data size. Similarly, we can say that algorithms take constant
memory if the amount of memory the algorithm uses, does not increase as the data size increases.
In general, if the amount of a resource (time, memory, whatever) that is used, is unaffected by the
growth of the data size, then we say that the order of growth of that resource usage is constant.
Similarly:

• If the increase in the usage of a resource grows proportionally to the increase in the size of
the data set, then we say that the usage of that resource by the algorithm has an order of
growth that is linear.
A linear-time algorithm is one that grows linearly as the amount of data to process grows.
For example, a loop that counts down from n to 0 in steps of 1 would be linear – each loop
pass subtracts 1 from n, so you’ll need n of those loop passes to reduce the number to 0. And
the substraction is constant-time, so you are performing constant-time work n times. The
math function c*n would be a linear function (i.e. a straight line).
That’s one way to recognize a linear function – if you are doing a constant amount of work
for each step, and the number of steps you do is proportional to n. Or in other words, you are
eliminating a constant amount of data with each constant step. Printing an array is another
example – each step, do you a constant-time amount of work to print one cell, so if you have
n cells, you need to do that constant-time amount of work n times.
So, another way to recognize a linear algorithm is to double the amount of data, or triple
it, or quadruple it. What happens to the running time? If it likewise doubles, or triples, or
quadruples, respectively, then you have a linear algorithm. This is because since you perform
constant-time work to process a constant amount of the information, if you double the amount
of information, you will be doubling the amount of times you have to run that constant-time
work.

• The order of growth of an algorithm’s usage of a particular resource is said to be quadratic


if, when the data size grows by some factor, the resource usage grows by the square of that
factor, rather than growing by that same factor as a linear algorithm would do. An example
is printing an n row, n column array to the screen. That is, if n is the number of rows of the
array and # rows == # cols, then if n doubles, we have four times as many cells and thus
four times as much printing work to do. If n triples, we’ll have 9 times as much printing work
to do.
Another way to view this is that you do a linear amount of work (such as printing an entire
row of an array) to eliminate a constant amount of our count (one row of n).
If a quadratic-time algorithm has it’s running time graphed as a function of the data size,
the graph will be a parabola – i.e. a quadratic function.
384

• The order of growth of an algorithm’s usage of a particular resource is said to be logarithmic


if an increase in the data size by some factor, increases the resource usage by the log of that
factor. For example, in the case of logarithmic running time, each constant amount of work
eliminates an entire fraction of the data, so if you double the amount of work, you are adding
only one more operation total – i.e. adding constant time to the running time (because log-
base-2 of 2, is 1). An example is binary search – you compare A[mid] to the key, first for
equality and then to see if the key is less than A[mid] – and those two comparisons together
eliminate at least half the array from having to be examined. So the next step is on at most
half the original array. And the next step is on at most half of *that* subarray, which is a
fourth of the original array. And so on. The number of steps needed to reduce the subarray
are dealing with to size 1 is going to be a logarithmic number of steps, and if you graph a
logarithmic-time algorithm’s running time as the size of the data varies, you get the graph of
a logarithmic function.

We aren’t very concerned with the lower order terms, and constant factors, and such, here.
4.332n and 1.232n+2 are still both linear functions. 0.5 (n squared) and 213.234 (n squared) + 3n
- 2 are still both quadratic functions. In fact, you can even take a quadratic function and see how
little the lower order terms start to matter as n increases:

n n^2 + 2n + 1 n^2 / (n^2 + 2n + 1)


------ ------------- --------------------
10 121 .826446
100 10,201 .980296
1000 1,002,001 .998003
10000 100,020,001 .999800
100000 10,000,200,001 .999980

The column at the far right measures what percentage the quadratic term is, as a total of the entire
expression. As you can see, even when n is 100, the quadratic term accounts for almost the entire
value of the expression. This result just gets more prominent the larger n gets. So, we don’t have to
concern ourselves too much with the particular details of the function. All we really care about is
whether the function is linear or quadratic or such. (You’ll learn some formal notation for ignoring
the lower-order terms in CS173.)
This information is useful because we can use it to learn what kind of effect increasing our data
set is likely to have. If it is taking me about an hour to search an array for an element (it’s a
big array), then I know trying to search an array about twice the size should take me about two
hours. Trying to search an array four times the size should take me about four hours. But this
has nothing to do with the operating system I am using, it has nothing to do with the processor in
my machine, and it has nothing to do with the compiler I used. The fact that the running time is
linear in the size of the array is a fundamental mathematical property of the algorithm!!.
That means our analysis is completely platform-and-software-tool independent. That is the best
way to compare algorithms, because it helps us choose between algorithms when we are still in the
process of designing our program, and because the decisions made based on such a comparison don’t
change when a new processor comes along because the result of the comparison of two algorithms
using mathematical analysis is completely independent of the processor you used. The algorithm
that is mathematically faster today will still be mathematically faster next year when processors
have doubled in speed.
Up to this point, the analysis we have done is what is known as worst-case analysis. In this kind
of analysis, we are concerned with finding out how long the algorithm takes to run in the worst
385

possible situation. For example, you are designing software to pipe extra oxygen into the space
shuttle living area for our astronauts in the event that their existing oxygen all leaks out somehow,
you’d want to make sure that the algorithm you chose to make this oxygen delivery system work
was very very fast, even in the worst case. It would do you no good if it “usually” worked quickly,
but “hit a snag” in certain situations and spent an extra hour processing before piping in oxygen.
The astronauts would die waiting for your algorithm to finish!! So, in such a case it would be
important to know the absolute longest that your algorithm could take, no matter what input the
algorithm is given. That is the idea of worst-case.
On the other hand, often what we are more concerned with is what usually occurs. We might
not like using a computer system that took 10 minutes to send a file to the printer each time we
wanted to print. But, if it usually printed right away, and only took 10 minutes every once in a
long while due to some quirk in certain types of files, then we might find that acceptable. In fact,
we might prefer that to a situation where the worst case was only 5 minutes, but that was what
“usually” happened as well. Sometimes we will prefer to focus on what “usually” happens, and
pick an algorithm that “usually” runs very fast, even if occasionally we end up with a slower case
as a result.
This notion of “usual” is known as the average case, and attempts to analyze an algorithm with
the average case in mind are known as average case analysis (in contrast to worst case analysis).
Sometimes – though not usually – we also discuss the ”best case”. ”What happens in the most
ideal situation?” is what we are asking here. We usually don’t care about this case because it’s not
interesting. We don’t *care* if things work out better than we expect, but we *do* care if they
end up worse than we expect...so while we care about what the worst we can expect to see would
be, we don’t care too much about what the possible best we can expect to see it. We care about
the worst possible situation, and also what is most likely.
386

Lecture 33 : Selection Sort Analysis


Consider our selection sort code:

public static int findMinimum(int[] arr, int lo, int hi) // lo <= hi
{
if (lo == hi)
return lo;
else
{
int indexOfMinOfRest = findMinimum(arr, lo + 1, hi);
if (arr[lo] <= arr[indexOfMinOfRest])
return lo;
else
return indexOfMinOfRest;
}
}

public static void swap(int[] arr, int i, int j)


{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}

public static void selectionSort(int[] arr, int lo, int hi)


{
if (lo < hi)
{
int indexOfMin = findMinimum(arr, lo, hi);
swap(arr, lo, indexOfMin);
selectionSort(arr, lo + 1, hi);
}
}

If we imagine calling selectionSort(...) from main(...), on a subarray of size n, we would


end up with n total calls to selectionSort. This is because every time we hit the recursive case of
selectionSort, we will make another selectionSort call, on a subarray one smaller in size than
before. If our subarray is initially size n when we make our first call to selectionSort, then since
each recursive call decreases the subarray size by one, we will need to make n-1 more calls before
the subarray size has reached 1. And when the subarray size reaches 1, that is the base case and
so there would not be any additional calls to selectionSort. Thus, we have our first call (the one
from main(...), plus the n-1 more calls to get down to a subarray of size 1 – making n calls total
to selectionSort.
387

So, what work gets done by selectionSort as we go through those n calls? Well:

• You will sometimes need to evaluate the base-case condition to see if you are in a recursive
case or a base case, i.e. lo < hi

• You will sometimes need to make a call to findMinimum

• You will sometimes need to perform a swap

• You will sometimes need to add 1 to the parameter lo in preparation for a recursive call (i.e.
you’ll need to evaluate the recursive call arguments that need evaluating)

• You will sometimes start a new method, which means creating a new method notecard and
copying the argument values onto that notecard. Along with that work, is the eventual
destruction of that notecard when that method call has completed. We are not speaking
here of all the work that goes on while you are on that notecard, nor are we speaking of the
work that gets done on elsewhere whenever you leave that notecard temporarily. We are only
speaking here, of the work involved in creating the notecard, and some time later, the work
involved in permanently destroying that notecard and returning to the previous method call.

So, how often does each of those things happen?

• Each of the n calls to selectionSort(...) must check once to see if you are at the base case
or the recursive case; you’ll be at the recursive case n-1 of thoses times, and at the base case,
one of those times. But you evaluate the lo < hi condition within selectionSort(...), n
different times in all.

• You will need to make a call to findMinimum(...) every time you are in the recursive case,
so you’ll make n-1 calls to findMinimum(...) from selectionSort(...).

• You will need to make a call to swap(...) every time you are in the recursive case, so you’ll
make n-1 calls to swap(...) from selectionSort(...).

• You will need to add 1 to the parameter lo in preparation for a recursive call, every time you
are in the recursive case, so you’ll add 1 to lo, n-1 separate times from within selectionSort(...).

• And as we’ve already explained, you will start – and later return from – n separate calls to
selectionSort(...).

So, you evaluate the base-case condition and make/return from a method call, n times, and you
call findMinimum(...), call swap(...), and add 1 to lo, n-1 times.
Finally, what is the order of growth of the time the computer needs to do each one of those
steps once, as the array grows bigger and bigger?
388

• The base-case condition check: evaluating a “less than” comparison of two integers is a
simple machine instruction and thus is constant time; if the array becomes ten times as large,
it doesn’t change the time needed to compare two integers to each other once. We will call
this constant, cbase .

• Let’s ignore findMinimum(...) for just a moment; we’ll deal with that on its own after we
figure everything else out.

• A call to swap(...) consists of the overhead to start and return from one method, plus
four array accesses and three assignments. An array access is constant time, as we’ve already
discussed. So is an assignment. And so is method call overhead. So, the whole package adds
up to constant time for one swap operation. We will call this constant, cswap .

• Adding 1 to lo is a simple machine operation, i.e. a single addition implemented by a single


addition instruction. This does not take longer as the array grows larger, so it is constant
time. We will call this constant caddition .

• The method call set-up/destroy overhead will not depend on the size of the array, since no
matter how large the array is, we are only passing a single reference to the array, as an
argument. So, this cost will also be constant time. We will call this constant, cmethodcall .

And that leaves us with:

(cbase ∗ n) + (cswap ∗ (n − 1)) + (caddition ∗ (n − 1)) + (cmethodcall ∗ n)

which, after running some algebra, becomes:

(cbase + cswap + caddition + cmethodcall ) ∗ n + (cswap + caddition ) ∗ −1

If we condense our constants by creating a new constant cW to be the sum of four of these constants:

cW = cbase + cswap + caddition + cmethodcall

and another new constant cX to be the sum of two of those terms:

cX = cswap + caddition

then our algebra becomes

cW ∗ n − cX

i.e., it is a linear function.


389

And that brings us to findMinimum(...). What work gets done by findMinimum(...) on a


subarray of size n? well, there will be n-1 recursive calls and 1 base case. That said:
• Checking the lo == hi condition will happen in every one of the n method calls, and it takes
constant time. Let’s call this constant, cequality−comp .
• Every one of the n-1 recursive cases needs to add 1 to lo. A single addition, runs in constant
time. We’ll call that cadd−in−f ind−min .
• Every one of the n-1 recursive cases will need to perform an assignment to write the value of
the recursive acll into a variable. A single asignment, runs in constant time. We’ll call that
cassign .
• Every one of the n-1 recursive cases will need to compare arr[lo] to arr[indexOfMinOfRest].
That is two arrays accesses and a comparison; together, that work is still constant time, since
having more array cells won’t change the time it takes to read two array cells and perform one
comparison. Each array access is constant time, and the comparison is constant time, and
three constants added together still result in a constant. We’ll call that constant cmin−compare .
• We are actually returning a value in every case of this method. The time to evaluate the
(very simple) expression inside the return statement, and to run the return statement, is
constant; having more array cells won’t make it take longer to run one return statement.
We’ll call this constant creturn .
• We have to start, and eventually, return from n different findMinimum(...) calls. Each
such call/return cost is a constant, as we have already discussed. We’ll call this constant
cf ind−min−call .
And that gives us:

(cequality−comp ∗ n) + (cadd−in−f ind−min ∗ (n − 1)) + (cassign ∗ (n − 1)) + (cmin−compare ∗ (n − 1)) +


(creturn ∗ n) + (cf ind−min−call ∗ n)

which, after running some algebra, becomes:

(cequality−comp +cadd−in−f ind−min +cassign +cmin−compare +creturn +cf ind−min−call )∗n+(cadd−in−f ind−min +
cassign + cmin−compare ) ∗ −1

If we condense our constants by creating a new constant cY to be th e sum of these six of these
constants:

cY = cequality−comp + cadd−in−f ind−min + cassign + cmin−compare + creturn + cf ind−min−call

and another new constant cZ to be the sum of three of those terms:

cZ = cadd−in−f ind−min + cassign + cmin−compare

then our algebra becomes

cY ∗ n − cZ

i.e., it is a linear function.


390

Now, the one problem here is, even though findMinimum(...) is called once in each of the
recursive-case method calls of selectionSort(...), the “n” that the findMinimum(...) call
runs on is different each time. So to get a true assessment of the situation, we need to list the
findMinimum(...) cost of each step of selectionSort(...), and then add those costs together:

selection sort running time of findMinimum(...)


step # in this step
----------- ------------------------------
1 cY * n - cZ
2 cY * (n-1) - cZ
3 cY * (n-2) - cZ
4 cY * (n-3) - cZ
5 cY * (n-4) - cZ
6 cY * (n-5) - cZ
. .
. .
. .
k cY * (n-k+1) - cZ
. .
. .
. .
n-4 cY * 5 - cZ
n-3 cY * 4 - cZ
n-2 cY * 3 - cZ
n-1 cY * 2 - cZ

If you add up the second column, that’s the work for findMinimum(...) over the lifetime of
the selectionSort(...) algorithm. And that sum is just:

cY * (sum of numbers from 2 through n) - (n-1)*cZ

Now, it turns out that:

n + (n-1) + (n-2) + (n-3) + ... + 4 + 3 + 2 + 1 == n(n+1)/2

(You’ll probably prove this in CS173 using mathematical induction.) So, with substitution, this
becomes:

cY ∗ ((n ∗ (n + 1)/2) − 1) − (n − 1) ∗ cZ

which if simplified, becomes:

((cY /2) ∗ n2 ) + (((cY /2) − cZ ) ∗ n) + cZ

and that’s a quadratic function. Even if we add the earlier work, we get:
((cY /2) ∗ n2 ) + (((cY /2) − cZ + cW ) ∗ n) + (cZ − cX )

which is still quadratic.


391

A quick way to summarize this is:

• All the work with the exception of findMinimum(...) is constant per step, and thus linear
total.

• The findMinimum(...) result is linear per step, and thus when you add the findMinimum(...)
costs for each step together, you see that it is quadratic.

• A linear plus a quadratic is a quadratic, i.e. the non-findMinimum(...) costs, plus the
findMinimum(...) costs, add up to a quadratic function.

Normally you can just reason through the summary like that; you don’t need to always indicate
every little constant the way we did here. But it’s useful to have gone through at least one very
detailed analysis, just so you see all the constants and how they would add up.
What we just did was the worst-case analysis, but note that it is also the average-case, and even
the best-case analysis – since findMinimum(...) doesn’t run any faster when the minimum is the
first cell of the array, selectionSort(...) won’t run any faster even if you pass a perfectly-sorted
array to selectionSort(...). So, there is no difference between any of the cases. All of them
have running times whose orders of growth are quadratic.
392

Lecture 34 : Insertion Sort Analysis


Analyzing InsertionSort

The worst case here is much like SelectionSort. The primary work here is InsertInOrder –
the rest is just starting and returning from InsertionSort recursive calls, and as we saw above for
SelectionSort, that is constant time per step and thus linear overall. ¡p¿ InsertInOrder’s worst case
is when A[hi] needs to be shifted all the way to the front of the array – then *every* value needs to
be moved to a different cell, i.e. n values need to be moved. But of course, the size of the subarray
that InsertInOrder works on is different each step, so we have a table much like for FindMinimum.
Remember that the first call InsertionSort has, is the *last* time InsertInORder runs, since we
don’t run InsertInOrder in the first InsertionSort call, until we make the second InsertionSort call
and return from it:

InsertionSort # of cells InsertInOrder moves


call in worst case in this call
----------- ------------------------------
1 n
2 n-1
3 n-2
4 n-3
5 n-4
6 n-5
. .
. .
. .
k n-(k-1) == n-k+1
. .
. .
. .
n-4 5
n-3 4
n-2 3
n-1 2
n (i.e. base case) 0

So again, worst case is about (n squared)/2. On average, we might expect to have to shift A[hi]
down half the array, rather than the entire array, so the average case would involve multiplying each
of the second-column values above by one-half. So, the average case would be about (n squared)/4.
Note that for SelectionSort, the worst and average cases were the same. For InsertionSort,
though they are both quadratic, if you were to time them, the average case would take only about
half as long as the worst case.
This trend continues – if you had an already sorted array, then in InsertInOrder, A[hi] would
need to be compared to A[hi-1], but no shifting would be needed and the method would end there.
That’s just constant time...so over the life of the algorithm – n steps – we spend constant time
on each step so it’s linear total. Or in other words, on an already-sorted array, all InsertionSort
basically does is to compare A[2] to A[1], then compares A[3] to A[2], then compares A[4] to A[3],
and so on, up to comparing A[hi] to A[hi-1]. That’s n-1 comparisons (there’s no comparison in the
base case) so linear time.
393

Lecture 35 : Exponentiation and Searching


A second and third algorithm for exponentiation

The subproblem we chose for our first exponentiation algorithm – reducing the exponent by
one and keeping the base the same – is not the only subproblem that works for the exponentiation
problem. Consider what would happen if we still kept the base the same, but instead of subtracting
1 from the exponent, we divided it by 2.
For example, if we are trying to calculate 220 , how does knowing the value of 210 help us? And
the answer is:

220 = 210 ∗ 210

So, let’s try this:

x = 210
220 =x∗x

That is, instead of doing 20 multiplications to calculate 220 , let’s just do 10 multiplications to get
210 , and then square the “result so far” to get 220 . That’s only 11 multiplications total! In fact,
we could get away with even fewer multiplications, since we could calculate 210 in this manner
as well. 210 is 25 ∗ 25 , so we could perform 5 multiplications to obtain 25 , and then square it (a
6th multplication) to get 210 , and then square that result (a 7th multiplication) to get 220 . In the
implementations we were just discussing, we would have needed 15 multiplications to go from 25
to 220 , but by squaring 25 , and then squaring that square, we can go from 25 to 220 in just two
multiplications, which is much faster than using 15 multiplications to get there.
In our first exponentiation algorithm, our important recursive idea was:

baseexp = base ∗ baseexp−1

now, we will instead rely on the following recursive idea instead:

baseexp = baseexp/2 ∗ baseexp/2

Now, this doesn’t quite work for exponents that are odd numbers, if for no other reason than,
mathematically, exp/2 is not an integer if exp is odd, and we are trying to stay with integer
exponents. But our solution to this problem can be seen by considering the calculation of 221 . We
already know that 220 = 210 ∗ 210 , and we only need to multiply 220 by 2 to get 221 . So, that gives
us:

221 = 2 ∗ 210 ∗ 210

If we wanted to calcualte 222 , that would just be multiplying by 2 one more time:

222 = 2 ∗ 2 ∗ 210 ∗ 210

but that’s just the equivalent of:

222 = 211 ∗ 211


394

So, as long as we have an even exponent, we can just use the

baseexp = baseexp/2 ∗ baseexp/2

pattern, and for an odd exponent, we’ll use this pattern instead:

baseexp = base ∗ base(exp−1)/2 ∗ base(exp−1)/2

That is, we have the following algorithm as a second algorithm for calculating exponentiation,
where we express baseexp in terms of baseexp/2

// second algorithm for exponentiation


______ 1 if exp > 0
|
|
exp | exp/2 exp/2
base -----|----- base * base
|
| if exp > 0 and exp is even
|
|
| (exp-1)/2 (exp-1)/2
|_____ base * base * base

if exp > 0 and exp is odd

This is a fundamentally different way of performing the exponentiation than the previous algo-
rithm was. However, we will still perform the same number of multiplications in the end. What we
might note, though, is that in both recursive cases, we are performing the same recursive calculation
twice. If we save the result the first time, we can just use it again instead of recalculating it from
scratch. And this will give us a third algorithm for computing exponentiation:

// third algorithm for exponentiation


______ 1 if exp > 0
|
|
exp | exp/2
base -----|----- x * x, where x = base , if exp > 0 and exp is even
|
|
|
| (exp-1)/2
| base * x * x, where x = base , if exp > 0 and
|______ exp is odd

If we actually implement the above in the way the formula implies – by calculating your recursive
call once and then squaring the result – then this third algorithm will use fewer multiplications
395

than the previous algorithms did. The three code results we looked at for the first algorithm, were
all implementations of the same algorithm, and thus did the same calculation work, just organized
in different ways (some of those organizations needing many method calls, and others not needing
those calls). But this is not simply a different way to code the same work; this is a completely
different computational process in the first place. There is a difference between coding the same
algorithm in a slightly different way, and a completely different algorithm entirely. And that is why
all three code examples for algorithm 1 all performed the same number of comparisons and the
same number of multiplications and the same number of subtractions, but we are already talking
above about how we can save so many multplications using this approach, and we haven’t even
written any code yet!
Saving intermediate results is sometimes helpful, because some recursive algorithms can repeat
subproblems and we’d rather not have to perform the same calculation many times. (This saving
of intermediate results is called dynamic programming; you will explore the concept of dynamic
programming much more in a later course.)
For example, consider the algorithm above that was labelled our “second exponentiation algo-
rithm”; we can code it as follows:

public static int pow(int base, int exp) // base >= 1, exp >= 0
{
if (exp == 0)
return 1;
else // exp > 0
{
if (exp % 2 == 0)
return pow(base, exp/2) * pow(base, exp/2);
else // (exp % 2 == 1)
return pow(base, (exp - 1)/2) * pow(base, (exp - 1)/2) * base;
}
}

Note that, if exp is odd, (exp - 1) / 2 is equal to (exp / 2), leading to the following alteration
to the above code:

public static int pow(int base, int exp) // base >= 1, exp >= 0
{
if (exp == 0)
return 1;
else // exp > 0
{
if (exp % 2 == 0)
return pow(base, exp/2) * pow(base, exp/2);
else // (exp % 2 == 1)
return pow(base, exp/2) * pow(base, exp/2) * base;
}
}

In both those cases, we are calling pow twice with the same arguments, thus repeating work. If we
instead write code for our third exponentiation algorithm, we get the following:
396

public static int pow(int base, int exp) // base >= 1, exp >= 0
{
if (exp == 0)
return 1;
else
{
int intermediateResult = pow(base, exp/2);
if (exp % 2 == 0)
return intermediateResult * intermediateResult;
else
return intermediateResult * intermediateResult * base;
}
}

By saving the value of our recursive call, we avoided having to run that entire recursive calculation
a second time.
397

Binary Seach

If our linear search is unsuccessful, we will end up having searched every cell in the range lo
through hi – there is no other way to be sure every cell is unequal to the search key. However, as
we said back in lecture 1, if we have access to additional information about our collection or the
items inside it, we can sometimes use that additional information to rule out certain items without
having to inspect them.
So, we will consider the problem of having a very particular piece of extra information – knowl-
edge that the array is sorted. That is, the items in our array cells increase as we move from arr[lo]
to arr[hi] (or if we will allow duplicate items, then the items in the array are non-decreasing as
we move from arr[lo] to arr[hi]). Knowing that the array is sorted allows us to improve our
search algorithm tremendously. How? Well, pick some value in the array to examine. Obviously,
if that value turns out to be our key, then we are done! But if it is not our key, then we only need
to search one side of the value, or the other. For example, suppose the value X, at index i, is the
initial value we inspect:

_____________________________________________________________
| | | |
| values < X | X | values > X |
|________________________|_______|___________________________|
indices < i i indices > i

Everything less than X will be to the left of X, and everything greater than X will be be to the right
of X. So, if the item we are looking for is less than X, we will only need to search the subarray to
the left of X; nothing to the right of X is less than X, so the entire subarray to the right of X can be
ignored. And we can use a similar reasoning if what we are looking for is greater than X – in that
case, nothing in the subarray to the left of X can be greater than X, so the subarray to the left of X
can be ignored entirely.
Now, if X were the first value in the array, then if we’re really lucky, we are looking for either X
itself, or if not, we are looking for an item less than X, so that the next subarray we search is of size
0 (since if X is the first value in the array, there is nothing to its left). But if in that case – where
X is the first value in the array – if we are unlucky, we need to find a value larger than X, and in
that case, we can only rule out the cell containing X (once we inspect that cell) but still have the
entire rest of the array to the right of X to search:

____________________________________
| | |
no values < X | X | all other values are > X |
|_______|___________________________|
no indices < lo lo indices > lo

So, inspecting the first cell of the array first is not the nicest decision. Sure, sometimes we
finish our search quickly, but other times, we have the entire rest of the array to inspect, and
that wouldn’t be any better than linear search, and so we’re not really taking advantage of our
knowledge that the array is sorted. It would be nicer to inspect the middle cell of the array first
– that way, whether we are searching for a value less than X or greater than X, we’ll be able to
eliminate about half the array from needing to be searched:
398

_____________________________________________________________
| | | |
| values < X | X | values > X |
|________________________|_______|___________________________|
indices < middle middle indices > middle
(about half of the (about half of the
total number of indices) total number of indices)

So, our subproblem could be to search on half the array. We could check the middle value of
the array, rather than the first value, and if the middle value was not our search key, then we either
recursively search the subarray to the left of the middle value, or we recursively search the subarray
to the right of the middle value. We won’t need to make both recursive calls, only one. This is
because the search key – assuming it’s not equal to the middle value – can only be less than that
middle element or greater than the middle value. It cannot be both less than and greater than the
middle element; that makes no sense.
We’ll start with the same parameters we had for linear search, and the same return type:

public static int binarySearch(int[] arr, int key, int lo, int hi)

As with linear search, if lo > hi, then you have no values to explore and should return -1. Other-
wise, find the middle index of the sorted array and call it mid. The middle index will be the average
of the lowest and highest indices, since the indices are all consecutive. If you have an odd number
of cells in the range lo...hi, then (lo + hi)/2 would be an integer, and an existing index. If
you have an even number of cells in the range lo...hi, then (lo + hi)/2 will end up truncating
a division. For example, if you had the indices 0 through 9:

0 1 2 3 4 5 6 7 8 9
-----

then 4 and 5 are the cells in the middle. There is no one clear middle cell, since we have an even
number of cells; mathematically, (lo + hi)/2 gives us 4.5, and we could take either 4 or 5 as the
middle cell. But since, in our actual code, (lo + hi)/2 will be truncated to 4, then the index 4
will be the easiest choice for the middle index. That is, when we have an even number of cells in
our subarray, we’ll take the left of the two middle cells, as the middle cell we use in our algorithm.
Call the middle index mid. If what you are searching for is at that index, return the index.
Otherwise, if what you are searching for is less than that value, you recursively search the subarray
to the left of the middle index. Since we’re looking to the left, the low index is still lo, but the
high index is now the highest index that is still to the left of mid – namely, the index mid - 1. So,
if what we are searching for is less than the value at mid, then we recursively search the subarray
with index range (lo...mid - 1). Likewise, if what we are searching for is greater than the value
at mid, then we want to search the subarray to the right of mid. In that case, the low index of our
new subarray will be the smallest index that is still to the right of mid – namely, mid + 1 – and
therefore the subarray we search recursively has the index range (mid + 1...hi).
399

For example, suppose we are searching for 71 in the array below:

---------------------------------------
arr[i] 3 11 31 46 59 67 71 78 93 94
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9
lo == 0, hi == 9

Our mid index is 4, and 71 is greater than arr[4] == 59, so we recursively search the subarray
5...9:

-------------------
arr[i] 3 11 31 46 59 67 71 78 93 94
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9
lo == 5, hi == 9

Now, mid == (5 + 9)/2 == 7, and 71 is less than arr[7] == 78, so we recursively search the
subarray 5...6:

------
arr[i] 3 11 31 46 59 67 71 78 93 94
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9
lo == 5, hi == 6

Now, mid == (5 + 6)/2 == 5, and 71 is greater than arr[5] == 67, so we recursively search the
subarray 6...6:

--
arr[i] 3 11 31 46 59 67 71 78 93 94
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9
lo == 6, hi == 6

Now, mid == (6 + 6)/2 == 6, and 71 is equal to arr[6], so we have found our value and we
return 6, the index where that value is located.
If our search turns out to be unsuccessful, we end up shrinking our subarray down to size 0. For
example, if we were searching for 72 instead of 71, the first three of the four steps above would be
the same; 72 is greater than 59, less than 78, and greater than 67. But in the fourth step, instead
of having found our value, as we did above, we instead have a search key greater than the current
middle value:
400

--
arr[i] 3 11 31 46 59 67 71 78 93 94
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9
lo == 6, hi == 6

mid == (6 + 6)/2 == 6, and 72 is greater than arr[6] == 71

So, we’d search the range mid + 1...hi recursively. Except, mid + 1 == 7, so now we are search-
ing the range 7...6 and we have lo > hi:

| <----- subarray of size 0


|
arr[i] 3 11 31 46 59 67 71 78 93 94
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9
lo == 7, hi == 6

And as previously stated, in that case we would return -1, indicating an unsuccessful search.
Certainly, each of our two search algorithms finds some values faster – linear search works better
if our value is at the first index, for example, and binary search works better if our value is at the
middle index. The catch is that we don’t know where the value is ahead of time, so we can’t really
select an algorithm based on where the value is. But one thing we can do is to select an algorithm
based on the maximum number of comparisons we might need to make before we know our return
value for certain. And, in that case, binary search wins out (assuming we can use it to begin
with...i.e. assuming the array is sorted) – linear search potentially searches every cell in the array,
whereas binary search is continually discarding half the remaining array without even having to
inspect the cells in that half of the array. Since there are so many cells that binary search won’t
need to look at, the maximum number of cells it will need to look at will be much less than the
maximum number of cells that linear search will need to look at. We’ll quantify this difference in a
moment, but for now we can at least note that binary search, at worst, inpects significantly fewer
cells than the worst possible case of linear search.
401

Our code that implements this algorithm appears below:

public static int binarySearch(int[] arr, int key, int lo, int hi)
{
int returnVal;
if (lo > hi)
returnVal = -1;
else // lo <= hi
{
int mid = (lo + hi)/2;
if (arr[mid] == key)
returnVal = mid;
else if (key < arr[mid])
returnVal = binarySearch(arr, key, lo, mid - 1);
else // key > arr[mid]
returnVal = binarySearch(arr, key, mid + 1, hi);
}
return returnVal;
}

Of course, we’d probably have a wrapper method around that as well, as we did for linearSearch:

public static int binarySearch(int[] arr, int key)


{
return binarySearch(arr, key, 0, arr.length-1);
}

In both of these algorithms, the recursive call operates on a collection that is half the size of
the original collection. The exponentiation algorithm divides the exponent in half each time, and
the binary search algorithm recursively searches on half the remaining array each time.
For exponentiation, it takes constant time to go through the call once, and likewise, for binary
search, it takes constant time to go through the call once. So, the question is, how many recursive
calls are started? If you divide the value in half each time, how many times can you do that before
the problem can no longer be divided in half?
The answer is, the number of times you can divide n in half before you reach 1 is log2 n. So, if
you have constant time per step, and log2 n steps, then your algorithm will take log2 n time, that
is, logarithmic time.
The first exponentiation algorithm’s running time grew linearly with the change in exponent;
this new exponentiation algorithm’s running time grows logarithmically with the change in expo-
nent. Similarly, the running time for linear search grew linearly with the change in the array size,
but for binary search, the running time grows logarithmically with the change in the array size. So
we’ve managed to improve the order of growth of the running time, of both exponentiation, and
the searching of a sorted array.
402

Lectures 36 and 37 : Mergesort


Mergesort is related to InsertionSort in the same way that Quicksort was related to SelectionSort.
SelectionSort has one recursive call as the last work that needs to be done, and Quicksort has
two recursive calls as the last work that needs to be done. InsertionSort began with one recursive
call and then did other work; MergeSort will begin with two recursive calls and then do other work.
That is to say, the primary work on MergeSort occurs after we’ve already made two recursive
calls to sort two parts of the array recursively. Quicksort did a bunch of partitioning work, and
then, once the array was fully partitioned, made two recursive calls to sort the two halves of the
partition. Mergesort will make recursive calls to sort both the first half, and then the second half,
of the array – and so the work that remains after that is to merge the two sorted halves of the array
– hence the name of the algorithm.

Mergesort:
1) sort the first half of the array (recursively)
2) sort the second half of the array (recursively)
3) now that you have two sorted subarrays, merge them
into one sorted array

So, just as FindMinimum was where most of the work was done in SelectionSort, just as
InsertInOrder was where most of the work was done in InsertionSort, and just as Partition
and PartitionWrapper were where most of the work was done in Quicksort, we will have a Merge
algorithm in Mergesort that will handle most of the work for this algorithm. The recursive calls
are all going to make Merge calls themselves, and over the lifetime of the algorithm, most of the
time will be spent in Merge.
What does Merge do? Well, it takes two sorted collections and builds a single sorted collection
out of them. This can actually be done in one pass down the data, because for any two sorted
collections, it’s easy to find the minimum value:

a1 a2 a3 a4

b1 b2 b3 b4

(a1 < a2 < a3 < a4, and b1 < b2 < b3 < b4)

Above are two sorted collections of values. And if we want to find the overall minimum, well,
we know it can’t be any of a2, a3, or a4, because the first collection is sorted so a1 is smaller
than all of them, and so if a2, a3, and a4 aren’t even the minimum of their own collection, they
certainly can’t be the overall minimum. (If we had duplicates, a1 still has to be the smallest value
of its collection, even of other values are tied with it.) Likewise, in the second collection, since it is
sorted, b1 is the minimum of that collection by definition, and so there is no way b2, b3, or b4 can
be the overall minimum since they aren’t even the minimum of their own collection. So, the overall
minimum has to be either a1 or b1, and to find out which of the two is the overall minimum, we
just compare them and select the smaller of the two values.
In the event that it is a1, that’s the first element of your new sorted collection, and you get the
rest of the sorted collection by merging the remaining two sorted collections recursively:
403

a1 then merge these: a2 a3 a4

b1 b2 b3 b4

(a2 < a3 < a4, and b1 < b2 < b3 < b4)

And if instead b1, then that’s the first element of your new sorted collection, and you get the
rest of the sorted collection by merging the remaining two sorted collections recursively:

b1 then merge these: a1 a2 a3 a4

b2 b3 b4

(a1 < a2 < a3 < a4, and b2 < b3 < b4)

If you reach the point where one collection is completely empty, you would then just automat-
ically pick the minimum value from the other collection for your first element. If both collections
are completely empty, then you have no elements and just return. For example:

Start of merge call #1:

2 6 18 26 50

1 10 20 23 25

1 < 2, so...

Start of merge call #2:

2 6 18 26 50

1 10 20 23 25

2 < 10, so...

Start of merge call #3:

6 18 26 50

1 2 10 20 23 25

6 < 10, so...


404

Start of merge call #4:

18 26 50

1 2 6 10 20 23 25

10 < 18, so...

Start of merge call #5:

18 26 50

1 2 6 10 20 23 25

18 < 20, so...

Start of merge call #6:

26 50

1 2 6 10 18 20 23 25

20 < 26, so...

Start of merge call #7:

26 50

1 2 6 10 18 20 23 25

23 < 26, so...

Start of merge call #8:

26 50

1 2 6 10 18 20 23 25

25 < 26, so...


405

Start of merge call #9:

26 50

1 2 6 10 18 20 23 25

first collection is all that’s left, and min of that is 26, so...

Start of merge call #10:

50

1 2 6 10 18 20 23 25 26

first collection is all that’s left, and min of that is 50, so...

Start of merge call #11:

1 2 6 10 18 20 23 25 26 50

no collections are left, we are done, start returning from


recursive method calls

The one sticking point here is that we can’t do this efficiently in place:

2 6 18 26 50 1 10 20 23 25
____________________ _________________
first sorted second sorted
half half

Above, we see the two sorted collections we just merged in our example, stored side-by-side
in an array as they would be after MergeSort’s two recursive calls. In this case, again, 1 is the
minimum of the entire collection, since it is less than 2. However, if we try to move 1 to the front
of the array, the entire first collection – half the cells – needs to be shifted to the right one cell to
make room for 1 to go at the front. If we did that shifting of the first collection repeatedly, that
cost will add up.
So instead, we use a temporary array. We keep track of the lo and hi of each of the two
collections, but have a temporary array that we write the next minimum into, cell by cell. This
means we’ll need to keep track of the next spot to write into in the temporary array as well. When
we’ve finished the merge, we copy everything from the temporary array back into the original array.
For example:
406

Start of merge call #1:

2 6 18 26 50 1 10 20 23 25
loA hiA loB hiB

// temp values written here


cur // index to temp array

1 < 2, so...

Start of merge call #2:

2 6 18 26 50 1 10 20 23 25
loA hiA loB hiB

1
cur

2 < 10, so...

Start of merge call #3:

2 6 18 26 50 1 10 20 23 25
loA hiA loB hiB

1 2
cur

6 < 10, so...


407

Start of merge call #4:

2 6 18 26 50 1 10 20 23 25
loA hiA loB hiB

1 2 6
cur

10 < 18, so...

Start of merge call #5:

2 6 18 26 50 1 10 20 23 25
loA hiA loB hiB

1 2 6 10
cur

... and so on. You know the first collection is empty when loA > hiA, and you know the second
collection is empty when loB > hiB.
This gives us the following code:

public static void MergeSort(int[] A, int lo, int hi)


{
if (lo < hi)
{
int mid = (lo + hi)/2;
MergeSort(A, lo, mid);
MergeSort(A, mid+1, hi);
int[] temp = new int[hi-lo+1];
Merge(A, temp, lo, mid, mid+1, hi, 0);
Copy(temp, A, lo, hi, 0); // copy temp array back to A
}
}
408

public static void Merge(int[] A, int[] temp, int cur1, int end1,
int cur2, int end2, int cur3)
{
if ((cur1 > end1) && (cur2 > end2))
return;
else if (cur1 > end1)
{
temp[cur3] = A[cur2];
Merge(A, temp, cur1, end1, cur2+1, end2, cur3+1);
}
else if (cur2 > end2)
{
temp[cur3] = A[cur1];
Merge(A, temp, cur1+1, end1, cur2, end2, cur3+1);
}
else if (A[cur1] < A[cur2])
{
temp[cur3] = A[cur1];
Merge(A, temp, cur1+1, end1, cur2, end2, cur3+1);
}
else // (A[cur2] <= A[cur1]
{
temp[cur3] = A[cur2];
Merge(A, temp, cur1, end1, cur2+1, end2, cur3+1);
}
}

/*
you could also combine those conditions, i.e.

if (( cur1 > end1) && (cur2 > end2))


return;
else if ((cur1 > end1) || ((cur2 <= end2) && (A[cur2] <= A[cur1])))
{
temp[cur3] = A[cur2];
Merge(A, temp, cur1, end1, cur2+1, end2, cur3+1);
}
else // ((cur2 > end2) || ((cur1 <= end1) && (A[cur1] < A[cur2])))
{
temp[cur3] = A[cur1];
Merge(A, temp, cur1+!, end1, cur2, end2, cur3+1);
}
*/
409

public static void Copy(int[] temp, int[] A, int lo, int hi, int index)
{
if (index <= hi-lo)
{
A[lo+index] = temp[index];
Copy(temp, A, lo, hi, index+1);
}
}
410

Lectures 38 and 39 : Quicksort


Quicksort is the best sorting algorithm known which is still comparison-based. A comparison-based
sorting algorithm sorts using only comparisons on the elements. Other sorting algorithms which
take advantage of other specific properties of your data (for example, if you are sorting integers you
can use your data as array indices and count how many times each integer occurs) can be faster,
but those algorithms are topics for a more advanced course than this. Quicksort relies only on
being able to say whether a given element of a given type is less than, equal to, or greater than
another element of the same type.
As with the previous two sorting algorithms, Quicksort will operate on subarrays, which means
we will be passing the lower and upper array bounds to our recursive function in addition to the
array itself.
The algorithm works as follows. Given an array, choose a pivot value. Partition the array
so that you have on one side, all the elements less than the pivot, and on the other side, all the
elements greater than the pivot, and in between them the pivot itself (which would by definition
be in its correct final location). Then, recursively sort the two sides.
For example, if we had the array:

A[i] 2 6 8 1 7 4 3 5
--------------------------------------
i 0 1 2 3 4 5 6 7

and let’s suppose we chose 5 as our pivot value. (We will discuss in just a bit how we would go
about choosing a pivot. For now, never mind that and assume we end up with 5 as the pivot.)
With 5 selected as the pivot, what we want to is arrange the array – i.e. partition it – so that all
values less than 5 are to the left of 5, and all values greater than 5 are to the right of 5.

A[i] (elements 1- | | (elements 6-8


4 take up | | take up cells
cells 0-3) | 5 | 5-7)
--------------------------------------
i 0 1 2 3 4 5 6 7

Above, the values 1-4, which are all less than 5, are to the left of 5. The values 6-8, which are all
greater than 5, are to the right of five. This will force the pivot value, 5, into its correct location
(i.e. when the array is fully sorted, value 5 will be in cell 4, but that is also where it already is right
now).
If you had the array

A[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

and chose 39 as the pivot then the properly partitioned array would be:

(elements 6, | | (elements 41, 54, 67,


A[i] 11, 23 here)| 39 | 71, 88 and 93 here)
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9
411

Again, everything less than the pivot ends up to the left of the pivot, everything greater than the
pivot ends up to the right of the pivot, and the pivot ends up in its correct location (in the fully
sorted array, 39 goes in cell 3, and that is where it is already). (As you can see, though we are
calling the two sides “halves”, the are not always exactly the same size. More on that later.)
Once you have partitioned the array, then you simply call Quicksort recursively on the two
halves. If your array runs from lower bound lo to upper bound hi, and your pivot ends up at index
m, then the left half consists of the cells lo...m-1 and the right half consists of the cells m+1...hi.
You don’t need to include the pivot cell in either of the recursive calls because it is already placed
correctly.
And, since we can assume the recursive call works correctly, we get the following:

Array after partitioning:

A[i] (elements 1- | | (elements 6-8


4 take up | | take up cells
cells 0-3) | 5 | 5-7)
--------------------------------------
i 0 1 2 3 4 5 6 7

Array after recursively sorting left half:

A[i] | | (elements 6-8


| | take up cells
1 2 3 4 | 5 | 5-7)
--------------------------------------
i 0 1 2 3 4 5 6 7

Array after recursively sorting right half:

A[i] | |
| |
1 2 3 4 | 5 | 6 7 8
--------------------------------------
i 0 1 2 3 4 5 6 7

and after that second recursive call returns, we are done!


So, there are three things we still don’t really know, and we are going to look at those next:

1. How is the pivot selected?

2. Once the pivot is selected, how exactly does the array get partitioned into “less than pivot”
and “greater than pivot” sections? It’s all well and good to say “all these values end up in this
section”, but that is a little vague. Precisely what algorithm gets used to do this partitioning
work?

3. We said we should “recursively sort the halves”. When are those halves too small? i.e., what
is the base case?
412

Selecting the pivot


The key to making Quicksort run quickly is to get the two halves to actually be as close to real
halves as possible. That is, if our pivot ends up at index m, we would like m to be the middle index
of the array, and barring that, we would like m to be as close to the middle index of the array as
possible.
The danger of allowing otherwise can be seen if we go back to our first example array, but pick
the leftmost element instead of the rightmost element for the pivot.
A[i] 2 6 8 1 7 4 3 5
--------------------------------------
i 0 1 2 3 4 5 6 7

2 will be the pivot


In this case, after we partition, we will have all values less than 2 to the left of 2, and all values
greater than 2 to the right of 2:
A[i] 1 2 (everything > 2 here)
--------------------------------------
i 0 1 2 3 4 5 6 7

2 (at index 1) is the pivot


As you can see, the left subarray has only one element, and the right subarray is only two cells less
than the overall array. In fact, the worst situation is when we have the minimum or maximum as
the pivot. In those situations, one of the two “halves” won’t exist at all, and the other will be only
one cell shorter than the overall array. Sorting two arrays half the size of the original array actually
turns out to take less time than it takes to sort one array only one cell less in length than the
original array, so we would prefer to have a pivot location close to the middle so that we can divide
our array into two equal-size subarrays rather than one non-existent subarray and one subarray
nearly the same size as the overall array.
The problem is that trying to find the exact median element will take far too long for our
purposes (take our word for this; the proof of it is beyond the scope of this course). So, we can’t
go in search of the exact median. Instead, we have to do the best we can to get a decent pivot.
But, it turns out this is okay. As long as we get a decent pivot, most of the time, we are okay. If
sometimes, we get bad pivot, that is not a problem. What we want to avoid is getting a bad pivot,
almost all of the time.
When we get a bad pivot almost all of the time, that is our worst-case behavior. In that case,
Quicksort will take linear time to find the pivot and partition at each step (we’ll see why shortly),
and yet each step will result in trying to recursively sort an array only one cell less in length
– meaning we have n nested recursive calls. This will result in quadratic behavior, and indeed,
Quicksort runs in quadratic time in the worst case.
However, we generally get good pivots most of the time, and so we are also concerned with the
average case. And in the average case, Quicksort runs in time proportional to n log n – that is,
“linear times logarithmic”. This is a larger function – and thus a longer (worse) running time – than
linear functions, but smaller function – and thus a shorter (faster) running time – than quadratic
functions.
So, how can we hit the average case as often as possible? How can we make sure our pivot
selection is as good as we can make it?
413

• As already stated, actually searching for the ideal pivot is not practical, because it will make
each recursive call take too much time. We want to partition quickly.

• We could randomly select an index and use that value as the pivot. It would make it unlikely
that we would consistently select a pivot value very close to the minimum or very close to the
maximum. But (good) random number generation can take a bit of time. It is still constant
time, just not as low of a constant as you might think.

• If the array is arranged randomly, we could just grab the first or last element in the array
(or subarray). This should be the equivalent of making a random selection of index, while
avoiding the random number generation time. But, in the real world (as we mentioned when
discussing the running time of InsertionSort) data is often partially sorted, so picking the
first or last index is more likely to result in a bad pivot choice than would a purely random
selection.

• One other alternative – the one we are going to use, by the way – involves reading three
different values, and choosing their median. The three values we read are the ones in the
second cell of the subarray, the middle cell of the subarray, and the last cell of the subarray.
Even if the data is partially sorted, this is likely to give us a low element, a middle element,
and a high element, and at any rate, it makes it even less likely that we get a bad pivot,
since we’d now have to pick two values that were both close-to-the-end on the same side of
the array – something which is even less likely than getting one close-to-the-end value. This
process is known as median-of-three pivot selection, since we are selecting the pivot by taking
the median of three different values.
This method exchanges the random number generation time for three comparisons among
the three values at the three cells. But, it tends to get us a better pivot in practice, which
makes the algorithm run faster overall. So, this is the more appealing choice. It makes a
consistently bad pivot selection unlikely enough that we can consider the worst case to be
quite unlikely. And, since the average case – which is much more likely than the worst case –
is the fastest known for a comparison-based sorting algorithm (there are other n log n sorting
algorithms, but none with a constant as low as Quicksort’s), that makes Quicksort a very
appealing choice for many sorting problems.
414

Example 1:

A[i] 2 6 8 1 7 4 3 5
--------------------------------------
i 0 1 2 3 4 5 6 7

In the above array, if we want to perform median-of-three pivot selection, we must (as stated
above) look at the second cell, the middle cell, and the last cell. The second cell is A[1], the last
cell is A[7], and the middle cell is A[(0 + 7)/2] == A[3]. (With arrays of even length, there
are technically two middle elements, but typically the one on the left gets chosen because typically
integer division rounds down (to 3, above) instead of up (to 4, above).)
The values in the second, middle, and last cells are 6, 1, and 5 respectively. And, the median
of those values is 5. Therefore 5 would be the pivot value selected for the above array by the
median-of-three pivot selection algorithm.

Example 2:

A[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

In example 2, the second, middle, and last cells are those at indices 1, 4, and 9 respectively. The
values at those cells are 11, 39, and 54 respectively. The median of those three values is 39, and
thus 39 would be the pivot value selected for the above array by the median-of-three pivot selection
algorithm.
415

Partitioning the array

Once we have chosen our pivot value using median-of-three pivot selection, we then need to
partition the array. The problem is, the pivot might be in the last cell, or it might be in the middle
cell, or it might be in the second cell. Our partition algorithm is going to want to traverse down
the array trying to rearrange values, and for the moment, that pivot is just getting in the way.
So, what we are going to do is get it out of the way, by moving the pivot to the first cell. No
matter which of the three cells – second, middle, or last – the pivot was found in, we’ll swap it with
the value in the first cell and now we can safely traverse from the second cell through the last cell
without running into the pivot.
Now, the question is, how can we partition the rest of this array – from the second cell through
the last cell – into a “less than pivot portion” and a “greater than pivot” portion?
We will partition the array by inspecting the values in the array one by one. Values that are
less than the pivot will go to the left side of the array, and values that are greater than the pivot
will go to the right side of the array. However, we want to make sure we don’t deal with a value
once it has been inspected. For example, once we read a value and we know it belongs on the left
side of the array, we would like to put it on the left side of the array and know we’ll never run into
it again for the rest of the partitioning process.

A[i] p=39 - 26 - - - - - - -
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9
^
39 is pivot |
|
We’d like to know we can move 26 to the ‘‘left side’’
and not deal with it any longer...which means we need
some knowledge of which cells constitute the ‘‘left side’’.
so we know where to move 26 to.

Likewise, if a value belongs on the right side of the array, we would like to put it on the right
side of the array and know we’ll never run into it again for the rest of the partitioning process.

A[i] p=39 - 86 - - - - - - -
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9
^
39 is pivot |
|
We’d like to know we can move 86 to the ‘‘right side’’
and not deal with it any longer...which means we need
some knowledge of which cells constitute the ‘‘right side’’
so we know where to move 86 to.

The solution is to keep track of which cells constitute the “left side” and the “right side”, just
as we said we needed to do above. However, there is no need to keep track of all of those cells.
Rather, we need only keep track of the rightmost value of the left side (anything to its left is also
part of the left side and less than the pivot) and the leftmost value of the right side (anything to its
right is also part of the right side and greater than the pivot). In other words, we will have “side
416

bounds”, which will move inwards as we learn about the values in the “unknown” region. So, the
next value we check will always be the value in the cell just to the right of the left bound.

left right
bound bound
confirmed | unknown | confirmed
< pivot | | > pivot
A[i] pivot=39 - - - | - - - - | - -
----------- -----------|---------------|--------
i 0 1 2 3 | 4 5 6 7 | 8 9
| |
| |
|____________________________________|
|
portion of array that
we are partitioning

As we discover a new value that belongs on the left, we’ll move the left bound over to the right
one spot to open up a space to the left of the left bound...

If X < pivot
left right
bound bound
confirmed | unknown | confirmed
< pivot |---> | > pivot
A[i] pivot=39 - - - | X - - - | - -
----------- -----------|---------------|--------
i 0 1 2 3 | 4 5 6 7 | 8 9
| |

...and then put X on the left side of the left bound. Effectively, we just keep X exactly where it
is but move the left bound over to the right one location. And now that the left bound has moved
to the right one location, the less-than-pivot region is one cell larger and the unknown region is one
cell smaller. And we next check cell 5, the cell which is now just to the right of the left bound.

If X < pivot
left right
bound bound
confirmed | unknown | confirmed
< pivot | | > pivot
A[i] pivot=39 - - - X | - - - | - -
----------- ---------------|-----------|--------
i 0 1 2 3 4 | 5 6 7 | 8 9
| |
417

Likewise, if we come across a value that belongs on the right side of the right bound, we want
to move that value to the right side of the right bound. But, this means that we need to move the
right bound over to the left one location, so that there is an additional space to the right of the
right bound where we can store X.

If X > pivot
left right
bound bound
confirmed | unknown | confirmed
< pivot | <---| > pivot
A[i] pivot=39 - - - | X - - Y | - -
----------- -----------|---------------|--------
i 0 1 2 3 | 4 5 6 7 | 8 9
| |

But if we move the right bound over, that moves Y into the right side when we haven’t even
checked it yet. And then when we move X into that newly-added-to-the-right-side cell (i.e. cell 7),
we will write over Y, which is also bad. The solution here is to swap X and Y before we move the
right bound over one location, and then we will re-check cell 4 because we have moved a new value
there – again, the cell we check is always the one just to the right of the left bound. Whenever we
don’t move the left bound, it is because we moved the right bound instead, and that means a new
value got swapped into the cell (A[4] above) that we just inspected, and so it is okay to inspect
that cell a second time (since it contains a new value).

If X > pivot
left right
bound bound
confirmed | unknown | confirmed
< pivot | | > pivot
A[i] pivot=39 - - - | Y - - | X - -
----------- -----------|-----------|------------
i 0 1 2 3 | 4 5 6 | 7 8 9
| |

X is where Y was, Y is where X was, and X is on the right side of the right bound.
At this point, an example will help. Our rules, again, will be:

• Check cell i, which will always be the cell just to the right of the left bound.

• If the value we encounter at cell i is less than the pivot, move the left bound one position to
the right (so cell i is now to the left of the left bound) and check cell i+1.

• If the value we encounter at cell i (call it X) is greater than the pivot, swap that value and
the value just to the left of the right bound (call it Y). Now, X is just to the left of the right
bound, and Y is just to the right of the left bound. Then, move the right bound one position
to the left so that X is just to the right of the right bound, and then re-check cell i, since the
swap has placed a new value (Y) there.
418

Example 1, where 5 was the pivot:

Original array:

A[i] 2 6 8 1 7 4 3 5
--------------------------------------
i 0 1 2 3 4 5 6 7

Swap pivot with first cell to get it out of


the way:

A[i] 5 6 8 1 7 4 3 2
--------------------------------------
i 0 1 2 3 4 5 6 7

Set up bounds marked with L and R:

L R
| |
A[i] 5 | 6 8 1 7 4 3 2 |
----------|---------------------------|
i 0 | 1 2 3 4 5 6 7 |
| |

Inspect cell just to right of Left Bound, which


here is A[1]. 6 > pivot, so swap with
value to left of Right Bound and move Right
Bound over one location. Now 6 is on ‘‘right side’’.

L R
| |
A[i] 5 | 2 8 1 7 4 3 | 6
----------|-----------------------|---
i 0 | 1 2 3 4 5 6 | 7
| |

Inspect cell just to right of Left Bound, which


here is A[1]. 2 < pivot, so here you just
move Left Bound over one location. Now, 2 is
on ‘‘ left side’’.

L R
| |
A[i] 5 2 | 8 1 7 4 3 | 6
--------------|-------------------|---
i 0 1 | 2 3 4 5 6 | 7
| |
419

Inspect cell just to right of Left Bound, which


here is A[2]. 8 > pivot, so swap with
value to left of Right Bound and move Right
Bound over one location. Now 8 is on ‘‘right side’’.

L R
| |
A[i] 5 2 | 3 1 7 4 | 8 6
--------------|---------------|-------
i 0 1 | 2 3 4 5 | 6 7
| |

Inspect cell just to right of Left Bound, which


here is A[2]. 3 < pivot, so here you just
move Left Bound over one location. Now, 3 is
on ‘‘ left side’’.

L R
| |
A[i] 5 2 3 | 1 7 4 | 8 6
------------------|-----------|-------
i 0 1 2 | 3 4 5 | 6 7
| |

Inspect cell just to right of Left Bound, which


here is A[3]. 1 < pivot, so here you just
move Left Bound over one location. Now, 1 is
on ‘‘ left side’’.

L R
| |
A[i] 5 2 3 1 | 7 4 | 8 6
----------------------|-------|-------
i 0 1 2 3 | 4 5 | 6 7
| |
420

Inspect cell just to right of Left Bound, which


here is A[4]. 7 > pivot, so swap with
value to left of Right Bound and move Right
Bound over one location. Now 7 is on ‘‘right side’’.

L R
| |
A[i] 5 2 3 1 | 4 | 7 8 6
----------------------|---|-----------
i 0 1 2 3 | 4 | 5 6 7
| |

As we continue with the partitioning, our “less than” bound will increase, and our “greater
than” bound will decrease, thus closing in a smaller and smaller area until we have only one cell
left.
So, now we come to the final idea behind our partitioning. What happens when we have only
one cell left above? As far as the partitioning goes, there isn’t really any problem anymore. Say
that last cell is cell i. We know that, except for the pivot itself, everything in cells to the left of
cell i is less than the pivot. And, we also know that everything to the right of cell i is greater than
the pivot. So, we can picture our value at i with one side or the other, as appropriate:

Our leftover value is 4:

L R
| |
A[i] 5 2 3 1 | 4 | 7 8 6
----------------------|---|-----------
i 0 1 2 3 | 4 | 5 6 7

| < pivot | > pivot |


|______________|___________|

But if it had happened to be 9 instead, we’d


simply view it as being part of the right
side instead of as part of the left side:

L R
| |
A[i] 5 2 3 1 | 9 | 7 8 6
----------------------|---|-----------
i 0 1 2 3 | 4 | 5 6 7

| < pivot | > pivot |


|__________|___ ___________|
421

So, clearly, the problem isn’t partitioning this last element. We are fine as far as rearranging
the elements goes. The problem is in re-placing the pivot at its correct location. Which of the two
situations we have above doesn’t matter from the standpoint of “finding sides”, since the array is
correct in that regard either way. But they are quite different from the standpoint of “placing the
pivot”.

Our leftover value is 4:

L R
| |
A[i] 5 2 3 1 | 4 | 7 8 6
----------------------|---|-----------
i 0 1 2 3 | 4 | 5 6 7

| | < pivot | > pivot |


| |______________|___________|
|
|_________________^

pivot goes in between the sides, so really,


pivot should be at cell 4 and the ‘‘< pivot’’
stuff should be in cells 0-3

But if A[4] had happened to be 9 instead, we’d


simply view it as being part of the right
side instead of as part of the left side:

L R
| |
A[i] 5 2 3 1 | 9 | 7 8 6
----------------------|---|-----------
i 0 1 2 3 | 4 | 5 6 7

| | < pivot | > pivot |


| |__________|___ ___________|
|
|_____________^

pivot goes in between the sides, so really,


pivot should be at cell 3 and the ‘‘< pivot’’
stuff should be in cells 0-2.

In other words, the size of the left side determines where the pivot gets placed, since the pivot
gets placed right after it. And, when we are partitioning, and we end up down to one last value, if
that value belongs with the left side then it is sitting where the pivot should go, and if it belongs
422

to the right side, then it is the first value in the right side and the pivot should be placed just to
the left of it. But in either case, the last cell of the left side is sitting where the pivot should go,
whether that last cell is the “last unknown cell” (the first example in the example pairs above), or
the cell to its left (the second example in the example pairs above).
Why do things work this way? Why is the last cell of the left side always where the pivot should
go? Well, this is because the left side needs to take up c cells, but that left side doesn’t get to start
at 0, since the pivot is in the way. So, instead, it starts at cell 1, and therefore instead of ending
up at cell c-1 (after taking up cells 0 through c-1), it ends up at cell c (after taking up cells 1
through c). Since the c values less than the pivot should be taking up cells 0 through c-1, leaving
cell c free for the pivot, then since the rightmost value of the left side is taking up cell c instead of
cell c-1 due to the pivot forcing the left side to start at cell 1 instead of cell 0, the rightmost cell
of the left side will always be sitting where the pivot should be.
What this means, however, is that we can complete our partition by swapping the pivot with
the rightmost value of the left side, since the pivot is supposed to go in that rightmost cell of the
left side, and the rightmost value of the left side can go where the pivot currently is, since it is
less than the pivot and thus can end up anywhere on the left side after this partition operation is
complete. Once we make that swap, then all the “less than pivot” elements are now to the left of
the pivot, as we can see by expanding on our two examples above.
The first example, where the last value to be handled by the partition was less than the pivot:

Our leftover value is 4:

L R
| |
A[i] 5 2 3 1 | 4 | 7 8 6
----------------------|---|-----------
i 0 1 2 3 | 4 | 5 6 7

| < pivot | > pivot |


|______________|___________|

pivot goes in between the sides, so really,


pivot should be at cell 4 and the ‘‘< pivot’’
stuff should be in cells 0-3

So swap pivot 5 (A[0] == 5) and our


one remaining ‘‘unknown’’ value (A[4] == 4)
to fix this problem, and we get the array as shown
below:
423

L R
| |
A[i] 4 2 3 1 | 5 | 7 8 6
----------------------|---|-----------
i 0 1 2 3 | 4 | 5 6 7

| < pivot | | > pivot |


|______________| m |___________|

And we should return the index m, which will


be the spot that our last ‘‘unknown’’ value was located in.

But if A[4] had happened to be 9 instead, we’d


simply view it as being part of the right
side instead of as part of the left side:

L R
| |
A[i] 5 2 3 1 | 9 | 7 8 6
----------------------|---|-----------
i 0 1 2 3 | 4 | 5 6 7

| | < pivot | > pivot |


| |__________|_______________|
|
|_____________^

pivot goes in between the sides, so really,


pivot should be at cell 3 and the ‘‘< pivot’’
stuff should be in cells 1-2.

So swap pivot 5 (A[0] == 5) and the cell


just to the left of our last ‘‘unknown’’cell
(A[3] == 1) to fix this problem, and we get
the array as shown below.
424

L R
| |
A[i] 1 2 3 5 | 9 | 7 8 6
----------------------|---|-----------
i 0 1 2 3 | 4 | 5 6 7

| < pivot | | > pivot |


|__________| m |_______________|

And we should return the index m, which will be just to


the left of the spot that our last ‘‘unknown’’ value
was located in.

Please take note of one last thing – our “wall” or bounds idea is really just a set of recursive
calls. Each call attempts to partition an array with bounds lo and hi, and when the “left bound
moves” we are just calling on the subarray with bounds lo+1 and hi, and likewise when the “right
bound moves”, we are just calling on the subarray with bounds lo and hi-1.

Example 2, where 39 was the pivot:

A[i] 93 11 71 6 39 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Swap pivot to side and out of way.


Then Partition the range 1...9
L R
A[i] 39 11 71 6 93 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Partitioning range 1...9. A[1] is less than


pivot so (left bound case) partition range 2...9
L R
A[i] 39 11 71 6 93 67 41 88 23 54
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Partitioning range 2...9. A[2] is greater than


pivot so (right bound case) swap with A[9] and
then partition range 2...8
L R
A[i] 39 11 54 6 93 67 41 88 23 71
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9
425

Partitioning range 2...8. A[2] is greater than


pivot so (right bound case) swap with A[8] and
then partition range 2...7
L R
A[i] 39 11 23 6 93 67 41 88 54 71
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Partitioning range 2...7. A[2] is less than


pivot so (left bound case) partition range 3...7.
L R
A[i] 39 11 23 6 93 67 41 88 54 71
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Partitioning range 3...7. A[3] is less than


pivot so (left bound case) partition range 4...7.
L R
A[i] 39 11 23 6 93 67 41 88 54 71
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Partitioning range 4...7. A[4] is greater than


pivot so (right bound case) swap with A[7] and
partition range 4...6.
L R
A[i] 39 11 23 6 88 67 41 93 54 71
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Partitioning range 4...6. A[4] is greater than


pivot so (right bound case) swap with A[6] and
partition range 4...5.
L R
A[i] 39 11 23 6 41 67 88 93 54 71
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Partitioning range 4...5. A[4] is greater than


pivot so (right bound case) swap with A[5] and
partition range 4...4.
L R
A[i] 39 11 23 6 67 41 88 93 54 71
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9
426

Partitioning range 4...4. Low and high bounds are


equal, so actual partitioning is completed, and it is
simply a matter of knowing where pivot should go.
Here, 67 is greater than pivot, so pivot should
go to its left, into cell 3. Eventually we swap
A[0] and A[3].
L R
A[i] 39 11 23 6 67 41 88 93 54 71
-----------------------------------------------
i 0 1 2 3 4 5 6 7 8 9

Done!

< pivot | m | > pivot


A[i] 6 11 23| 39 |67 41 88 93 54 71
------------------|----|-----------------------
i 0 1 2 | 3 |4 5 6 7 8 9
427

Quicksort base cases

There are a number of ways we could handle the base case for Quicksort. The way we will use
for this class is one of the most easily understandable, though other methods are likely to be a bit
more efficient overall.
Our base case will be the situation where the array size is 0, 1, or 2. In other words, if the array
size is 3 or more, then go ahead and perform pivot selection, partitioning, and recursive calls as we
have already discussed. But, if the array size is 0, 1, or 2, then we will not do those things, but will
instead handle those cases with non-recursive code specifically designed for those small-array-size
cases.
What do we do in those three cases?

• If the array size is zero, then that means we have passed bounds that don’t make sense.
This is similar to what happened with BinarySearch. There, once lo was greater than hi,
we knew that we had made a recursive call on an “empty” or “non-existent” subarray, and
we could simply return without doing anything. The same rule applies here. There is no
subarray, so just return.

• If the array size is 1, then there is nothing to sort – there is only one element, so it cannot
possibly be “out of order”. So, you only need to return.

• If the array size is 2, then either the 2-element array is in order, or else we swap the two
elements to put it in order. So, we can check to make sure the lower-cell element is less than
the higher-cell element, swap the two if it is not, and then return.

In any case where the array size is 3 or more, we will need more than one comparison to learn the
correct sorted order, and so those situations will not be part of the base case, but will instead be
handled by the recursive case. Essentially, we are saying that our base case is that the array can
be sorted in one comparison or less, and that the recursive case is that the array cannot be sorted
in one comparison or less.
428

Implementing Quicksort in Java

First, a few quick preliminaries...

1) We are going to need the Swap method again. We first looked at this method when discussing
SelectionSort, and the SelectionSort notes go over the development of Swap. But as a reminder
of what the code looks like, we’ll reproduce it here:

public static void Swap(int[] A, int i, int j)


{
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}

2) Quicksort needs to recursively call itself on subarrays, just as some of the other recursive algo-
rithms we’ve looked at have done. So, once again, if you’d prefer that the user just send in the array
to be sorted and not worry about having to pass in array bounds to start the recursive calls with,
then you can have a public Quicksort that accepts only the array, and then that that method can
be implemented with the one-line call to the private version of Quicksort which does take array
bounds as parameters and does all the real work of the algorithm.

public static void Quicksort(int[] A)


{
Quicksort(A, 0, A.length - 1);
}
429

On to the Quicksort implementation...


The core of the Quicksort method is the recursive structure:

// choose pivot and partition the subarray, with pivot at cell m


// sort (lo...m - 1)
// sort (m + 1...hi)
Note that we have chosen to describe the pivot selection and partitioning as one procedure. It
really is useful to view things this way, and so we are going to view them that way – by having a
separate method that takes care of pivot selection and partitioning together. This method will be
responsible for returning the index m where the pivot eventually ends up, but otherwise, all details
of pivot selection and partitioning can be hidden away in that method call, which will make the
recursive Quicksort method a lot cleaner.

private static void Quicksort(int[] A, int lo, int hi)


{
int m = PartitionWrapper(A, lo, hi);
Quicksort(A, lo, m - 1);
Quicksort(A, m + 1, hi);
}

We need to pass in our array bounds to PartitionWrapper, because the partitioning algorithm as
we described it way above needs the actual low and high bounds of the subarray in order to work.
But, we don’t need to care about the details of PartitionWrapper in this Quicksort method. All
we need is the index m where the pivot is located, so we know which subarray constitutes the “left
half” and which subarray constitutes the “right half”.
Before we move on, we need to add the base case to the recursive case above. We said our base
case would be that the subarray size was 2 or less. The cases for subarrays of size 2, 1, and 0 are
as follows:
size == 2: hi == lo + 1 (lo is just to left of hi)
size == 1: hi == lo (lo and hi are same cell)
in which case ---> hi < lo + 1
size == 0: hi == lo - 1 (lo is just to right of hi)
in which case ---> hi < lo
in which case ---> hi < lo + 1
So, it appears we can say that if hi <= lo + 1, then the size of the subarray is 2 or less, and
by contrast, if hi > lo + 1, then we have 3 or more elements in our subarray.

private static void Quicksort(int[] A, int lo, int hi)


{
if (hi > lo + 1) // size is 3 or more
{
int m = PartitionWrapper(A, lo, hi);
Quicksort(A, lo, m - 1);
Quicksort(A, m + 1, hi);
}
else // size is 0, 1, or 2
// handle cases
}
430

The last thing we have to handle here is what to actually do for our base cases. For 0 and 1, we
don’t need to do anything. And, if we have the 2-element case and the lower element is already less
than the higer element, we still don’t need to do anything. The only time we need to do anything
is when we have the two-element case and the lower element is greater than the higher element, in
which case we swap them and then we are done.

private static void Quicksort(int[] A, int lo, int hi)


{
if (hi > lo + 1) // size is 3 or more
{
int m = PartitionWrapper(A, lo, hi);
Quicksort(A, lo, m - 1);
Quicksort(A, m + 1, hi);
}
else // size is 0, 1, or 2
if ((hi == lo + 1) && (A[lo] > A[hi]))
Swap(A, lo, hi);
}

So, above is the Quicksort method. Now we only need to write PartitionWrapper and we are
done.
431

Implementing PartitionWrapper

We start with the following framework, and will take care of the steps one at a time.

private static int PartitionWrapper(int[] A, int lo, int hi)


{
// 1) Choose pivot.
// 2) Swap pivot and first element.
// 3) Partition the remainder of the subarray.
// 4) Put pivot in its appropriate spot between the halves.
// 5) Return index where pivot is currently stored.
}

1) Choosing the pivot: As stated above, our pivot selection will be done via the median-of-three
pivot selection algorithm. We’ll pull that selection into a separate method, just to organize things
a little bit better.

private static int PartitionWrapper(int[] A, int lo, int hi)


{
int currentPivotLocation =
MedianOfThree(A, lo+1, hi, (lo+hi)/2 );
// 2) Swap pivot and first element.
// 3) Partition the remainder of the subarray.
// 4) Put pivot in its appropriate spot between the halves.
// 5) Return index where pivot is currently stored.
}

Notice that we are passing the array, and the second index, last index, and middle index to
MedianOfThree. The method itself won’t care which index was the middle one, which was the
last one, and which was the second one. What it will care about is the values at these indices.
Specifically, the method needs to decide, given an array A and three indices i, j, and k, what order
A[i], A[j], and A[k] belong in, and therefore which of the three is the median, and therefore
which of i, j, and k is the index of the median.
432

We have six possibilities for the ordering of the three values at those three indices:

A[i] <= A[j] <= A[k] // return j


A[i] <= A[k] <= A[j] // return k
A[k] <= A[i] <= A[j] // return i
A[j] <= A[i] <= A[k] // return i
A[j] <= A[k] <= A[i] // return k
A[k] <= A[j] <= A[i] // return j

And so all we really need to do is compare between the three values to determine the median,
and then return the appropriate index. I went over this in class, but it is straightforward enough
that I’ll just put the resultant method here and leave it to you to understand why it is written the
way it is.

private static int MedianLocation(int[] A, int i, int j, int k)


{
if (A[i] <= A[j])
if (A[j] <= A[k])
return j;
else if (A[i] <= A[k])
return k;
else
return i;
else // A[j] < A[i]
if (A[i] <= A[k])
return i;
else if (A[j] <= A[k])
return k;
else
return j;
}
433

2) Swapping the pivot and the first element: With MedianOfThree completed, we now need
to swap the pivot out of the way, into the first cell in the array, so that the cells from the second
index through the last index can be part of the partitioning.

private static int PartitionWrapper(int[] A, int lo, int hi)


{
int currentPivotLocation =
MedianOfThree(A, lo+1, hi, (lo+hi)/2 );
Swap(A, lo, currentPivotLocation);
// 3) Partition the remainder of the subarray.
// 4) Put pivot in its appropriate spot between the halves.
// 5) Return index where pivot is currently stored.
}

Actually, we will never use currentPivotLocation after this swap – from now on, the pivot will
either be sitting at A[lo] or much later, we will move the pivot to its correct location in the array.
So, we really don’t care where we found the pivot except that we need the index of that median-of-3
element in order to make the “swap pivot out of the way” work. Because of this, we can write the
index directly into the swap by just passing the method call as an argument to swap. But there’s
no need to store the currentPivotLocation in a separate variable because it will never be needed
again after this line of code.

private static int PartitionWrapper(int[] A, int lo, int hi)


{
Swap(A, lo, MedianLocation(A, lo+1, hi, (lo+hi)/2));
// 3) Partition the remainder of the subarray.
// 4) Put pivot in its appropriate spot between the halves.
// 5) Return index where pivot is currently stored.
}

3) Partitioning the remainder of the array: As we said way above, the partitioning is es-
sentially handled recursively, as “partition this subarray” becomes a matter of “deal witht the
leftmost element and then partition a smaller portion of this subarray”. So, we could write a
recursive version of Partition which would only take care of that actual partitioning work, and
from this non-recursive PartitionWrapper, we would only control the swap of pivot to the left-
most cell (step 2) and then the swap of pivot to it’s proper location later on (step 4). That is,
Partition will be the recursive algorithm that repeats the partitioning step over and over, and
PartitionWrapper does the setup work that is needed before Parition runs (such as selecting the
pivot) and the clean-up work that is needed after Partition runs.
All we need returned from that recursive version of Partition is the index m where the pivot
should swap to in the end. Since the pivot is sitting at A[lo], we pass the recursive partitioning
algorithm the bounds lo + 1 and hi, which are the bounds of the remainder of this array. We will
also pass in the pivot value, while lo + 1 and hi are only indices.
434

private static int PartitionWrapper(int[] A, int lo, int hi)


{
Swap(A, lo, MedianLocation(A, lo+1, hi, (lo+hi)/2));
int m = Partition(A, lo+1, hi, A[lo]);
// 4) Put pivot in its appropriate spot between the halves.
// 5) Return index where pivot is currently stored.
}

4) Putting pivot in its appropriate place between the halves: Now that you know which
index m is, well, we know we are suppposed to put the pivot there, which means it is time for another
swap (where we swap the pivot value in A[0] with the m generated by the recursive Partition
algorithm.

private static int PartitionWrapper(int[] A, int lo, int hi)


{
Swap(A, lo, MedianLocation(A, lo+1, hi, (lo+hi)/2));
int m = Partition(A, lo+1, hi, A[lo]);
Swap(A, lo, m);
// 5) Return index where pivot is currently stored.
}

5) Return index where pivot is currently stored: Once the above swap is complete, the pivot
is stored at index m, so we simply return the index m since that is the index that our Quicksort
method is waiting for in order to perform its own recursive calls.

// final version of the PartitionWrapper algorithm


private static int PartitionWrapper(int[] A, int lo, int hi)
{
Swap(A, lo, MedianLocation(A, lo+1, hi, (lo+hi)/2));
int m = Partition(A, lo+1, hi, A[lo]);
Swap(A, lo, m);
return m;
}

So, above, we run median-of-three to get the pivot location and swap the pivot with the far
left cell. We then call the recursive Partition to partition the rest of the array. It returns the
index where the pivot should go, and we swap the pivot into that cell and return that index back
to Quicksort.
435

Finally, we need to write the recursive version of Partition, which assumes the pivot is already
out of the way and just worries about the actual rest of the array.

private static int Partition(int[] A, int lo, int hi, int pivot)

To do this, we just consider the cases we discussed when going over two examples of partitioning
way above: We are partitioning the subarray lo...hi. We start by inspecting A[lo];

Recursive case 1: A[lo] is less than pivot. In that case, we just left A[lo] where it was and
recursively partitioned the subarray lo+1...hi.

if (A[lo] <= pivot)


return Partition(A, lo+1, hi, pivot);

Recursive case 2: A[lo] is greater than pivot. In that case, we swapped A[lo] with A[hi],
and then recursively partitioned the subarray lo...hi-1 (meaning the right bound moved left one
position, and we re-checked A[lo] since we had just swapped a new value into that position:

if (A[lo] > pivot)


{
Swap(A, lo, hi);
return Partition(A, lo, hi-1, pivot);
}

Base case: The base case occured when we were down to only one element in our subarray – i.e.
when lo == hi. In this case, we simply want to return which index the pivot should end up in,
and we said it would either be the location of this element in the partition of size 1 (i.e. the index
lo == hi) or else it would be the cell to the left of that (i.e. with index lo - 1). And, the way
we made that choice was, we returned the location of the element in the partition of size 1 if that
element was less than the pivot and should be swapped into pivot’s location on the left. If instead,
the element belonged to the right side, the “greater than pivot” side, then we wanted to swap pivot
with the element to the left of that instead – in both cases, the rightmost element in the left side.
436

if (hi == lo)
if (A[lo] <= pivot)
return lo;
else
return lo-1;

Putting it all together...

private static int Partition(int[] A, int lo, int hi, int pivot)
{
if (hi == lo)
if (A[lo] < pivot)
return lo;
else
return lo-1;
else if (A[lo] <= pivot)
return Partition(A, lo+1, hi, pivot);
else
{
Swap(A, lo, hi);
return Partition(A, lo, hi-1, pivot);
}
}
437

Lecture 40 : Advanced Sorting Analysis


Analyzing MergeSort

Mergesort can be described in this manner:

time to sort n values = time to sort left half +


time to sort right half +
time to merge sorted halves

which is commonly written in the form of a recurrence relation, i.e. a recursive mathematical
formula:

T(n) is the time to run MergeSort on n values

T(n) = T(n/2) + T(n/2) + time to merge


T(1) = 1 / base case is constant time

or after doing some algebra:

T(n) = 2 * T(n/2) + time to merge


T(1) = 1

You’ll learn about recurrence relations in a different course, and learn how to solve them. For
the moment, it simply makes for a convenient notation for the mergesort running time, before we
get our final result.
Now, what is the running time for merge? Well, if we have n total values among the two sorted
collections we are merging, and we spend constant time on each step to write one of those values
into the temporary array, then we have linear time total. And then, when we write the temporary
array back into the original array, likewise, that takes constant time to write one value on each
step, so that is likewise, linear time. So, overall, for a given step of MergeSort, the merge work is
linear; each value is written to the temporary array once and then written back once.
So, the recurrence relation looks like this:

T(n) = 2 * T(n/2) + linear


T(1) = 1

That describes mergesort – two recursive calls on an array half the size, plus linear work to
merge them. Now, you don’t have the mathematical tools to solve that yet; eventually, you’ll learn
all about solving recurrence relations.
But in this case, we can still analyze Mergesort – we just will use a different technique than
the recurrence relation. Each step breaks the array in half, and then we sort each of those halves
before trying to merge the entire array. So if we start out with n elements, consider the following
diagram, where the number we list (n, n/2, etc.) is the number of cells in the array at that level of
recursion:
438

/ \

left side right side


n/2 n/2

/ \ / \
left of right of left of right of
left left right right
n/4 n/4 n/4 n/4
/ \ / \ / \ / \
n/8 n/8 n/8 n/8 n/8 n/8 n/8 n/8

... etc.

Notice that each level adds up to n. The first level is indicating the Merge of the entire array
that we do at the end. On the second level, we have two recursive calls (the two we made from our
first call to Mergesort). Each one eventually runs Merge on an array of size n/2, but we double
that time because we have two such recursive calls. So that level, too, adds up to n.
The next level involves the third level of recursion – the calls we make, from the calls we made,
from our first call. Since we had already recursively called on half the array on the level above,
when we made two calls on half of THAT array, we end up with two recursive calls on an array one
fourth the size of the original. So the Merge for such a call takes time n/4, and we have four such
calls, so again, the total time for all the 3-levels-deep recursive calls is n.
And so on. Each row will total to n, in terms of time. And we have a logarithmic number of
rows, since we keep cutting the array size in half as we move from one level, to the level below it.
So if each row has time n, and there are a logarithmic number of rows, then the result is that the
running time for Mergesort has an order of growth equal to:
linear times logarithmic
OR

n * log-base-2 of n

OR

n * lg n // lg is a shorthand for log-base-2

but ”linear times logarithmic” is good enough for our purposes here. It means the algorithm takes
longer than linear time – which makes sense, since the last merge *alone* is linear time – but it’s
not quite as long as quadratic time. (just as constant is less than logarithmic is less than linear,
likewise n*constant is less than n*lg n is less than n * n, i.e. linear is less than n * lg n is less than
quadratic).
This turns out to be exactly the result we’d get if we mathematically solved the recurrence
relation we listed earlier. Our diagram above was just an alternate way of arriving at the same
result.
And note Mergesort does not take any advantage of the array being already sorted – the same
work gets done regardless. So it’s n *lg n for any case – worst, average, or even best.
439

Analyzing Quicksort

Partition is linear – each step takes constant time to place one value beyond the ”left wall” or
”right wall” so to partition an array of n values takes time that is linear in n.
So, in the best case, we get the best possible pivot for quicksort and the two halves of the array
are equal in size. In that case, we have the same recurrence relation as for Mergesort:

T(n) = linear + 2 * T(n/2)


T(1) = 1

So quicksort in the best case is n*lg n. And on average, we expect quicksort to get a good pivot
most of the time, so we can expect the average case to be similar to the best case, and thus to
*also* be n*lg n. (That’s not a proof, of course, but the proof that the average case is basically
equal to the best case is beyond this course. I just want to give you a gut feeling that it’s true, so
that you remember it better.)
The worst case for quicksort is when the pivot is always as low as possible, and in that case,
basically everything, or everything but one value, in the partition, ends up on the right side of the
pivot. So each recursive call to sort the right side, is basically only sorting only one or two fewer
cells than the previous call...so this is basically the same as SelectionSort at that point, and is thus
quadratic.
440

Summary:

• Quicksort

– best case: n * lg n
– average case: n * lg n
– worst case: quadratic
– advantage: on average, fastest known comparison-based sorting algorithm; i.e. the only
faster algorithms we know of are designed for specific kinds of data, rather than working
on anything we can compare. Both quicksort and mergesort have the same order of
growth, but in terms of constant factors of that n * lg n term, quicksort’s constants are
lower.
– disadvantage: the quadratic worst case. It’s very unlikely to occur but it *can* happen,
making quicksort not the best choice if you *need* to be at n * lg n time.

• Mergesort

– best case: n * lg n
– average case : n * lg n
– worst case: n * lg n
– advangtage: n * lg n in all cases, even worst case
– disadvantage: uses linear extra memory (the temporary array needed by merge)...this is
especially a problem if you have a VERY large array to sort, since you might not even
*have* enough memory left over to create another array that size. Also, not quite as
fast as quicksort’s average case, when you consider the constant factors (since they both
have n * lg n order of growth)

• InsertionSort

– best case: linear


– average case: quadratic, though about half the time of worst case
– worst case: quadratic
– advantage: works well for partially sorted or completely sorted arrays; also good for
small arrays since quicksort and mergesort tend to be overkill for such arrays
– disadvantage: quadratic for any randomly arranged array, i.e. it’s not better than
quadratic unless the array *is* sorted a lot already

• SelectionSort

– best case: quadratic


– average case: quadratic
– worst case: quadratic
– advantage: fewest number of swaps...in situations (sorting integers is not one of them)
where swapping our data can take a very long time due to very large amounts of it to
move back and forth, swapping might have such a LARGE constant on it that it would
overshadow everything else. In that case, we might want selection sort.
441

– disadvantage: quadratic in all cases, can’t even be improved upon if the array is partially
sorted. Works okay for small arrays but insertionsort works better for those arrays...so
in general, SelectionSort is not generally useful. It’s only useful in the one case listed as
an advantage above.

You might also like