[go: up one dir, main page]

0% found this document useful (0 votes)
240 views559 pages

Course Pack

This document outlines the syllabus for the Computer Science S-111 course at Harvard University. The course is an intensive 10-week introduction to computer science, covering algorithms, programming, data structures, and analysis of algorithms and data structures. It will require an estimated 20-30 hours of work per week, including lectures, problem sets involving both theoretical and programming problems, exams after each unit, and a final exam. The course aims to teach fundamental computer science concepts and ways of thinking through learning to program in Java.

Uploaded by

Shahzad Shah
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
240 views559 pages

Course Pack

This document outlines the syllabus for the Computer Science S-111 course at Harvard University. The course is an intensive 10-week introduction to computer science, covering algorithms, programming, data structures, and analysis of algorithms and data structures. It will require an estimated 20-30 hours of work per week, including lectures, problem sets involving both theoretical and programming problems, exams after each unit, and a final exam. The course aims to teach fundamental computer science concepts and ways of thinking through learning to program in Java.

Uploaded by

Shahzad Shah
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 559

Computer Science S-111

Intensive Introduction to Computer Science


Harvard Summer School 2021
David G. Sullivan, Ph.D.
Unit 1: Getting Started
1-1: Course Overview; Programming in Scratch .......................................... 1
1-2: Programming in Java ........................................................................... 20
1-3: Procedural Decomposition Using Simple Methods.............................. 27

Unit 2: Imperative Programming I


2-1: Primitive Data, Types, and Expressions.............................................. 40
2-2: Definite Loops ....................................................................................... 73

Unit 3: Imperative Programming II


3-1: Methods with Parameters and Return Values .................................. 102
3-2: Using Objects from Existing Classes ................................................. 125
3-3: Conditional Execution ........................................................................ 148
3-4: Indefinite Loops and Boolean Expressions ........................................ 171

Unit 4: Processing Collections of Data


4-1: Arrays .................................................................................................. 188
4-2: File Processing .................................................................................... 218
4-3: Recursion ............................................................................................. 230

Unit 5: Object-Oriented Programming


5-1: Classes as Blueprints: How to Define New Types of Objects............ 248
5-2: Inheritance and Polymorphism .......................................................... 286

Unit 6: Foundations of Data Structures


6-1: Abstract Data Types ........................................................................... 313
6-2: Recursion Revisited; Recursive Backtracking ................................... 332

Unit 7: Sorting and Algorithm Analysis


7-1: Fundamentals ..................................................................................... 352
7-2: Divide-and-Conquer Algorithms, Distributive Sorting ..................... 372

Unit 8: Sequences
8-1: Linked Lists ........................................................................................ 394
8-2: Lists, Stacks, and Queues................................................................... 427

Unit 9: Trees and Hash Tables


9-1: Binary Trees and Huffman Encoding ................................................ 460
9-2: Search Trees ........................................................................................ 483
9-3: Heaps and Priority Queues ................................................................ 502
9-4: Hash Tables......................................................................................... 515

Unit 10: Graphs ............................................................................................... 531


Unit 1, Part I

Intensive Introduction to
Computer Science

Course Overview
Programming in Scratch

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Welcome to CS S-111!
Computer science is not so much the science of computers
as it is the science of solving problems using computers.
Eric Roberts

• This course covers:


• the process of developing algorithms to solve problems
• the process of developing computer programs to express
those algorithms
• fundamental data structures for imposing order on a
collection of information
• the process of comparing data structures & algorithms
for a given problem

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 1


Computer Science and Programming
• There are many different fields within CS, including:
• software systems
• computer architecture
• networking
• programming languages, compilers, etc.
• theory
• AI

• Experts in many of these fields don’t do much programming!

• However, learning to program will help you to develop


ways of thinking and solving problems used in all fields of CS.

A Rigorous Introduction
• Intended for:
• future concentrators who plan to take more
advanced courses
• others who want a rigorous introduction
• no programming background required,
but can also benefit people with prior background

• Allow for 20-30 hours of work per week


• start work early!
• come for help!
• don't fall behind!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 2


CS 111 Requirements
• Lectures and sections
• attendance at both is required

• Ten problem sets (40%)


• part I = "written" problems
• part II = "programming" problems
• grad-credit students will have extra work on most assts.

• Four unit tests (25%)


• given at the end of lecture (see the schedule)

• Final exam (35%)


• Friday, August 6

Textbooks
• Required: The CSCI S-111 Coursepack
• contains all of the lecture notes
• print it and mark it up during lecture

• Optional resource for the first half:


Building Java Programs by Stuart Reges and Marty Stepp
(Addison Wesley).

• Optional resource for the second half:


Data Structures & Algorithms in Java, 2nd edition
by Robert Lafore (SAMS Publishing).

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 3


Course Staff
• Instructor: Dave Sullivan

• Teaching Assistant (TA): Ashby Hobart

• See the course website for contact info. and office hours

• Piazza is your best bet for questions.

• For purely administrative questions: libs111@fas.harvard.edu


• will forward your email to the full course staff

Other Details of the Syllabus


• Schedule:
• note the due dates and test dates
• no lectures or sections on most Wednesdays
• exceptions: July 7 (July 5 is off), July 14 (July 16 is off),
August 4 (August 5 is off)

• Policies:
• 10% penalty for submissions that are one day late
• please don't request an extension unless it's an emergency!
• grading

• Please read the syllabus carefully and make sure that you
understand the policies and follow them carefully.

• Let us know if you have any questions.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 4


Algorithms
• In order to solve a problem using a computer,
you need to come up with one or more algorithms.

• An algorithm is a step-by-step description of how to


accomplish a task.

• An algorithm must be:


• precise: specified in a clear and unambiguous way
• effective: capable of being carried out

Programming
• Programming involves expressing an algorithm in a form that
a computer can interpret.

• We will primarily be using the Java programming language.


• one of many possible languages

• The key concepts of the course transcend this language.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 5


What Does a Program Look Like?
• Here's a Java program that displays a simple message:

public class HelloWorld {


public static void main(String[] args) {
System.out.println("hello, world");
}
}

• Like all programming languages, Java has a precise set of rules


that you must follow.
• the syntax of the language

• To quickly introduce you to a number of key concepts,


we will begin with a simpler language.

Scratch
• A simple but powerful graphical programming language
• developed at the MIT Media Lab
• makes it easy to create animations, games, etc.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 6


Scratch Basics
• Scratch programs (scripts) control characters called sprites.

• Sprites perform actions and interact with each other on the stage.

the stage

building
blocks
for
programs/
scripts

drag building blocks here to create scripts

Program Building Blocks


• Grouped into color-coded categories:

• The shape of a building block indicates where it can go.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 7


Program Building Blocks: Statements
• Statement = a command or action

• Statements have bumps and/or notches


that allow you to stack them.
• each stack is a single script

• A statement may have:


• an input area that takes
a value (10, 1, etc.)
• a pull-down menu with choices (meow)

Program Building Blocks: Statements (cont.)


• Clicking on any statement in a script executes the script.

• When rearranging blocks, dragging a statement drags it


and any other statements below it in the stack.
• example: dragging the wait command below

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 8


Flow of Control
• Flow of control = the order in which statements are executed

• By default, statements in a script are executed sequentially


from top to bottom when the script is clicked.

• Control blocks (gold in color) allow you to affect the


flow of control.
• simple example: the wait statement above pauses
the flow of control

Flow of Control: Repetition


• Many control statements are C-shaped, which allows them
to control other statements.

• Example: statements that repeat other statements.

• Drag statements inside the opening to create a repeating stack.

forms

• In programming, a group of statements that repeats


is known as a loop.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 9


Flow of Control: Responding to an Event
• Hat blocks (ones with rounded tops) can be put on top of a script.

• They wait for an event to happen.


• when it does, the script is executed

What Does a Program Look Like?


• Recall our earlier Java program:

public class HelloWorld {


public static void main(String[] args) {
System.out.println("hello, world");
}
}

• Here's the Scratch version … and here's the result:

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 10


Stage Coordinates
• Dimensions: 480 units wide by 360 units tall

• Center has coordinates of 0, 0

What does this program draw?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 11


How many changes would be needed
to draw this figure instead? (What are they?)

How could we draw this figure?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 12


Flow of Control: Repeating a Repetition!

• One loop inside another loop!


• known as a nested loop

• How many times is the move statement executed above?

Making Our Program Easier to Change

• It would be nice to avoid having to manually change


all of the numbers.

• Take advantage of relationships between the numbers.


• what are they?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 13


Program Building Blocks: Variables
• A variable is a named location in the computer's memory
that is used to store a value.

• Can picture it as a named box:

• To create a variable:

Using Variables in Your Program

note: you must drag a variable into place, not type its name

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 14


Program Building Blocks: Operators
• Operators create a new value from existing values/variables.

Our Program with Variables and Operators

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 15


Getting User Input
• Use the ask command from the sensing category.

• The value entered by the user is stored in the special variable


answer, which is also located in the sensing category.

• Allowing the user to enter


numSides and numCopies:

Program Building Blocks: Boolean Expressions


• Blocks with pointed edges produce boolean values:
• true or false

• Boolean operators:

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 16


Flow of Control: Conditional Execution
• conditional execution = deciding whether to execute
one or more statements on the basis of some condition

• There are C-shaped control blocks for this:

• They have an input area with pointed edges for the condition.

Flow of Control: Conditional Execution (cont.)

• If the condition is true:


• the statements under the if are executed
• the statements under the else are not executed

• If the condition is false:


• the statements under the if are not executed
• the statements under the else are executed

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 17


How can we deal with invalid user inputs?

More Info on Scratch


• We're using the latest version:
https://scratch.mit.edu/projects/editor

• Creating a Scratch account is not required for this course.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 18


Final version
• We use two if-else statements to
check for invalid inputs:
• one checks for numSides < 3
• one checks for numCopies < 1

• If an invalid input is found, we:


• show the sprite
• have the sprite say
an error message
• end the program

• Otherwise, we continue with the rest


of the program.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 19


Unit 1, Part 2

Programming in Java

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Programs and Classes


• In Java, all programs consist of one of more classes.

• For now:
• we'll limit ourselves to writing a single class
• you can just think of a class as a container for your program

• Example: our earlier program:

public class HelloWorld {


public static void main(String[] args) {
System.out.println("hello, world");
}
}

• A class must be defined in a file with a name of the form


classname.java
• for the class above, the name would be HelloWorld.java

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 20


Using an IDE
• An integrated development environment (IDE) is an application
that helps you develop programs.

• We’ll use the Dr. Java IDE.


• PS 0 told you how to obtain and install it.

• With an IDE, you do the following:


• use its built-in text editor to write your code
• instruct the IDE to compile the code
• turns it into lower-level instructions that can be run
• checks for violations of the syntax of the language
• instruct the IDE to run the program
• debug as needed, using the IDE's debugging tools

Using Dr. Java

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 21


Format of a Java Class
• General syntax:
public class name {

code goes here…

where name is replaced by the name of the class.

• Notes:
• the class begins with a header:
public class name
• the code inside the class is enclosed in curly braces
({ and })

Methods
• A method is a collection of instructions that perform
some action or computation.

• Every Java program must include a method called main.


• contains the instructions that will be executed first
when the program is run

• Our example program includes a main method with a


single instruction:

public class HelloWorld {


public static void main(String[] args) {
System.out.println("hello, world");
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 22


Methods (cont.)
• General syntax for the main method:
public static void main(String[] args) {
statement;
statement;

statement;
}

where each statement is replaced by a single instruction.

• Notes:
• the main method always begins with the same header:
public static void main(String[] args)
• the code inside the method is enclosed in curly braces
• each statement typically ends with a semi-colon
• the statements are executed sequentially

Identifiers
• Used to name the components of a Java program like
classes and methods.

• Rules:
• must begin with a letter (a-z, A-Z), $, or _
• can be followed by any number of letters, numbers, $, or _
• spaces are not allowed
• cannot be the same as a keyword – a word like class
that is part of the language itself (see the Resources page)

• Which of these are not valid identifiers?


n1 num_values 2n
avgSalary course name

• Java is case-sensitive (for both identifiers and keywords).


• example: HelloWorld is not the same as helloWorld

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 23


Conventions for Identifiers
• Capitalize class names.
• example: HelloWorld

• Do not capitalize method names.


• example: main

• Capitalize internal words within the name.


• example: HelloWorld

Printing Text
public class HelloWorld {
public static void main(String[] args) {
System.out.println("hello, world");
}
}

• Our program contains a single statement that prints some text.

• The printed text appears in a window known as the console.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 24


Printing Text (cont.)
• The general format of such statements is:
System.out.println("text");

where text is replaced by the text you want to print.

• A piece of text like "Hello, world" is referred to as


a string literal.
• string: a collection of characters
• literal: specified explicitly in the program ("hard-coded")

• A string literal must be enclosed in double quotes.

• You can print a blank line by omitting the string literal:


System.out.println();

Printing Text (cont.)


• A string literal cannot span multiple lines.
• example: this is not allowed:
System.out.println("I want to print a string
on two lines.");

• Instead, we can use two different statements:


System.out.println("I want to print a string");
System.out.println("on two lines.");

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 25


println vs. print
• After printing a value, System.out.println
"moves down" to the next line on the screen.

• If we don’t want to do this, we can use System.out.print


instead:
System.out.print("text");

The next text to be printed will begin just after this text –
on the same line.

• For example:
System.out.print("I ");
System.out.print("program ");
System.out.println("with class!");

is equivalent to
System.out.println("I program with class!");

Escape Sequences
• Problem: what if we want to print a string that includes
double quotes?
• example: System.out.println("Jim said, "hi!"");
• this won’t compile. why?

• Solution: precede the double quote character by a \


System.out.println("Jim said, \"hi!\"");

• \" is an example of an escape sequence.

• The \ tells the compiler to interpret the following character


differently than it ordinarily would.

• Other examples:
• \n a newline character (goes to the next line)
• \t a tab
• \\ a backslash

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 26


Unit 1, Part 3

Procedural Decomposition
(How to Use Methods to Write Better Programs)

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Example Program: Writing Block Letters


• Here's a program that writes the name "DEE" in block letters:
public class BlockLetters {
public static void main(String[] args) {
System.out.println(" -----");
System.out.println(" | \\");
System.out.println(" | |");
System.out.println(" | /");
System.out.println(" -----");
System.out.println();
System.out.println(" +-----");
System.out.println(" |");
System.out.println(" +----");
System.out.println(" |");
System.out.println(" +-----");
System.out.println();
System.out.println(" +-----");
System.out.println(" |");
System.out.println(" +----");
System.out.println(" |");
System.out.println(" +-----");
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 27


Example Program: Writing Block Letters
• The output looks like this:
-----
| \
| |
| /
-----

+-----
|
+----
|
+-----

+-----
|
+----
|
+-----

Code Duplication
public class BlockLetters {
public static void main(String[] args) {
System.out.println(" -----");
System.out.println(" | \\");
System.out.println(" | |");
System.out.println(" | /");
System.out.println(" -----");
System.out.println();
System.out.println(" +-----");
System.out.println(" |");
System.out.println(" +----");
System.out.println(" |");
System.out.println(" +-----");
System.out.println();
System.out.println(" +-----");
System.out.println(" |");
System.out.println(" +----");
System.out.println(" |");
System.out.println(" +-----");
}
}

• The code that writes an E appears twice – it is duplicated.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 28


Code Duplication (cont.)
• Code duplication is undesirable. Why?

• Also, what if we wanted to create another word containing the


letters D or E? What would we need to do?

• A better approach: create a command for writing each letter,


and execute that command as needed.

• To create our own command in Java, we define a method.

Defining a Simple Static Method


• We've already seen how to define a main method:
public static void main(String[] args) {
statement;
statement;

statement;
}

• The simple methods that we'll define have a similar syntax:


public static void name() {
statement;
statement;

statement;
}

• This type of method is known as static method.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 29


Defining a Simple Static Method (cont.)
• Here's a static method for writing a block letter E:
public static void writeE() {
System.out.println(" +-----");
System.out.println(" |");
System.out.println(" +----");
System.out.println(" |");
System.out.println(" +-----");
}

• It contains the same statements that we used to write an E


in our earlier program.

• This method gives us a command for writing an E.

• To use it, we simply include the following statement:


writeE();

Calling a Method
• The statement
writeE();
is known as a method call.

• General syntax for a static method call:


methodName();

• Calling a method causes the statements inside the method


to be executed.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 30


Using Methods to Eliminate Duplication
• Here's a revised version of our program:
public class BlockLetters2 {
public static void writeE() {
System.out.println(" +-----");
System.out.println(" |");
System.out.println(" +----");
System.out.println(" |");
System.out.println(" +-----");
}

public static void main(String[] args) {


System.out.println(" -----");
System.out.println(" | \\");
System.out.println(" | |");
System.out.println(" | /");
System.out.println(" -----");
System.out.println();
writeE();
System.out.println();
writeE();
}
}

Methods Can Be Defined In Any Order


• Here's a version in which we put the main method first:
public class BlockLetters2 {
public static void main(String[] args) {
System.out.println(" -----");
System.out.println(" | \\");
System.out.println(" | |");
System.out.println(" | /");
System.out.println(" -----");
System.out.println();
writeE();
System.out.println();
writeE();
}
public static void writeE() {
System.out.println(" +-----");
System.out.println(" |");
System.out.println(" +----");
System.out.println(" |");
System.out.println(" +-----");
}
}

• By convention, the main method should appear first or last.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 31


Flow of Control
• A program's flow of control is the order in which its statements
are executed.

• By default, the flow of control:


• is sequential
• begins with the first statement in the main method

Flow of Control (cont.)


• Example: consider the following program:
public class HelloWorldAgain {
public static void main(String[] args) {
System.out.println("hello");
System.out.println("world");
System.out.println();
...
}
}

• We can represent the flow of control using a flow chart:

System.out.println("hello");

System.out.println("world");

System.out.println();

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 32


Method Calls and Flow of Control
• When we call a method, the flow of control jumps to the method.
• After the method completes, the flow of control jumps back
to the point where the method call was made.
public class BlockLetters2 {
public static void writeE() {
System.out.println(" +-----");
System.out.println(" |");
System.out.println(" +----");
System.out.println(" |");
System.out.println(" +-----");
}
public static void main(String[] args) {
System.out.println(" -----");
System.out.println(" | \\");
System.out.println(" | |");
System.out.println(" | /");
System.out.println(" -----");
System.out.println();
writeE();
System.out.println();
...

Method Calls and Flow of Control (cont.)


• Here's a portion of the flowchart for our program:

main method: writeE method:


.
.
.

System.out.println(" +-----");
System.out.println();

System.out.println(" |");
writeE(); .
.
.
System.out.println();
System.out.println(" +-----");
.
.
.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 33


Another Use of a Static Method
public class BlockLetters3 {
public static void writeD() {
System.out.println(" -----");
System.out.println(" | \\");
System.out.println(" | |");
System.out.println(" | /");
System.out.println(" -----");
}
public static void writeE() {
System.out.println(" +-----");
System.out.println(" |");
System.out.println(" +----");
System.out.println(" |");
System.out.println(" +-----");
}
public static void main(String[] args) {
writeD();
System.out.println();
writeE();
System.out.println();
writeE();
}
}

Another Use of a Static Method (cont.)


• The code in the writeD method is only used once,
so it doesn't eliminate code duplication.

• However, using a separate static method still makes the


overall program more readable.

• It helps to reveal the structure of the program.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 34


Procedural Decomposition
• In general, methods allow us to decompose a problem into
smaller subproblems that are easier to solve.
• the resulting code is also easier to understand and maintain

• In our program, we've decomposed the task "write DEE"


into two subtasks:
• write D
• write E (which we perform twice).

• We can use a structure diagram to show the decomposition:

write DEE

write D write E

Procedural Decomposition (cont.)


• How could we use procedural decomposition in printing
the following lyrics?
Dashing through the snow in a one-horse open sleigh,
O'er the fields we go, laughing all the way.
Bells on bobtail ring, making spirits bright.
What fun it is to ride and sing a sleighing song tonight!
Jingle bells, jingle bells, jingle all the way!
O what fun it is to ride in a one-horse open sleigh!
Jingle bells, jingle bells, jingle all the way!
O what fun it is to ride in a one-horse open sleigh!
A day or two ago, I thought I'd take a ride,
And soon Miss Fanny Bright was seated by my side.
The horse was lean and lank; misfortune seemed his lot;
We got into a drifted bank and then we got upsot.
Jingle bells, jingle bells, jingle all the way!
O what fun it is to ride in a one-horse open sleigh!
Jingle bells, jingle bells, jingle all the way!
O what fun it is to ride in a one-horse open sleigh!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 35


Procedural Decomposition (cont.)
Dashing through the snow in a one-horse open sleigh,
O'er the fields we go, laughing all the way. printVerse1
Bells on bobtail ring, making spirits bright.
What fun it is to ride and sing a sleighing song tonight!
Jingle bells, jingle bells, jingle all the way!
O what fun it is to ride in a one-horse open sleigh! printRefrain
Jingle bells, jingle bells, jingle all the way! printHalfRefrain
O what fun it is to ride in a one-horse open sleigh!
A day or two ago, I thought I'd take a ride,
And soon Miss Fanny Bright was seated by my side.
The horse was lean and lank; misfortune seemed his lot; printVerse2
We got into a drifted bank and then we got upsot.

printSong

printVerse1 printRefrain printVerse2

printHalfRefrain

Code Reuse
• Once we have a set of methods, we can use them to solve
other problems.

• Here's a program that writes the name "ED":


public class BlockLetters4 {
// these methods are the same as before
public static void writeD() {
...
}
public static void writeE() {
...
}
public static void main(String[] args) {
writeE();
System.out.println();
writeD();
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 36


Tracing the Flow of Control
• What is the output of the following program?
public class FlowControlTest {
public static void methodA() {
System.out.println("starting method A");
}
public static void methodB() {
System.out.println("starting method B");
}
public static void methodC() {
System.out.println("starting method C");
}
public static void main(String[] args) {
methodC();
methodA();
}
}

Methods Calling Methods


• The definition of one method can include calls to other methods.

• We've seen this already in the main method:


public static void main(String[] args) {
writeE();
System.out.println();
writeD();
}

• We can also do this in other methods:


public static void foo() {
System.out.println("This is method foo.");
bar();
}

public static void bar() {


System.out.println("This is method bar.");
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 37


Methods Calling Methods (cont.)
• What is the output of the following program?
public class FlowControlTest2 {
public static void methodOne() {
System.out.println("boo");
methodThree();
}
public static void methodTwo() {
System.out.println("hoo");
methodOne();
}
public static void methodThree() {
System.out.println("foo");
}
public static void main(String[] args) {
methodOne();
methodThree();
methodTwo();
}
}

Comments
• Comments are text that is ignored by the compiler.

• Used to make programs more readable

• Two types:
1. line comments: begin with //
• compiler ignores from // to the end of the line
• examples:
// this is a comment
System.out.println(); // so is this

2. block comments: begin with /* and end with */


• compiler ignores everything in between
• typically used at the top of each source file

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 38


Comments (cont.)
/*
* DrawTriangle.java
* Dave Sullivan (dgs@cs.bu.edu) block comments
* This program draws a triangle.
*/

public class DrawTriangle {


public static void main(String[] args) {
System.out.println("Here's my drawing:");
line comments
// Draw the triangle using characters.
System.out.println(" ^");
System.out.println(" / \\");
System.out.println(" / \\");
System.out.println(" / \\");
System.out.println(" -------");
}
}

Comments (cont.)
• Put comments:
• at the top of each file, naming the author and explaining
what the program does
• at the start of every method other than main,
describing its behavior
• inside methods, to explain complex pieces of code
(this will be more useful later in the course)

• We will deduct points for failing to include the correct comments


and other stylistic problems.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 39


Unit 2, Part 1

Primitive Data, Variables,


and Expressions;
Simple Conditional Execution

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Overview of the Programming Process

Analysis/Specification

Design

Implementation

Testing/Debugging

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 40


Example Problem: Adding Up Your Change
• Let's say that we have a bunch of coins of various types,
and we want to figure out how much money we have.
• Let’s begin the process of developing a program that
does this.

Step 1: Analysis and Specification


• Analyze the problem (making sure that you understand it),
and specify the problem requirements clearly and
unambiguously.
• Describe exactly what the program will do, without worrying
about how it will do it.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 41


Step 2: Design
• Determine the necessary algorithms (and possibly other
aspects of the program) and sketch out a design for them.
• This is where we figure out how the program will solve
the problem.
• Algorithms are often designed using pseudocode.
• more informal than an actual programming language
• allows us to avoid worrying about the syntax of the language
• example for our change-adder problem:
get the number of quarters
get the number of dimes
get the number of nickels
get the number of pennies
compute the total value of the coins
output the total value

Step 3: Implementation
• Translate your design into the programming language.
pseudocode  code
• We need to learn more Java before we can do this!

• Here's a portion or fragment of a Java program for computing


the value of a particular collection of coins:
quarters = 10;
dimes = 3;
nickels = 7;
pennies = 6;
cents = 25*quarters + 10*dimes + 5*nickels + pennies;
System.out.println("Your total in cents is:");
System.out.println(cents);

• In a moment, we'll use this fragment to examine some of the


fundamental building blocks of a Java program.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 42


Step 4: Testing and Debugging
• A bug is an error in your program.

• Debugging involves finding and fixing the bugs.

The first program bug! Found by Grace Murray Hopper at Harvard.


(http://www.hopper.navy.mil/grace/grace.htm)

• Testing – trying the programs on a variety of inputs –


helps us to find the bugs.

Overview of the Programming Process

Analysis/Specification

Design

Implementation

Testing/Debugging

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 43


Program Building Blocks: Literals
quarters = 10;
dimes = 3;
nickels = 7;
pennies = 6;
cents = 25*quarters + 10*dimes + 5*nickels + pennies;
System.out.println("Your total in cents is:");
System.out.println(cents);

• Literals specify a particular value.

• They include:
• string literals: "Your total in cents is:"
• are surrounded by double quotes
• numeric literals: 25 3.1416
• commas are not allowed!

Program Building Blocks: Variables


quarters = 10;
dimes = 3;
nickels = 7;
pennies = 6;
cents = 25*quarters + 10*dimes + 5*nickels + pennies;
System.out.println("Your total in cents is:");
System.out.println(cents);

• We've already seen that variables are named memory locations


that are used to store a value:

quarters 10

• Variable names must follow the rules for identifiers


(see previous notes).

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 44


Program Building Blocks: Statements
quarters = 10;
dimes = 3;
nickels = 7;
pennies = 6;
cents = 25*quarters + 10*dimes + 5*nickels + pennies;
System.out.println("Your total in cents is:");
System.out.println(cents);

• In Java, a single-line statement typically ends with a semi-colon.

• Later, we will see examples of control statements that


contain other statements, just as we did in Scratch.

Program Building Blocks: Expressions


quarters = 10;
dimes = 3;
nickels = 7;
pennies = 6;
cents = 25*quarters + 10*dimes + 5*nickels + pennies;
System.out.println("Your total in cents is:");
System.out.println(cents);

• Expressions are pieces of code that evaluate to a value.

• They include:
• literals, which evaluate to themselves
• variables, which evaluate to the value that they represent
• combinations of literals, variables, and operators:
25*quarters + 10*dimes + 5*nickels + pennies

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 45


Program Building Blocks: Expressions (cont.)
• Numerical operators include:
+ addition
- subtraction
* multiplication
/ division
% modulus or mod: gives the remainder of a division
example: 11 % 3 evaluates to 2

• Operators are applied to operands:


25 * quarters (2 * length) + (2 * width)

operands
of the * operator operands
of the + operator

Evaluating Expressions
• With expressions that involve more than one mathematical
operator, the usual order of operations applies.
• example:
3 + 4 * 3 / 2 – 7
=
=
=
=

• Use parentheses to:


• force a different order of evaluation
• example:
radius = circumference / (2 * pi);
• make the standard order of operations obvious!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 46


Evaluating Expressions with Variables
• When an expression includes variables, they are first
replaced with their current value.

• Example: recall our code fragment:


quarters = 10;
dimes = 3;
nickels = 7;
pennies = 6;
cents = 25*quarters + 10*dimes + 5*nickels + pennies;
= 25* 10 + 10* 3 + 5* 7 + 6
= 250 + 10* 3 + 5* 7 + 6
= 250 + 30 + 5* 7 + 6
= 250 + 30 + 35 + 6
= 280 + 35 + 6
= 315 + 6
= 321

println Statements Revisited


• Recall our earlier syntax for println statements:
System.out.println("text");

• Here is a more complete version:


System.out.println(expression);

any type of expression,


not just text
• Examples:
System.out.println(3.1416);
System.out.println(2 + 10 / 5);
System.out.println(cents); // a variable
System.out.println("cents"); // a string

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 47


println Statements Revisited (cont.)
• The expression is first evaluated, and then the value is printed.
System.out.println(2 + 10 / 5);

System.out.println(4); // output: 4

System.out.println(cents);

System.out.println(321); // output: 321

System.out.println("cents");

System.out.println("cents"); // output: cents

• Note that the surrounding quotes are not displayed when


a string is printed.

println Statements Revisited (cont.)


• Another example:
System.out.println(10*dimes + 5*nickels);

System.out.println(10*3 + 5*7);

System.out.println(65);

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 48


Expressions in DrJava
• If you enter an expression in the Interactions Pane,
DrJava evaluates it and displays the result.
• examples:
> "Hello world!" // do not put a semi-colon!
"Hello world!"
> 5 + 10
15
> 10 * 4 + 5 * 2
50
> 10 * (4 + 5 * 2)
140

• Note: This type of thing does not work inside a program.


public static void main(String[] args) {
5 + 10 // not allowed!
System.out.println(5 + 10); // do this instead
}

Data Types
• A data type is a set of related data values.
• examples:
• integers
• strings
• characters

• Every data type in Java has a name that we can use


to identify it.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 49


Commonly Used Data Types for Numbers
• int
• used for integers
• examples: 25 -2

• double
• used for real numbers (ones with a fractional part)
• examples: 3.1416 -15.2
• used for any numeric literal with a decimal point,
even if it's an integer:
5.0
• also used for any numeric literal written in scientific notation
3e8 -1.60e-19
more generally:
p
n x 10 is written n e p

Incorrect Change-Adder Program


/*
* ChangeAdder.java
* Dave Sullivan (dgs@cs.bu.edu)
* This program determines the value of some coins.
*/

public class ChangeAdder {


public static void main(String[] args) {
quarters = 10;
dimes = 3;
nickels = 7;
pennies = 6;

// compute and print the total value


cents = 25*quarters + 10*dimes + 5*nickels + pennies;
System.out.print("total in cents is: ");
System.out.println(cents);
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 50


Declaring a Variable
• Java requires that we specify the type of a variable before
attempting to use it.

• This is called declaring the variable.


• syntax:
type name;

• examples:
int count; // will hold an integer
double area; // will hold a real number

• A variable declaration can also include more than one


variable of the same type:
int quarters, dimes;

Assignment Statements
• Used to give a value to a variable.

• Syntax:
variable = expression;
= is known as the assignment operator.

• Examples:
int quarters = 10; // declaration plus assignment

// declaration first, assignment later


int cents;
cents = 25*quarters + 10*dimes + 5*nickels + pennies;

// can also use to change the value of a variable


quarters = 15;

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 51


Corrected Change-Adder Program
/*
* ChangeAdder.java
* Dave Sullivan (dgs@cs.bu.edu)
* This program determines the value of some coins.
*/

public class ChangeAdder {


public static void main(String[] args) {
int quarters = 10;
int dimes = 3;
int nickels = 7;
int pennies = 6;
int cents;

// compute and print the total value


cents = 25*quarters + 10*dimes + 5*nickels + pennies;
System.out.print("total in cents is: ");
System.out.println(cents);
}
}

Assignment Statements (cont.)


• Steps in executing an assignment statement:
1) evaluate the expression on the right-hand side of the =
2) assign the resulting value to the variable on the
left-hand side of the =

• Examples:
int quarters = 10;

int quarters = 10; // 10 evaluates to itself!

int quartersValue = 25 * quarters;

int quartersValue = 25 * 10;

int quartersValue = 250;

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 52


Assignment Statements (cont.)
• An assignment statement does not create a permanent
relationship between variables.

• Example from the DrJava Interactions Pane:


> int x = 10;
> int y = x + 2;
> y
12
> x = 20;
> y
12

• changing the value of x does not change the value of y!

• You can only change the value of a variable by assigning it


a new value.

Assignment Statements (cont.)


• As the values of variables change, it can be helpful to picture
what's happening in memory.

• Examples: undefined
int num1;
int num2 = 120; num1 ? num2 120
after the assignment at left, we get:

num1 = 50; num1 50 num2 120

num1 = num2 * 2; num1 240 num2 120


120 * 2
240

num2 = 60; num1 240 num2 60


The value of num1 is unchanged!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 53


Assignment Statements (cont.)
• A variable can appear on both sides of the assignment
operator!

• Example (fill in the missing values):


int sum = 13;
int val = 30; sum 13 val 30

sum = sum + val; sum val

val = val * 2; sum val

Operators and Data Types


• Each data type has its own set of operators.
• the int version of an operator produces an int result
• the double version produces a double result
• etc.

• Rules for numeric operators:


• if the operands are both of type int,
the int version of the operator is used.
• examples: 15 + 30
1 / 2
25 * quarters
• if at least one of the operands is of type double,
the double version of the operator is used.
• examples: 15.5 + 30.1
1 / 2.0
25.0 * quarters

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 54


Incorrect Extended Change-Adder Program
/*
* ChangeAdder2.java
* Dave Sullivan (dgs@cs.bu.edu)
* This program determines the value of some coins.
*/

public class ChangeAdder2 {


public static void main(String[] args) {
int quarters = 10;
int dimes = 3;
int nickels = 7;
int pennies = 6;
int cents;

// compute and print the total value


cents = 25*quarters + 10*dimes + 5*nickels + pennies;
System.out.print("total in cents is: ");
System.out.println(cents);
double dollars = cents / 100;
System.out.print("total in dollars is: ");
System.out.println(dollars);
}
}

Two Types of Division


• The int version of the / operator performs integer division,
which discards the fractional part of the result
(i.e., everything after the decimal).
• examples:
> 5 / 3
1
> 11 / 5
2
• The double version of the / operator performs
floating-point division, which keeps the fractional part.
• examples:
> 5.0 / 3.0
1.6666666666666667
> 11 / 5.0
2.2

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 55


How Can We Fix Our Program?
/*
* ChangeAdder2.java
* Dave Sullivan (dgs@cs.bu.edu)
* This program determines the value of some coins.
*/

public class ChangeAdder2 {


public static void main(String[] args) {
int quarters = 10;
int dimes = 3;
int nickels = 7;
int pennies = 6;
int cents;

// compute and print the total value


cents = 25*quarters + 10*dimes + 5*nickels + pennies;
System.out.print("total in cents is: ");
System.out.println(cents);
double dollars = cents / 100;
System.out.print("total in dollars is: ");
System.out.println(dollars);
}
}

String Concatenation
• The meaning of the + operator depends on the types of
the operands.

• When at least one of the operands is a string, the + operator


performs string concatenation.
• combines two or more strings into a single string
• example:
System.out.println("hello " + "world");
is equivalent to
System.out.println("hello world");

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 56


String Concatenation (cont.)
• If one operand is a string and the other is a number,
the number is converted to a string and then concatenated.
• example: instead of writing
System.out.print("total in cents: ");
System.out.println(cents);
we can write
System.out.println("total in cents: " + cents);

• Here's how the evaluation occurs:


int cents = 321;
System.out.println("total in cents: " + cents);
"total in cents: " + 321
"total in cents: " + "321"
"total in cents: 321"

Change-Adder Using String Concatenation


/*
* ChangeAdder2.java
* Dave Sullivan (dgs@cs.bu.edu)
* This program determines the value of some coins.
*/

public class ChangeAdder2 {


public static void main(String[] args) {
int quarters = 10;
int dimes = 3;
int nickels = 7;
int pennies = 6;
int cents;

// compute and print the total value


cents = 25*quarters + 10*dimes + 5*nickels + pennies;
System.out.println("total in cents is: " + cents);
double dollars = cents / 100.0;
System.out.println("total in dollars is: " +
dollars);
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 57


An Incorrect Program for Computing a Grade
/*
* ComputeGrade.java
* Dave Sullivan (dgs@cs.bu.edu)
* This program computes a grade as a percentage.
*/

public class ComputeGrade {


public static void main(String[] args) {
int pointsEarned = 13;
int possiblePoints = 15;

// compute and print the grade as a percentage


double grade;
grade = pointsEarned / possiblePoints * 100;
System.out.println("The grade is: " + grade);
}
}

• What is the output?

Will This Fix Things?


/*
* ComputeGrade.java
* Dave Sullivan (dgs@cs.bu.edu)
* This program computes a grade as a percentage.
*/

public class ComputeGrade {


public static void main(String[] args) {
int pointsEarned = 13;
int possiblePoints = 15;

// compute and print the grade as a percentage


double grade;
grade = pointsEarned / possiblePoints * 100.0;
System.out.println("The grade is: " + grade);
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 58


Type Casts
• To compute the percentage, we need to tell Java to treat
at least one of the operands as a double.

• We do so by performing a type cast:


grade = (double)pointsEarned / possiblePoints * 100;
or
grade = pointsEarned / (double)possiblePoints * 100;

• General syntax for a type cast:


(type)variable

Corrected Program for Computing a Grade


/*
* ComputeGrade.java
* Dave Sullivan (dgs@cs.bu.edu)
* This program computes a grade as a percentage.
*/

public class ComputeGrade {


public static void main(String[] args) {
int pointsEarned = 13;
int possiblePoints = 15;

// compute and print the grade as a percentage


double grade;
grade = (double)pointsEarned / possiblePoints * 100;
System.out.println("The grade is: " + grade);
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 59


Evaluating a Type Cast
• Example of evaluating a type cast:
pointsEarned = 13;
possiblePoints = 15;
grade = (double)pointsEarned / possiblePoints * 100;
(double)13 / 15 * 100;
13.0 / 15 * 100;
0.8666666666666667 * 100;
86.66666666666667;

• Note that the type cast occurs after the variable is replaced
by its value.

• It does not change the value that is actually stored in the variable.
• in the example above, pointsEarned is still 13

Type Conversions
• Java will automatically convert values from one type
to another provided there is no potential loss of information.

• Example: we can perform the following assignment


without a type cast:
double d = 3;

variable of value of
type double type int

• the JVM will convert the integer value 3 to the


floating-point value 3.0 and assign that value to d
• any int can be assigned to a double without losing
any information

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 60


Type Conversions (cont.)
• The compiler will complain if the necessary type conversion
could (at least in some cases) lead to a loss of information:
int i = 7.5; // won't compile

variable of value of
type int type double

• This is true regardless of the actual value being converted:


int i = 5.0; // won't compile

• To make the compiler happy in such cases, we need to


use a type cast:
double area = 5.7;
int approximateArea = (int)area;
System.out.println(approximateArea);
• what would the output be?

Type Conversions (cont.)


• When an automatic type conversion is performed as part of
an assignment, the conversion happens after the evaluation
of the expression to the right of the =.

• Example:
double d = 1 / 3;
= 0; // uses integer division. why?
= 0.0;

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 61


A Block of Code
• A block of code is a set of statements that is treated as a
single unit.

• In Java, a block is typically surrounded by curly braces.

• Examples:
• each class is a block
• each method is a block

public class MyProgram {


public static void main(String[] args) {
int i = 5;
System.out.println(i * 3);
int j = 10;
System.out.println(j / i);
}
}

Variable Scope
• The scope of a variable is the portion of a program
in which the variable can be used.

• By default, the scope of a variable in Java:


• begins at the point at which it is declared
• ends at the end of the innermost block
that encloses the declaration
public class MyProgram2 {
public static void main(String[] args) {
System.out.println("Welcome!");
System.out.println("Let's do some math!");
int j = 10;
scope of j
System.out.println(j / 5);
}
}

• Because of these rules, a variable cannot be used outside


of the block in which it is declared.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 62


Another Example
public class MyProgram3 {
public static void method1() {
int i = 5;
scope of
System.out.println(i * 3);
method1's
int j = 10;
scope of j version of i
System.out.println(j / i);
}
public static void main(String[] args) {
// The following line won't compile.
System.out.println(i + j);
int i = 4;
System.out.println(i * 6); scope of
method1(); main's
} version of i
}

Local Variables vs. Global Variables


public class MyProgram {
static int x = 10; // a global variable
public static void method1() {
int y = 5; // a local variable
System.out.println(x + y);
...

• Variables that are declared inside a method are local variables.


• they cannot be used outside that method.

• In theory, we can define global variables that are available


throughout the program.
• they are declared outside of any method,
using the keyword static

• However, we generally avoid global variables.


• can lead to problems in which one method accidentally
affects the behavior of another method

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 63


Yet Another Change-Adder Program!
• Let's change it to print the result in dollars and cents.
• 321 cents should print as 3 dollars, 21 cents
public class ChangeAdder3 {
public static void main(String[] args) {
int quarters = 10;
int dimes = 3;
int nickels = 7;
int pennies = 6;
int dollars, cents;

cents = 25*quarters + 10*dimes + 5*nickels + pennies;

// what should go here?

System.out.println("dollars = " + dollars);


System.out.println("cents = " + cents);
}
}

The Need for Conditional Execution


• What if the user has 121 cents?
• will print as 1 dollars, 21 cents
• would like it to print as 1 dollar, 21 cents

• We need a means of choosing what to print at runtime.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 64


Recall: Conditional Execution in Scratch

Conditional Execution in Java


if (condition) { if (condition) {
true block true block
} else { }
false block
}

• If the condition is true:


• the statement(s) in the true block are executed
• the statement(s) in the false block (if any) are skipped

• If the condition is false:


• the statement(s) in the false block (if any) are executed
• the statement(s) in the true block are skipped

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 65


Expressing Simple Conditions
• Java provides a set of operators called relational operators
for expressing simple conditions:
operator name examples
< less than 5 < 10
num < 0

> greater than 40 > 60 (which is false!)


count > 10

<= less than or equal to average <= 85.8

>= greater than or equal to temp >= 32

== equal to sum == 10
(don't confuse with = ) firstChar == 'P'

!= not equal to age != myAge

Change Adder With Conditional Execution


public class ChangeAdder3 {
public static void main(String[] args) {
...

System.out.print(dollars);
if (dollars == 1) {
System.out.print(" dollar, ");
} else {
System.out.print(" dollars, ");
}

// Add statements to correctly print cents.


// Try to use only an if, not an else.

}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 66


Classifying Bugs
• Syntax errors
• found by the compiler
• occur when code doesn't follow the rules of the
programming language
• examples?

Classifying Bugs
• Syntax errors
• found by the compiler
• occur when code doesn't follow the rules of the
programming language
• examples?

• Logic errors
• the code compiles, but it doesn’t do what you intended
it to do
• may or may not cause the program to crash
• called runtime errors if the program crashes
• often harder to find!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 67


Common Syntax Errors Involving Variables
• Failing to declare the type of the variable.

• Failing to initialize a variable before you use it:


int radius;
double area = 3.1416 * radius * radius;

• Trying to declare a variable when there is already a variable


with that same name in the current scope:
int val1 = 10;
System.out.print(val1 * 2);
int val1 = 20;

Will This Compile?


public class ChangeAdder {
public static void main(String[] args) {
...
int cents;
cents = 25*quarters + 10*dimes + 5*nickels + pennies;

if (cents % 100 == 0) {
int dollars = cents / 100;
System.out.println(dollars + " dollars");
} else {
int dollars = cents / 100;
cents = dollars % 100;
System.out.println(dollars + " dollars and "
+ cents + " cents");
}
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 68


Representing Integers
• Like all values in a computer, integers are stored as
binary numbers – sequences of bits (0s and 1s).

• With n bits, we can represent 2n different values.


• examples:
• 2 bits give 22 = 4 different values
00, 01, 10, 11
• 3 bits give 23 = 8 different values
000, 001, 010, 011, 100, 101, 110, 111

• When we allow for negative integers (which Java does)


n bits can represent any integer from –2n-1 to 2n-1 – 1.
• there's one fewer positive value to make room for 0

Java’s Integer Types


• Java’s actually has four primitive types for integers, all of which
represent signed integers.

type # of bits range of values


byte 8 –27 to 27 – 1
(–128 to 127)

short 16 –215 to 215 – 1


(–32768 to 32767)

int 32 –231 to 231 – 1


(approx. +/–2 billion)

long 64 –263 to 263 – 1g

• We typically use int, unless there’s a good reason not to.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 69


Java’s Floating-Point Types
• Java has two primitive types for floating-point numbers:

type # of bits approx. range approx. precision


float 32 +/–10–45 to +/–1038 7 decimal digits

double 64 +/–10–324 to +/–10308 15 decimal digits

• We typically use double because of its greater precision.

Binary to Decimal
• Number the bits from right to left
• example: 0 1 0 1 1 1 0 1
b7 b6 b5 b4 b3 b2 b1 b0

• For each bit that is 1, add 2n, where n = the bit number
• example: 0 1 0 1 1 1 0 1
b7 b6 b5 b4 b3 b2 b1 b0

decimal value = 26 + 24 + 23 + 22 + 20
64 + 16 + 8 + 4 + 1 = 93

• another example: what is the integer represented by


01001011?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 70


Decimal to Binary
• Go in the reverse direction: determine which powers of 2
need to be added together to produce the decimal number.
• example: 42 = 32 + 8 + 2
= 25 + 23 + 21
• thus, bits 5, 3, and 1 are all 1s: 42 = 00101010

• Start with the largest power of 2 less than or equal to the


number, and work down from there.
• example: what is 21 in binary?
16 is the largest power of 2 <= 21: 21 = 16 + 5
now, break the 5 into powers of 2: 21 = 16 + 4 + 1
1 is a power of 2 (20), so we’re done: 21 = 16 + 4 + 1
= 24 + 22 + 20
= 00010101

Decimal to Binary (cont.)


• Another example: what is 90 in binary?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 71


printf: Formatted Output
• When printing a decimal number, you may want to limit yourself
to a certain number of places after the decimal.

• You can do so using the System.out.printf method.


• example:
System.out.printf("%.2f", 1.0/3);
will print
0.33
• the number after the decimal point in the first parameter
indicates how many places after the decimal should be used

• There are other types of formatting that can also be performed


using this method.
• see Table 4.6 in the textbook for more examples

Review
• Consider the following code fragments
1) 1000
2) 10 * 5
3) System.out.println("Hello");
4) hello
5) num1 = 5;
6) 2*width + 2*length
7) main

• Which of them are examples of:


• literals? • expressions?
• identifiers? • statements?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 72


Unit 2, Part 2

Definite Loops

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Using a Variable for Counting


• Let's say that we're using a variable i to count the number
of times that something has been done:

int i = 0; i 0

• To increase the count, we can do this:


i = i + 1;
0 + 1
1 i 1

• To increase the count again, we repeat the same assignment:


i = i + 1;
1 + 1
2 i 2

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 73


Increment and Decrement Operators
• Instead of writing
i = i + 1;
we can use a shortcut and just write
i++;

• ++ is known as the increment operator.


• increment = increase by 1

• Java also provides a decrement operator (--).


• decrement = decrease by 1
• example:
i--;

Review: Flow of Control


• Flow of control = the order in which instructions are executed

• By default, instructions are executed in sequential order.


instructions flowchart
int sum = 0;
int num1 = 5; int sum = 0;
int num2 = 10;
sum = num1 + num2; int num1 = 5;

int num2 = 10;

sum = num1 + num2;

• When we make a method call, the flow of control "jumps" to


the method, and it "jumps" back when the method completes.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 74


Altering the Flow of Control: Repetition
• To solve many types of problems, we need to be able
to modify the order in which instructions are executed.

• One reason for doing this is to allow for repetition.

• We saw this in Scratch:

Example of the Need for Repetition


• Here's a method for writing a large block letter L:
public static void writeL() {
System.out.println("|");
System.out.println("|");
System.out.println("|");
System.out.println("|");
System.out.println("|");
System.out.println("|");
System.out.println("|");
System.out.println("+----------");
}

• Rather than duplicating the statement


System.out.println("|");
seven times, we'd like to have this statement appear just once
and execute it seven times.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 75


for Loops
• To repeat one or more statements multiple times, we can
use a construct known as a for loop.

• Here's a revised version of our writeL method that uses one:


public static void writeL() {
for (int i = 0; i < 7; i++) {
System.out.println("|");
}
System.out.println("+----------");
}

for Loops
• Syntax:
for ( initialization ; continuation test ; update ) {
one or more statements
}

• In our example: initialization continuation test

for (int i = 0 ; i < 7 ; i++ ) {


System.out.println("|"); update
}

• The statements inside the loop are known as


the body of the loop.

• In our example, we use the variable i to count the number


of times that the body has been executed.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 76


Executing a for Loop
for ( initialization ; continuation test ; update ) {
body of the loop
}

perform the Notes:


initialization
• the initialization is
no
only performed once
is the
test true? • the body is only
executed if the
yes
test is true
execute the • we repeatedly do:
body of the loop
test
perform the
body
update update
until the test is false
execute statement
after the loop

Executing Our for Loop


for (int i = 0; i < 7; i++) {
System.out.println("|");
}

initialization:
int i = 0; i i < 7 action
0 true print 1st "|"
is i < 7 no 1 true print 2nd "|"
true?
2 true print 3rd "|"
yes
3 true print 4th "|"
execute body: 4 true print 5th "|"
System.out.println("|");
5 true print 6th "|"
perform update: 6 true print 7th "|"
i++
7 false execute stmt.
execute statement after the loop
after the loop

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 77


Definite Loops
• For now, we'll limit ourselves to definite loops –
which repeat actions a fixed number of times.

• To repeat the body of a loop N times, we typically


take one of the following approaches:
for (int i = 0; i < N; i++) {
<body of the loop>
}
OR
for (int i = 1; i <= N; i++) {
<body of the loop>
}

• Each time that the body of a loop is executed is known as


an iteration of the loop.
• the loops shown above perform N iterations

Other Examples of Definite Loops


• What does this loop do?
for (int i = 0; i < 3; i++) {
System.out.println("Hip! Hip!");
System.out.println("Hooray!");
}

• What does this loop do?


for (int i = 0; i < 10; i++) {
System.out.println(i);
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 78


Using Different Initializations, Tests, and Updates
• The second loop from the previous page would be clearer
if we expressed it like this:
for (int i = 0; i <= 9; i++) {
System.out.println(i);
}

• Different problems may require different initializations,


continuation tests, and updates.

• What does this code fragment do?


for (int i = 2; i <= 10; i = i + 2) {
System.out.println(i * 10);
}

Tracing a for Loop


• Let's trace through the final code fragment from the last slide:
for (int i = 2; i <= 10; i = i + 2) {
System.out.println(i * 10);
}

i i <= 10 value printed

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 79


Common Mistake
• You should not put a semi-colon after the for-loop header:
for (int i = 0; i < 7; i++); {
System.out.println("|");
}

• The semi-colon ends the for statement.


• thus, it doesn't repeat anything!

• The println is independent of the for statement,


and only executes once.

Practice
• Fill in the blanks below to print the integers from 1 to 10:

for (____________; ____________; ____________) {


System.out.println(i);
}

• Fill in the blanks below to print the integers from 10 to 20:

for (____________; ____________; ____________) {


System.out.println(i);
}

• Fill in the blanks below to print the integers from 10 down to 1:

for (____________; ____________; ____________) {


System.out.println(i);
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 80


Other Java Shortcuts
• Recall this code fragment:
for (int i = 2; i <= 10; i = i + 2) {
System.out.println(i * 10);
}

• Instead of writing
i = i + 2;
we can use a shortcut and just write
i += 2;

• In general
variable += expression;
is equivalent to
variable = variable + (expression);

Java Shortcuts
• Java offers other shortcut operators as well.

• Here's a summary of all of them:


shortcut equivalent to
var ++; var = var + 1;
var --; var = var – 1;
var += expr; var = var + (expr);
var -= expr; var = var – (expr);
var *= expr; var = var * (expr);
var /= expr; var = var / (expr);
var %= expr; var = var % (expr);

• Important: the = must come after the mathematical operator.


+= is correct
=+ is not!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 81


More Practice
• Fill in the blanks below to print the even integers in reverse
order from 20 down to 6:

for (____________; ____________; ____________) {


System.out.println(i);
}

Find the Error


• Let's say that we want to print the numbers from 1 to n.

• Where is the error in the following code?


for (int i = 1; i < n; i++) {
System.out.println(i);
}

• This is an example of an off-by-one error. Beware of these


when writing your loop conditions!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 82


Example Problem: Printing a Pattern, version 1
• Ask the user for a positive integer (call it n), and print a pattern
containing n asterisks.
• example:
Enter a positive integer: 3
***

• Let's use a for loop to do this:


// code to read n goes here...
for ( ) {
System.out.print("*");
}
System.out.println();

Example Problem: Printing a Pattern, version 2


• Print a pattern containing n lines of n asterisks.
• example:
Enter a positive integer: 3
***
***
***

• One way to do this is to use a nested loop – one loop inside


another:
// code to read in n goes here...
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
System.out.print("*");
}
System.out.println();
}
• This makes it easier to create a similar box of a different size.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 83


Nested Loops
• When you have a nested loop, the inner loop is executed to
completion for every iteration of the outer loop.

• Recall our Scratch drawing program:

• How many times is the move statement executed?

Nested Loops (cont.)


• How many times is the println statement executed below?
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 7; j++) {
System.out.println(i + " " + j);
}
}

• How many times is the println statement executed below?


for (int i = 0; i < 5; i++) {
for (int j = 0; j < i; j++) {
System.out.println(i + " " + j);
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 84


Tracing a Nested for Loop
for (int i = 0; i < 5; i++) {
for (int j = 0; j < i; j++) {
System.out.println(i + " " + j);
}
}
i i < 5 j j < i value printed

Recall: Variable Scope


• The scope of a variable is the portion of a program
in which the variable can be used.

• By default, the scope of a variable in Java:


• begins at the point at which it is declared
• ends at the end of the innermost block
that encloses the declaration
public class MyProgram2 {
public static void main(String[] args) {
System.out.println("Welcome!");
System.out.println("Let's do some math!");
int j = 10;
scope of j
System.out.println(j / 5);
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 85


Special Case: for Loops and Variable Scope
• When a variable is declared in the initialization clause of
a for loop, its scope is limited to the loop.

• Example:
public static void myMethod() {
for (int i = 0; i < 5; i++) {
int j = i * 3;
scope of i
System.out.println(j);
}
// the following line won't compile
System.out.print(i);
System.out.println(" values were printed.");
}

Special Case: for Loops and Variable Scope (cont.)


• To allow i to be used outside the loop, we need to
declare it outside the loop:

• Example:
public static void myMethod() {
int i;
for (i = 0; i < 5; i++) {
int j = i * 3;
System.out.println(j);
} scope
of i
// now this will compile
System.out.print(i);
System.out.println(" values were printed.");
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 86


Special Case: for Loops and Variable Scope (cont.)
• Limiting the scope of a loop variable allows us to use the
standard loop templates multiple times in the same method.

• Example:
public static void myMethod() {
for (int i = 0; i < 5; i++) {
int j = i * 3;
scope of
System.out.println(j);
first i
}

for (int i = 0; i < 7; i++) {


System.out.println("Go Crimson!"); scope of
} second i
}

Review: Simple Repetition Loops


• Recall our two templates for performing N repetitions:
for (int i = 0; i < N; i++) {
// code to be repeated
}

for (int i = 1; i <= N; i++) {


// code to be repeated
}

• How may repetitions will each of the following perform?


for (int i = 1; i <= 15; i++) {
System.out.println("Hello");
System.out.println("How are you?");
}
for (int i = 0; i < 2*j; i++) {

}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 87


More Practice: Tracing a Nested for Loop
for (int i = 1; i <= 3; i++) {
for (int j = 0; j < 2*i + 1; j++) {
System.out.print("*");
}
System.out.println();
}
i i <= 3 j j < 2*i + 1 output

Case Study: Drawing a Complex Figure


• Here's the figure:
()
(())
((()))
(((())))
========
|::::::|
|::::|
|::|
|::|
|::|
|::|
+==+

• To begin with, we'll focus on creating this exact figure.

• Then we'll modify our code so that the size of the figure
can easily be changed.
• we'll use for loops to allow for this

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 88


Problem Decomposition
• We begin by breaking the problem into subproblems,
looking for groups of lines that follow the same pattern:
()
(())  flame
((()))
(((())))
========  rim of torch
|::::::|
|::::|  top of torch

|::|
|::|
|::|  handle of torch
|::|
+--+  bottom of torch

Problem Decomposition (cont.)


• This gives us the following initial pseudocode:
draw the flame
() draw the rim of the torch
(()) draw the top of the torch
((())) draw the handle of the torch
(((()))) draw the bottom of the torch

======== • This is a high-level description


of what needs to be done.
|::::::|
|::::|
• We'll gradually expand the pseudocode
|::| into more and more detailed instructions –
|::| until we're able to implement them in Java.
|::|
|::|
+--+

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 89


Drawing the Flame
• Let's begin by refining our specification 1 ()
for drawing the flame. 2 (())
3 ((()))
4(((())))
• Here's our initial pseudocode for this task:
for (each of 4 lines) {
print some spaces (possibly 0)
print some left parentheses
print some right parentheses
go to a new line
}

• We need formulas for how many spaces and parens should


be printed on a given line.

Finding the Formulas


• To begin with, we: 1 ()
2 (())
• number the lines in the flame
3 ((()))
• form a table of the number of spaces 4(((())))
and parentheses on each line:

line spaces parens (each type)


1 3 1
2 2 2
3 1 3
4 0 4

• Then we find the formulas.


• assume the formulas are linear functions of the line number:
c1*line + c2
where c1 and c2 are constants
• parens = ?
• spaces = ?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 90


Refining the Pseudocode
• Given these formulas, we can refine our pseudocode:
for (each of 4 lines) {
print some spaces (possibly 0)
print some left parentheses
print some right parentheses
go to a new line
}

for (line going from 1 to 4) {


print 4 – line spaces
print line left parentheses
print line right parentheses
go to a new line
}

Implementing the Pseudocode in Java


• We use nested for loops:
for (line going from 1 to 4) {
print 4 – line spaces
print line left parentheses
print line right parentheses
go to a new line
}

for (int line = 1; line <= 4; line++) {


for (int i = 0; i < 4 - line; i++) {
System.out.print(" ");
}
for (int i = 0; i < line; i++) {
System.out.print("(");
}
for (int i = 0; i < line; i++) {
System.out.print(")");
}
System.out.println();
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 91


A Method for Drawing the Flame
• We put the code in its own static method, and add some
explanatory comments:
public static void drawFlame() {
for (int line = 1; line <= 4; line++) {
// spaces to the left of the current line
for (int i = 0; i < 4 - line; i++) {
System.out.print(" ");
}

// left and right parens on the current line


for (int i = 0; i < line; i++) {
System.out.print("(");
}
for (int i = 0; i < line; i++) {
System.out.print(")");
}

System.out.println();
}
}

Drawing the Top of the Torch


• What's the initial pseudocode for this task? 1|::::::|
2 |::::|
for (each of 2 lines) {

• Here's a table for the number of spaces and number of colons:


line spaces colons
1 0 6
2 1 4
• spaces = ?
• colons decreases by 2 as line increases by 1
 colons = -2*line + c2 for some number c2
• try different values, and eventually get: colons = ?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 92


Refining the Pseudocode
• Once again, we use the formulas to refine our pseudocode:
for (each of 2 lines) {
print some spaces (possibly 0)
print a single vertical bar
print some colons
print a single vertical bar
go to a new line
}

for (line going from 1 to 2) {


print line - 1 spaces
print a single vertical bar
print -2*line + 8 colons
print a single vertical bar
go to a new line
}

A Method for Drawing the Top of the Torch


public static void drawTop() {
for (int line = 1; line <= 2; line++) {
// spaces to the left of the current line
for (int i = 0; i < line - 1; i++) {
System.out.print(" ");
}

// bars and colons on the current line


System.out.print("|");
for (int i = 0; i < –2*line + 8; i++) {
System.out.print(":");
}
System.out.print("|");

System.out.println();
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 93


Drawing the Rim
• This always has only one line, ========
so we don't need nested loops.

• However, we still need a single loop,


because we want to be able to scale
the size of the figure.

• What should the code look like?

for ( ; ; ) {

• This code also goes in its own method, called drawRim()

Incremental Development
• We take similar steps to implement methods for the
remaining subtasks.

• After completing a given method, we test and debug it.

• The main method just calls the methods for the subtasks:
public static void main(String[] args) {
drawFlame();
drawRim();
drawTop();
drawHandle();
drawBottom();
}

• See the example program DrawTorch.java

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 94


Using Class Constants
• To make the torch larger or smaller, we'd need to make
many changes.
• the size of the figure is hard-coded into most methods

• To make the program more flexible, we can store info. about


the figure's dimensions in one or more class constants.
• like variables, but their values are fixed
• can be used throughout the program

Using Class Constants (cont.)


2*2 2*2
• We only need one constant for the torch.
()
• for the default size, it equals 2 (()) 2*2
• its connection to some of the dimensions ((()))
is shown at right (((())))
========
4*2
|::::::|
• We declare it at the very start of the class: |::::| 2
public class DrawTorch2 {
public static final int SCALE_FACTOR = 2;
...

• General syntax:
public static final type name = expression;

• conventions:
• capitalize all letters in the name
• put an underscore ('_') between multiple words

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 95


Scaling the Figure
• Here are some other versions of the figure:
() ()
(()) (())
((())) ====
(((()))) |::|
((((())))) ||
(((((()))))) ||
============ ++
|::::::::::|
|::::::::| SCALE_FACTOR = 1
|::::::|
|::::|
|::::|
|::::|
|::::|
|::::|
|::::|
+====+
SCALE_FACTOR = 3

Revised Method for Drawing the Flame


• We replace the two 4s with 2*SCALE_FACTOR:
public static void drawFlame() {
for (int line = 1; line <= 2*SCALE_FACTOR; line++) {
// spaces to the left of the flame
for (int i = 0; i < 2*SCALE_FACTOR - line; i++) {
System.out.print(" ");
}

// the flame itself, both left and right halves


for (int i = 0; i < line; i++) {
System.out.print("(");
}
for (int i = 0; i < line; i++) {
System.out.print(")");
} 2*2 2*2
System.out.println(); ()
} (()) 2*2
} ((()))
(((())))

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 96


Making the Rim Scaleable
• How does the width of the rim depend on SCALE_FACTOR?
() () ()
(()) (()) (())
((())) ((())) ====
(((()))) (((())))
((((())))) ========
(((((())))))
============

• Use a table!
SCALE_FACTOR width of rim
1 4
2 8
3 12
width of rim = ?

Revised Method for Drawing the Rim


• Original version (for the default size):
public static void drawRim() {
for (int i = 0; i < 8; i++) {
System.out.print("=");
}
System.out.println();
}

• Scaleable version:
public static void drawRim() {
for (int i = 0; i < 4*SCALE_FACTOR; i++) {
System.out.print("=");
}
System.out.println();
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 97


Making the Top of the Torch Scaleable
• For SCALE_FACTOR = 2, we got:
1|::::::|
number of lines = 2 2 |::::|
spaces = line – 1
colons = -2 * line + 8

• What about SCALE_FACTOR = 3? 1|::::::::::|


2 |::::::::|
line spaces colons 3 |::::::|
1 0 10
2 1 8
3 2 6
number of lines = 3
spaces = ?
colons = ?

• in general, number of lines = ?

Making the Top of the Torch Scaleable (cont.)


• Compare the two sets of formulas:
SCALE_FACTOR = 2 SCALE_FACTOR = 3
spaces = line – 1 spaces = line – 1
colons = -2 * line + 8 colons = -2 * line + 12

• There's no change in:


• the formula for spaces
• the first constant in the formula for colons

• Use a table for the second constant:


SCALE_FACTOR constant
2 8
3 12
constant = ?

• Scaleable formulas: spaces = line – 1


colons = ?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 98


Revised Method for Drawing the Top of the Torch
public static void drawTop() {
for (int line = 1; line <= SCALE_FACTOR; line++) {
// spaces to the left of the current line
for (int i = 0; i < line - 1; i++) {
System.out.print(" ");
}

// bars and colons on the current line


System.out.print("|");
for (int i = 0; i < -2*line + 4*SCALE_FACTOR; i++) {
System.out.print(":");
}
System.out.print("|");

System.out.println();
}
}

Practice: The Torch Handle ()


(())
• Pseudocode for default size: ((()))
(((())))
========
|::::::|
|::::|
1 |::|
2 |::|
3 |::|
4 |::|
• Java code for default size: +==+
public static void drawHandle() {

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 99


Practice: Making the Handle Scaleable
• We again compare two different sizes. |::::::|
|::::|
• SCALE_FACTOR # lines spaces colons
1 |::|
2 |::|
2 4 2 2 3 |::|
3 6 3 4 4 |::|

• number of lines = ? |::::::::::|


spaces = ? |::::::::|
|::::::|
colons = ? 1 |::::|
2 |::::|
3 |::::|
4 |::::|
5 |::::|
6 |::::|

Revised Method for Drawing the Handle


• What changes do we need to make?

public static void drawHandle() {


for (int line = 1; line <= 4; line++) {
for (int i = 0; i < 2; i++) {
System.out.print(" ");
}
System.out.print("|");
for (int i = 0; i < 2; i++) {
System.out.print(":");
}
System.out.println("|");
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 100


Extra Practice: Printing a Pattern, version 3
• Print a triangular pattern with lines containing n, n – 1, …, 1
asterisks.
• example:
Enter a positive integer: 3
***
**
*

• How would we use a nested loop to do this?


for ( ) {
for ( ) {
System.out.print("*");
}
System.out.println();
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 101


Unit 3, Part 1

Methods with Parameters


and Return Values

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Review: Static Methods


• We've seen how we can use static methods to:
1. capture the structure of a program – breaking a task
into subtasks
2. eliminate code duplication

• Thus far, our methods have been limited in their ability


to accomplish these tasks.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 102


A Limitation of Simple Static Methods
• For example, in our DrawTorch program, there are several
for loops that each print a series of spaces, such as:
for (int i = 0; i < 4 - line; i++) {
System.out.print(" ");
}

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


System.out.print(" ");
}

• However, despite the fact that all of these loops print spaces,
we can't replace them with a method that looks like this:
public static void printSpaces() {

Why not?

Parameters
• In order for a method that prints spaces to be useful,
we need one that can print an arbitrary number of spaces.

• Such a method would allow us to write commands like these:


printSpaces(5);
printSpaces(4 - line);
where the number of spaces to be printed is specified
between the parentheses.

• To do so, we write a method that has a parameter:


public static void printSpaces(int numSpaces) {
for (int i = 0; i < numSpaces; i++) {
System.out.print(" ");
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 103


Parameters (cont.)
• A parameter is a special type of variable that allows us
to pass information into a method.

• Consider again this method:


public static void printSpaces(int numSpaces) {
for (int i = 0; i < numSpaces; i++) {
System.out.print(" ");
}
}

• When we execute a method call like


printSpaces(10);

the expression specified between the parentheses:


• is evaluated
• is assigned to the parameter
• can thereby be used by the code inside the method

Parameters (cont.)
public static void printSpaces(int numSpaces) {
for (int i = 0; i < numSpaces; i++) {
System.out.print(" ");
}
}

• Here's an example with a more


complicated expression:
int line = 2;
printSpaces(4 - line);
4 - 2
2

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 104


A Note on Terminology
• The term parameter is used for both:
• the variable specified in the method header
• known as a formal parameter
• the value that you specify when you make the method call
• known as an actual parameter
• also known as an argument
formal parameter

public static void printSpaces(int numSpaces) {


for (int i = 0; i < numSpaces; i++) {
System.out.print(" ");
}
}
actual parameter / argument

printSpaces(10);

Parameters and Generalization


• Parameters allow us to generalize a task.

• They allow us to write one method that can perform


a family of related tasks – instead of writing a separate
method for each separate task.

print5Spaces()
print10Spaces()
printSpaces(parameter)
print20Spaces()
print100Spaces()

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 105


Representing Individual Characters
• So far we've learned about two data types:
• int
• double

• The char type is used to represent individual characters.

• To specify a char literal, we surround the character


by single quotes:
• examples: 'a' 'Z' '0' '7' '?' '\\'
• can only represent single characters
• don’t use double-quotes!
"a" is a string, not a character

Methods with Multiple Parameters


• Here's a method with more than one parameter:
public static void printChars(char ch, int num) {
for (int i = 0; i < num; i++) {
System.out.print(ch);
}
}

• Example of calling this method:

printChars(' ', 10);

• Notes:
• the parameters (both formal and actual) are separated
by commas
• each formal parameter must be preceded by its type
• the actual parameters are evaluated and assigned to
the corresponding formal parameters

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 106


Example of Using a Method with Parameters
public static void drawFlame() {
for (int line = 1; line <= 4; line++) {
for (int i = 0; i < 4 - line; i++) {
System.out.print(" ");
}
for (int i = 0; i < line; i++) {
System.out.print("(");
}
for (int i = 0; i < line; i++) {
System.out.print(")");
}
System.out.println();
}
}
replace nested loops with method calls

public static void drawFlame() {


for (int line = 1; line <= 4; line++) {
printChars(' ', 4 - line);
printChars('(', line);
printChars(')', line);
System.out.println();
}
}

Recall: Variable Scope


• The scope of a variable is the portion of a program
in which the variable can be used.

• By default, the scope of a variable in Java:


• begins at the point at which it is declared
• ends at the end of the innermost block
that encloses the declaration

public static void printResults(int a, int b) {


System.out.println("Here are the stats:");

int sum = a + b;
System.out.print("sum = ");
scope of sum
System.out.println(sum);

double avg = (a + b) / 2.0;


System.out.print("average = "); scope of
System.out.println(avg); avg
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 107


Special Case: Parameters and Variable Scope
• What about the parameters of a method?
• they do not follow the default scope rules!
• their scope is limited to their method

public class MyClass {


public static void printResults(int a, int b) {
System.out.println("Here are the stats:");
scope
int sum = a + b; of
System.out.print("sum = "); a and b
System.out.println(sum);

double avg = (a + b) / 2.0;


System.out.print("average = ");
System.out.println(avg);
}

static int c = a + b; // does not compile!


}

Practice with Scope


public static void drawRectangle(int height) {
for (int i = 0; i < height; i++) {
// which variables could be used here?
int width = height * 2;
for (int j = 0; j < width; j++) {
System.out.print("*");
// what about here?
}
// what about here?
System.out.println();
}
// what about here?
}

public static void repeatMessage(int numTimes) {


// what about here?
for (int i = 0; i < numTimes; i++) {
System.out.println("What is your scope?");
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 108


Practice with Parameters
public static void printValues(int a, int b) {
System.out.println(a + " " + b);
b = 2 * a;
System.out.println("b" + b);
}
public static void main(String[] args) {
int a = 2;
int b = 3;
printValues(b, a);
printValues(7, b * 3);
System.out.println(a + " " + b);
}
• What's the output?

A Limitation of Parameters
• Parameters allow us to pass values into a method.

• They don't allow us to get a value out of a method.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 109


A Limitation of Parameters (cont.)
• Example: using a method to compute the opposite of a number

• This won't work:


public static void opposite(int number) {
number = number * -1;
}

public static void main(String[] args) {


// read in points from the user
opposite(points);

}

• the opposite method changes the value of number,


but number can't be used outside of that method
• the method doesn't change the value of points

Methods That Return a Value


• To compute the opposite of a number, we need a method
that's able to return a value.

• Such a method would allow us to write statements like this:


int penalty = opposite(points);

• The value returned by the method would replace


the method call in the original statement.

• Example:
int points = 10;
int penalty = opposite(points);

int penalty = -10; // after the method completes

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 110


Defining a Method that Returns a Value
• Here's a method that computes and returns the opposite
of a number:
public static int opposite(int number) {
return number * -1;
}

• In the header of the method, void is replaced by int,


which is the type of the returned value.

• The returned value is specified using a return statement.


Syntax:
return expression;
• expression is evaluated
• the resulting value replaces the method call in
the statement that called the method

Defining a Method that Returns a Value (cont.)


• The complete syntax for the header of a static method is:
public static returnType name(type1 param1, type2 param2, …)

• Note: a method call is a type of expression!


• it evaluates to its return value
int opp = opposite(10);

int opp = -10;

• In our earlier methods, the return type was always void:


public static void printSpaces(int numSpaces) {
...
This is a special return type that indicates that no value
is returned.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 111


Flow of Control with Methods That Return a Value
• The flow of control jumps to a method until it returns.
• The flow jumps back, and the returned value replaces the call.
• Example:
int num = 10;
int opp = opposite(num);
System.out.println(opp);

method instruction 1
int num = 10;

method instruction 2
int opp = opposite(num); .
.
.
after the method returns

System.out.println(opp);
return statement

Flow of Control with Methods That Return a Value


• The flow of control jumps to a method until it returns.
• The flow jumps back, and the returned value replaces the call.
• Example:
int num = 10;
int opp = opposite(num);
System.out.println(opp);

method instruction 1
int num = 10;

method instruction 2
int opp = -10; .
.
.
after the method returns

System.out.println(opp);
return statement

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 112


Returning vs. Printing
• Instead of returning a value, we could write a method
that prints the value:
public static void printOpposite(int number) {
System.out.println(number * -1);
}

• However, a method that returns a value is typically


more useful.

• With such a method, you can still print the value by printing
what the method returns:
System.out.println(opposite(num));
• the return value replaces the method call and is printed

• In addition, you can do other things besides printing:


int penalty = opposite(num);

Practice: Computing the Volume of a Cone


• volume of a cone = base * height
3

• Let's write a method named coneVol for computing it.


• parameters and their types?
• return type?

• method definition:
public static ________ coneVol(___________________________) {

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 113


The Math Class
• Java's built-in Math class contains static methods for
mathematical operations.

• These methods return the result of applying the operation


to the parameters.

• Examples:
round(double value) – returns the result of rounding
value to the nearest integer
abs(double value) – returns the absolute value of value
pow(double base, double expon) – returns the result
of raising base to the expon power
sqrt(double value) – returns the square root of value

• For other examples, use the Java API on the Resources page.

The Math Class (cont.)


• To use a static method defined in another class,
we need to use the name of the class when we call it.

• We use what's known as dot notation.

• Syntax:
ClassName.methodName(param1, param2, …)

• Example:

double maxVal = Math.pow(2, numBits - 1) – 1;

class method actual


name name parameters

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 114


*** Common Mistake ***
• Consider this alternative opposite method:
public static int opposite(int number) {
number = number * -1;
return number;
}

• What's wrong with the following code that uses it?


public class OppositeFinder {
public static void main(String[] args) {
int number = 10;
opposite(number);
System.out.print("opposite = ");
System.out.println(number);
}

Keeping Track of Variables


• Consider again the alternative opposite method:
public static int opposite(int number) {
number = number * -1;
return number;
}

• Here's some code that uses it correctly:


public class OppositeFinder {
public static void main(String[] args) {
int number = 10;
int otherNumber = opposite(number);
...
}

• There are two different variables named number.


How does the runtime system distinguish between them?
• More generally, how does it keep track of variables?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 115


Keeping Track of Variables (cont.)
• When you make a method call, the Java runtime sets aside
a block of memory known as the frame of that method call.
main

number otherNumber note: we're ignoring main's parameter for now

• The frame is used to store:


• the formal parameters of the method
• any local variables – variables declared within the method

• A given frame can only be accessed by statements that are


part of the corresponding method call.

Keeping Track of Variables (cont.)


• When a method (method1) calls another method (method2),
the frame of method1 is set aside temporarily.
• method1's frame is "covered up" by the frame of method2
• example: after main calls opposite, we get:
main
main
opposite
maxOfThree
number otherNumber
a b c max
number

• When the runtime system encounters a variable, it uses


the one from the current frame (the one on top).

• When a method returns, its frame is removed, which


"uncovers" the frame of the method that called it.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 116


Example: Tracing Through a Program
main
• A frame is created
number otherNumber
for the main method.

public class OppositeFinder {


public static void main(String[] args) {
int number = 10;
int otherNumber = opposite(number);
System.out.print("opposite = ");
System.out.println(otherNumber);
}

public static int opposite(int number) {


number = number * -1;
return number;
}
}

Example: Tracing Through a Program


main
number otherNumber
10

public class OppositeFinder {


public static void main(String[] args) {
int number = 10;
int otherNumber = opposite(number);
System.out.print("opposite = ");
System.out.println(otherNumber);
}

public static int opposite(int number) {


number = number * -1;
return number;
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 117


Example: Tracing Through a Program
main
number otherNumber
10

public class OppositeFinder {


public static void main(String[] args) {
int number = 10;
int otherNumber = opposite(number);
System.out.print("opposite = ");
System.out.println(otherNumber);
}

public static int opposite(int number) {


number = number * -1;
return number;
}
}

Example: Tracing Through a Program


main
opposite • A frame is created
number otherNumber for the opposite method,
number and that frame "covers
up" the frame for main.

public class OppositeFinder {


public static void main(String[] args) {
int number = 10;
int otherNumber = opposite(10);
System.out.print("opposite = ");
System.out.println(otherNumber);
}

public static int opposite(int number) {


number = number * -1;
return number;
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 118


Example: Tracing Through a Program
main
opposite • The actual parameter
number otherNumber is passed in and is
number assigned to the formal
10 parameter.

public class OppositeFinder {


public static void main(String[] args) {
int number = 10;
int otherNumber = opposite(10);
System.out.print("opposite = ");
System.out.println(otherNumber);
}

public static int opposite(int number) {


number = number * -1;
return number;
}
}

Example: Tracing Through a Program


main
opposite
number otherNumber
number
10

public class OppositeFinder {


public static void main(String[] args) {
int number = 10;
int otherNumber = opposite(10);
System.out.print("opposite = ");
System.out.println(otherNumber);
}

public static int opposite(int number) {


number = number * -1;
return number;
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 119


Example: Tracing Through a Program
main
opposite
number otherNumber
number
-10

public class OppositeFinder {


public static void main(String[] args) {
int number = 10;
int otherNumber = opposite(10);
System.out.print("opposite = ");
System.out.println(otherNumber);
}

public static int opposite(int number) {


number = -10;
return number;
}
}

Example: Tracing Through a Program


main
• opposite returns,
number otherNumber
which removes its frame.
10
• The variable number
in main's frame hasn't
been changed!
public class OppositeFinder {
public static void main(String[] args) {
int number = 10;
int otherNumber = opposite(10);
System.out.print("opposite = ");
System.out.println(otherNumber);
}

public static int opposite(int number) {


number = -10;
return -10;
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 120


Example: Tracing Through a Program
main
• The returned value
number otherNumber
replaces the
10
method call.

public class OppositeFinder {


public static void main(String[] args) {
int number = 10;
int otherNumber = opposite(10);
System.out.print("opposite = ");
System.out.println(otherNumber);
}

public static int opposite(int number) {


number = -10;
return -10;
}
}

Example: Tracing Through a Program


main
number otherNumber
10 -10

public class OppositeFinder {


public static void main(String[] args) {
int number = 10;
int otherNumber = -10;
System.out.print("opposite = ");
System.out.println(otherNumber);
}

public static int opposite(int number) {


number = -10;
return -10;
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 121


Example: Tracing Through a Program
main
number otherNumber
10 -10

public class OppositeFinder {


public static void main(String[] args) {
int number = 10;
int otherNumber = -10;
System.out.print("opposite = ");
System.out.println(otherNumber);
}

public static int opposite(int number) {


number = -10;
return -10;
}
}

Example: Tracing Through a Program


• main returns, which
removes its frame.

public class OppositeFinder {


public static void main(String[] args) {
int number = 10;
int otherNumber = -10;
System.out.print("opposite = ");
System.out.println(-10);
}

public static int opposite(int number) {


number = -10;
return -10;
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 122


Practice
• What is the output of the following program?
public class MethodPractice {
public static int triple(int x) {
x = x * 3;
return x;
}
public static void main(String[] args) {
int y = 2;
y = triple(y);
System.out.println(y);
triple(y);
System.out.println(y);
}
}

foo
More Practice x | y
public class Mystery {
public static int foo(int x, int y) {
y = y + 1;
x = x + y;
System.out.println(x + " " + y);
return x;
}
public static void main(String[] args) {
int x = 2; main
int y = 0; x | y
y = foo(y, x);
System.out.println(x + " " + y);

foo(x, x); output


System.out.println(x + " " + y);

System.out.println(foo(x, y));
System.out.println(x + " " + y);
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 123


From Unstructured to Structured
public class TwoTriangles {
public static void main(String[] args) {
char ch = '*'; // character used in printing
int smallBase = 5; // base length of smaller triangle

// Print the small triangle.


for (int line = 1; line <= smallBase; line++) {
for (int i = 0; i < line; i++) {
System.out.print(ch);
}
System.out.println();
}

// Print the large triangle.


for (int line = 1; line <= 2 * smallBase; line++) {
for (int i = 0; i < line; i++) {
System.out.print(ch);
}
System.out.println();
}
}
}

From Unstructured to Structured (cont.)


public class TwoTriangles {
public static void main(String[] args) {
char ch = '*'; // character used in printing
int smallBase = 5; // base length of smaller triangle

// Print the small triangle.

printTriangle(_________________________________);

// Print the large triangle.

printTriangle(_________________________________);
}

public static void printTriangle(_______________________) {

}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 124


Unit 3, Part 2

Using Objects from Existing Classes

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Combining Data and Operations


• The data types that we've seen thus far are referred to as
primitive data types.
• int, double, char
• several others

• Java allows us to use another kind of data known as an object.

• An object groups together:


• one or more data values (the object's fields)
• a set of operations (the object's methods)

• Objects in a program are often used to model


real-world objects.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 125


Combining Data and Operations (cont.)
• Example: an Address object
• possible fields: street, city, state, zip
• possible operations: get the city, change the city,
check if two addresses are equal

• Here are two ways to visualize an Address object:

street "111 Cummington St."


street "111 Cummington St."
city "Boston" fields
city "Boston"
state "MA"
state "MA"
zip "02215"
zip "02215"
getCity()
changeCity() methods

Classes as Blueprints
• We've been using classes as containers for our programs.

• A class can also serve as a blueprint – as the definition of a


new type of object.

• The objects of a given class are built according to its blueprint.

• Another analogy:
• class = cookie cutter
objects = cookies

• The objects of a class are also referred to as instances


of the class.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 126


Class vs. Object
• The Address class is a blueprint:
public class Address {
// definitions of the fields
...

// definitions of the methods


...
}

• Address objects are built according to that blueprint:


street "111 Cummington St."
city "Boston" street "240 West 44th Street"

state "MA" city "New York"

zip "02215" state "NY"


street "1600 Pennsylvania Ave."
zip "10036"
city "Washington"
state "DC"
zip "20500"

Using Objects from Existing Classes


• Later in the course, you'll learn how to create your own
classes that act as blueprints for objects.

• For now, we'll focus on learning how to use objects from


existing classes.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 127


String Objects
• In Java, a string (like "Hello, world!") is actually
represented using an object.
• data values: the characters in the string
• operations: get the length of the string, get a substring, etc.

• The String class defines this type of object:


public class String {
// definitions of the fields
...

// definitions of the methods


...
}

• Individual String objects are instances of the String class:

Perry Hello object

Variables for Objects


• When we use a variable to represent an object,
the type of the variable is the name of the object's class.

• Here's a declaration of a variable for a String object:


String name;

type variable name


(the class name)

• we capitalize String, because it's a class name

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 128


Creating String Objects
• One way to create a String object is to specify a string literal:
String name = "Perry Sullivan";

• We create a new String from existing Strings when we


use the + operator to perform concatenation:
String firstName = "Perry";
String lastName = "Sullivan";
String fullName = firstName + " " + lastName;

• Recall that we can concatenate a String with other types


of values:
String msg = "Perry is " + 6;
// msg now represents "Perry is 6"

Using an Object's Methods


• An object's methods are different from the static methods
that we've seen thus far.
• they're called non-static or instance methods

• An object's methods belong to the object.


They specify the operations that the object can perform.

• To use a non-static method, we have to specify the object


to which the method belongs.
• use dot notation, preceding the method name
with the object's variable:
String firstName = "Perry";
int len = firstName.length();

• Using an object's method is like sending a message


to the object, asking it to perform that operation.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 129


The API of a Class
• The methods defined within a class are known as the API
of that class.
• API = application programming interface

• We can consult the API of an existing class to determine


which operations are supported.

• The API of all classes that come with Java is available here:
https://docs.oracle.com/javase/8/docs/api/

• there's a link on the resources page of the course website

Consulting the Java API

select
the
package
name
(optional)
String
is in
java.lang

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 130


Consulting the Java API

select
the
class
name

Consulting the Java API (cont.)


• Scroll down to see a summary of the available methods:

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 131


Consulting the Java API (cont.)
• Clicking on a method name gives you more information:

method header

behavior

• From the header, we can determine:


• the return type: int
• the parameters we need to supply:
the empty () indicates that length has no parameters

Numbering the Characters in a String


• The characters are numbered from left to right, starting from 0.
01 234
Perry
• The position of a character in a string is known as its index.
• 'P' has an index of 0 in "Perry"
• 'y' has an index of 4

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 132


substring Method

String substring(int beginIndex, int endIndex)


• return type: ?
• parameters: ?
• behavior: returns the substring that:
• begins at beginIndex
• ends at endIndex – 1

substring Method (cont.)


• To extract a substring of length N, you can just figure out
beginIndex and do:
substring(beginIndex, beginIndex + N )
• example: consider again this string:
String name = "Perry Sullivan";
To extract a substring containing the first 5 characters,
we can do this:
String first = name.substring(0, 5);

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 133


Review: Calling a Method
• Consider this code fragment:
String name = "Perry Sullivan";
int start = 6;
String last = name.substring(start, start + 8);

• Steps for executing the method call:


1. the actual parameters are evaluated to give:
String last = name.substring(6, 14);
2. a frame is created for the method, and the
actual parameters are assigned to the formal parameters
3. flow of control jumps to the method, which creates and
returns the substring "Sullivan"
4. flow of control jumps back, and the returned value
replaces the method call:
String last = "Sullivan";

How should we fill in the blank?

String s = "Strings have methods inside them!";


int len = s.length();
__________________ // get the last character in s

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 134


charAt Method

• The charAt() method that we use for indexing returns a


char, not a String.

• We have to be careful when we use its return value!


• example: what does this print?
String name = "Perry Sullivan";
System.out.println(name.charAt(0) +
name.charAt(6));

charAt Method
• Here's how we can fix this:
String name = "Perry Sullivan";
System.out.println(name.charAt(0) + "" +
name.charAt(6));

System.out.println('P' + "" +
'S');

System.out.println("PS");

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 135


Another String Method
String toUpperCase()
returns a new String in which all of the letters in the
original String are converted to upper-case letters

• Example:
String warning = "Start the problem set ASAP!";
System.out.println(warning.toUpperCase());

System.out.println("START THE PROBLEM SET ASAP!");

• toUpperCase() creates and returns a new String.


It does not change the original String.

• In fact, it's never possible to change an existing String object.

• We say that Strings are immutable objects.

indexOf Method
int indexOf(char ch)
• return type: int
• parameter list: (char ch)
• returns:
• the index of the first occurrence of ch in the string
• -1 if the ch does not appear in the string
• examples:
String name = "Perry Sullivan";
System.out.println(name.indexOf('r'));
System.out.println(name.indexOf('X'));

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 136


The Signature of a Method
• The signature of a method consists of:
• its name
• the number and types of its parameters

public String substring(int beginIndex, int endIndex)

the signature

• A class cannot include two methods with the same signature.

Two Methods with the Same Name


• There are actually two String methods named substring:
String substring(int beginIndex, int endIndex)

String substring(int beginIndex)


• returns the substring that begins at beginIndex and
continues to the end of the string

• Do these two methods have the same signature?

• Giving two methods the same name is known as


method overloading.

• When you call an overloaded method, the compiler uses


the number and types of the actual parameters to figure out
which version to use.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 137


Console Input Using a Scanner Object
• We’ve been printing text in the console window.

• You can also ask the user to enter a value in that window.
• known as console input

• To do so, we use a type of object known as a Scanner.


• recall PS 2

Packages
• Java groups related classes into packages.

• Many classes are part of the java.lang package.


• examples: String, Math
• We don't need to tell the compiler where to find
these classes.

• If a class is in another package, we need to use an


import statement so that the compiler will be able to find it.
• put it before the definition of the class

• The Scanner class is in the java.util package, so we do this:


import java.util.*;
public class MyProgram {
...

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 138


Creating an Object
• String objects are different from other objects, because
we're able to create them using literals.

• To create an object, we typically use a special method


known as a constructor.

• Syntax:
variable = new ClassName(parameters);
or
type variable = new ClassName(parameters);

• To create a Scanner object for console input:


Scanner console = new Scanner(System.in);

the parameter tells the constructor that we want the Scanner


to read from the standard input (i.e., the keyboard)

Scanner Methods: A Partial List


• String next()
• read in a single "word" and return it

• int nextInt()
• read in an integer and return it

• double nextDouble()
• read in a floating-point value and return it

• String nextLine()
• read in a "line" of input (could be multiple words)
and return it

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 139


Example of Using a Scanner Object
• To read an integer from the user:
Scanner console = new Scanner(System.in);
int numGrades = console.nextInt();

• The second line causes the program to pause until the user
types in an integer followed by the [ENTER] key.

• If the user only hits [ENTER], it will continue to pause.

• If the user enters an integer, it is returned and assigned


to numGrades.

• If the user enters a non-integer, an exception is thrown


and the program crashes.

Example Program: GradeCalculator


import java.util.*;
public class GradeCalculator {
public static void main(String[] args) {
Scanner console = new Scanner(System.in);

System.out.print("Points earned: ");


int points = console.nextInt();
System.out.print("Possible points: ");
int possiblePoints = console.nextInt();

double grade = points/(double)possiblePoints;


grade = grade * 100.0;

System.out.println("grade is " + grade);


}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 140


Important Note About Console Input
• When writing an interactive program that involves user input
in methods other than main, you should:
• create a single Scanner object in the first line of the main
method
• pass that object into any other method that needs it

• This allows you to avoid creating multiple objects that all


do the same thing.

• It also facilitates our grading, because it allows us to provide


a series of inputs using a file instead of the keyboard.

Important Note About Console Input (cont.)


• Example:
public class MyProgram {
public static void main(String[] args) {
Scanner console = new Scanner(System.in);
String str1 = getString(console);
String str2 = getString(console);
System.out.println(str1 + " " + str2);
}

public static String getString(Scanner console) {


System.out.print("Enter a string: ");
String str = console.next();
return str;
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 141


What's Wrong with the Following?
import java.util.*;
public class LengthConverter {
public static void main(String[] args) {
Scanner console = new Scanner(System.in);
int cm = (int)(getInches(console) * 2.54);
System.out.println(getInches(console)
+ " inches = " + cm + " cm");
}

public static int getInches(Scanner console) {


System.out.print("Enter a length in inches: ");
int inches = console.nextInt();
return inches;
}
}

Exercise: Analyzing a Name: First Version


public class NameAnalyzer {
public static void main(String[] args) {
String name = "Perry Sullivan";
System.out.println("full name = " + name);

int length = name.length();


System.out.println("length = " + length);

String first = name.substring(0, 5);


System.out.println("first name = " + first);

String last = name.substring(6);


System.out.println("last name = " + last);
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 142


Making the Program More General
• Would the code work if we used a different name?

import java.util.*;

public class NameAnalyzer {


public static void main(String[] args) {
Scanner console = new Scanner(System.in);
String name = console.nextLine();
System.out.println("full name = " + name);

int length = name.length();


System.out.println("length = " + length);

String first = name.substring(0, 5);


System.out.println("first name = " + first);

String last = name.substring(6);


System.out.println("last name = " + last);
}
}

Breaking Up a Name
• Given a string of the form "firstName lastName", how can
we get the first and last names, without knowing how long it is?

• Pseudocode for what we need to do:

• What String methods can we use? Consult the API!

• Code:

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 143


Static Methods for Breaking Up a Name
• How could we rewrite our name analyzer to use
separate methods for extracting the first and last names?

public static _________ firstName(_______________) {

public static _________ lastName(_______________) {

Using the Static Methods


• Given the methods from the previous slide, what would the
main method now look like?

public static void main(String[] args) {


Scanner console = new Scanner(System.in);
String name = console.nextLine();
System.out.println("full name = " + name);

int length = name.length();


System.out.println("length = " + length);

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 144


Processing a String One Character at a Time
• Write a method for printing the name vertically, one char per line.
import java.util.*;
public class NameAnalyzer {
public static void main(String[] args) {
Scanner console = new Scanner(System.in);
String name = console.nextLine();
System.out.println("full name = " + name);
...
printVertical(name);
}
public static _____ printVertical(_______________){

for (int i = 0; i < _______________; i++) {

}
}
}

Scanner Objects and Tokens


• Most Scanner methods read one token at a time.

• Tokens are separated by whitespace (spaces, tabs, newlines).


• example: if the user enters the line
wow, I slept for 9 hours!\n
there are six tokens:
newline character,
• wow,
which you get when
• I you hit [ENTER]
• slept
• for
• 9
• hours!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 145


Scanner Objects and Tokens (cont.)
• Consider the following lines of code:
System.out.print("Enter the length and width: ");
int length = console.nextInt();
int width = console.nextInt();

• Because the nextInt() method reads one token at a time,


the user can either:
• enter the two numbers on the same line, separated by
one or more whitespace characters
Enter the length and width: 30 15
• enter the two numbers on different lines
Enter the length and width: 30
15

nextLine Method
• The nextLine() method does not just read a single token.

• Using nextLine can lead to unexpected behavior,


for reasons that we'll discuss later on.

• Avoid it for now!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 146


Additional Terminology
• To avoid having too many new terms at once, I've limited
the terminology introduced in these notes.

• Here are some additional terms related to classes, objects,


and methods:
• invoking a method = calling a method
• method invocation = method call
• the called object = the object used to make a method call
• instantiate an object = create an object
• members of a class = the fields and methods of a class

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 147


Unit 3, Part 3

Conditional Execution

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Review: Simple Conditional Execution in Java


if (condition) { if (condition) {
true block true block
} else { }
false block
}

• If the condition is true:


• the statement(s) in the true block are executed
• the statement(s) in the false block (if any) are skipped

• If the condition is false:


• the statement(s) in the false block (if any) are executed
• the statement(s) in the true block are skipped

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 148


Example: Analyzing a Number
Scanner console = new Scanner(System.in);
System.out.print("Enter an integer: ");
int num = console.nextInt();

if (num % 2 == 0) {
System.out.println(num + " is even.");
} else {
System.out.println(num + " is odd.");
}

Flowchart for an if-else Statement

true false
condition

true block false block

next statement

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 149


Common Mistake
• You should not put a semi-colon after an if-statement header:
if (num % 2 == 0); {
System.out.println(…);
...
}

• The semi-colon ends the if statement.


• thus, it has an empty true block

• The println and other statements are independent of


the if statement, and always execute.

Choosing at Most One of Several Options


• Consider this code:
if (num < 0) {
System.out.println("The number is negative.");
}
if (num > 0) {
System.out.println("The number is positive.");
}
if (num == 0) {
System.out.println("The number is zero.");
}

• All three conditions are evaluated, but at most one of them


can be true (in this case, exactly one).

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 150


Choosing at Most One of Several Options (cont.)
• We can do this instead:
if (num < 0) {
System.out.println("The number is negative.");
}
else if (num > 0) {
System.out.println("The number is positive.");
}
else if (num == 0) {
System.out.println("The number is zero.");
}

• If the first condition is true, it will skip the second and third.

• If the first condition is false, it will evaluate the second, and


if the second condition is true, it will skip the third.

• If the second condition is false, it will evaluate the third, etc.

Choosing at Most One of Several Options (cont.)


• We can also make things more compact as follows:
if (num < 0) {
System.out.println("The number is negative.");
} else if (num > 0) {
System.out.println("The number is positive.");
} else if (num == 0) {
System.out.println("The number is zero.");
}

• This emphasizes that the entire thing is one compound


statement.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 151


if-else if Statements
• Syntax:
if (condition1) {
true block for condition1
} else if (condition2) {
true block for condition2
}

} else {
false block for all of the conditions
}

• The conditions are evaluated in order.


The true block of the first true condition is executed.
All of the remaining conditions and their blocks are skipped.
• If no condition is true, the false block (if any) is executed.

Flowchart for an if-else if Statement

true
condition1 true block 1

false

true
condition2 true block 2

false

...
false

false block

next statement

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 152


Choosing Exactly One Option
• Consider again this code fragment:
if (num < 0) {
System.out.println("The number is negative.");
} else if (num > 0) {
System.out.println("The number is positive.");
} else if (num == 0) {
System.out.println("The number is zero.");
}

• One of the conditions must be true, so we can omit the last one:
if (num < 0) {
System.out.println("The number is negative.");
} else if (num > 0) {
System.out.println("The number is positive.");
} else {
System.out.println("The number is zero.");
}

Types of Conditional Execution


• If it want to execute any number of several conditional blocks,
use sequential if statements:
if (num < 0) {
System.out.println("The number is negative.");
}
if (num % 2 == 0) {
System.out.println("The number is even.");
}

• If you want to execute at most one (i.e., 0 or 1) of several


blocks, use an if-else if statement ending in else if:
if (num < 0) {
System.out.println("The number is negative.");
} else if (num > 0) {
System.out.println("The number is positive.");
}

• If you want to execute exactly one of several blocks, use an


if-else if ending in just else (see bottom of last slide).

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 153


Find the Logic Error
Scanner console = new Scanner(System.in);

System.out.print("Enter the student's score: ");


int score = console.nextInt();

String grade;
if (score >= 90) {
grade = "A";
}
if (score >= 80) {
grade = "B";
}
if (score >= 70) {
grade = "C";
}
if (score >= 60) {
grade = "D";
}
if (score < 60) {
grade = "F";
}

Review: Variable Scope


• Recall: the scope of a variable is the portion of a program
in which the variable can be used.

• By default, the scope of a variable:


• begins at the point at which it is declared
• ends at the end of the innermost block that encloses the
declaration

• Because of these rules, a variable cannot be used outside


of the block in which it is declared.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 154


Variable Scope and if-else statements
• The following program will produce compile-time errors:
public static void main(String[] args) {
Scanner console = new Scanner(System.in);
System.out.print("enter a positive int: ");
int num = console.nextInt();
if (num < 0) {
System.out.println("number is negative;"
+ " using its absolute value");
double sqrt = Math.sqrt(num * -1);
} else {
sqrt = Math.sqrt(num);
}
System.out.println("square root = " + sqrt);
}

• Why?

Variable Scope and if-else statements (cont.)


• To eliminate the errors, declare the variable outside of
the true block:
public static void main(String[] args) {
Scanner console = new Scanner(System.in);
System.out.print("enter a positive int: ");
int num = console.nextInt();
double sqrt;
if (num < 0) {
System.out.println("number is negative;"
+ " using its absolute value");
sqrt = Math.sqrt(num * -1);
} else {
sqrt = Math.sqrt(num);
}
System.out.println("square root = " + sqrt);
}

• What is the scope of sqrt now?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 155


Review: Loop Patterns for n Repetitions
• Thus far, we've mainly used for loops to repeat something
a definite number of times.

• We've seen two different patterns for this:


• pattern 1:
for (int i = 0; i < n; i++) {
statements to repeat
}

• pattern 2:
for (int i = 1; i <= n; i++) {
statements to repeat
}

Another Loop Pattern: Cumulative Sum


• We can also use a for loop to add up a set of numbers.

• Basic pattern (using pseudocode):


sum = 0
for (all of the numbers that we want to sum) {
num = the next number
sum = sum + num
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 156


Example of Using a Cumulative Sum
public class GradeAverager {
public static void main(String[] args) {
Scanner console = new Scanner(System.in);
System.out.print("number of grades? ");
int numGrades = console.nextInt();
if (numGrades <= 0) {
System.out.println("nothing to average");
} else {
int sum = 0;
for (int i = 1; i <= numGrades; i++) {
System.out.print("grade #" + i + ": ");
int grade = console.nextInt();
sum = sum + grade;
}
System.out.println("The average is " +
(double)sum / numGrades);
}
}
}

• Note the use of an if-else statement to handle invalid


user inputs.

Tracing Through a Cumulative Sum


• Let's trace through this code.
int sum = 0;
for (int i = 1; i <= numGrades; i++) {
System.out.print("grade #" + i + ": ");
int grade = console.nextInt();
sum = sum + grade;
}

assuming that the user enters these grades: 80, 90, 84.

numGrades = 3

i i <= numGrades grade sum

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 157


Conditional Execution and Return Values
• With conditional execution, it's possible to write a method
with more than one return statement.
• example:
public static int min(int a, int b) {
if (a < b) {
return a;
} else {
return b;
}
}

• Only one of the return statements is executed.

• As soon as you reach a return statement, the method's


execution stops and the specified value is returned.
• the rest of the method is not executed

Conditional Execution and Return Values (cont.)


• Instead of writing the method this way:
public static int min(int a, int b) {
if (a < b) {
return a;
} else {
return b;
}
}
we could instead write it like this, without the else:
public static int min(int a, int b) {
if (a < b) {
return a;
}
return b;
}

• Why is this equivalent?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 158


Conditional Execution and Return Values (cont.)
• Consider this method, which has a compile-time error:
public static int compare(int a, int b) {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else if (a == b) {
return 0;
}
}

• Because all of the return statements are connected


to conditions, the compiler worries that no value
will be returned.

Conditional Execution and Return Values (cont.)


• Here's one way to fix it:
public static int compare(int a, int b) {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 159


Conditional Execution and Return Values (cont.)
• Here's another way:
public static int compare(int a, int b) {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
}

return 0;
}

• Both fixes allow the compiler to know for certain that


a value will always be returned.

Returning From a void Method


public static void repeat(String msg, int n) {
if (n <= 0) { // special cases
return;
}

for (int i = 0; i < n; i++) {


System.out.println(msg);
}
}

• Note that this method has a return type of void.


• it doesn't return a value.

• However, it still has a return statement.


• used to break out of the method
• note that there's nothing between the return and the ;

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 160


Testing for Equivalent Primitive Values
• The == and != operators are used when comparing primitives.
• int, double, char, etc.

• Example:
Scanner console = new Scanner(System.in);
...
System.out.print("Do you have another (y/n)? ");
char choice = console.next().charAt(0);
if (choice == 'y') { // this works just fine
processItem();
} else if (choice == 'n') {
return;
} else {
System.out.println("invalid input");
}

Testing for Equivalent Objects


• The == and != operators do not typically work
when comparing objects. (We'll see why this is later.)

• Example:
Scanner console = new Scanner(System.in);
System.out.print("regular or diet? ");
String choice = console.next();
if (choice == "regular") { // doesn't work
processRegular();
} else {
...
}

• choice == "regular" compiles, but it evaluates to false,


even when the user does enter "regular"!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 161


Testing for Equivalent Objects (cont.)
• We use a special method called the equals method
to test if two objects are equivalent.
• example:
Scanner console = new Scanner(System.in);
System.out.print("regular or diet? ");
String choice = console.next();
if (choice.equals("regular")) {
processRegular();
} else {
...
}

• choice.equals("regular") compares the string represented


by the variable choice with the string "regular"
• returns true when they are equivalent
• returns false when they are not

equalsIgnoreCase()
• We often want to compare two strings without paying attention
to the case of the letters.
• example: we want to treat as equivalent:
"regular"
"Regular"
"REGULAR"
etc.

• The String class has a method called equalsIgnoreCase that


can be used for this purpose:
if (choice.equalsIgnoreCase("regular")) {
...
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 162


Example Problem: Ticket Sales
• Different prices for balcony seats and orchestra seats

• Here are the rules:


• persons younger than 25 receive discounted prices:
• $20 for balcony seats
• $35 for orchestra seats
• everyone else pays the regular prices:
• $30 for balcony seats
• $50 for orchestra seats

• Assume only valid inputs.

Ticket Sales Program: main method


Scanner console = new Scanner(System.in);
System.out.print("Enter your age: ");
int age = console.nextInt();
if (age < 25) {
// handle people younger than 25
System.out.print("orchestra or balcony? ");
String choice = console.next();
int price;
if (choice.equalsIgnoreCase("orchestra")) {
price = 35;
} else {
price = 20;
}
System.out.println("The price is $" + price);
} else {
// handle people 25 and older
...
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 163


Ticket Sales Program: main method (cont.)

...

} else {
// handle people 25 and older
System.out.print("orchestra or balcony? ");
String choice = console.next();
int price;
if (choice.equalsIgnoreCase("orchestra")) {
price = 50;
} else {
price = 30;
}
System.out.println("The price is $" + price);
}

... Where Is the Code Duplication?


if (age < 25) {
System.out.print("orchestra or balcony? ");
String choice = console.next();
int price;
if (choice.equalsIgnoreCase("orchestra")) {
price = 35;
} else {
price = 20;
}
System.out.println("The price is $" + price);
} else {
System.out.print("orchestra or balcony? ");
String choice = console.next();
int price;
if (choice.equalsIgnoreCase("orchestra")) {
price = 50;
} else {
price = 30;
}
System.out.println("The price is $" + price);
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 164


Factoring Out Code Common to Multiple Cases
Scanner console = new Scanner(System.in);
System.out.print("Enter your age: ");
int age = console.nextInt();
System.out.print("orchestra or balcony? ");
String choice = console.next();
if (age < 25) {
int price;
if (choice.equalsIgnoreCase("orchestra")) {
price = 35;
} else {
price = 20;
}
} else {
int price;
if (choice.equalsIgnoreCase("orchestra")) {
price = 50;
} else {
price = 30;
}
}
System.out.println("The price is $" + price);

What Other Change Is Needed?


Scanner console = new Scanner(System.in);
System.out.print("Enter your age: ");
int age = console.nextInt();
System.out.print("orchestra or balcony? ");
String choice = console.next();
if (age < 25) {
int price;
if (choice.equalsIgnoreCase("orchestra")) {
price = 35;
} else {
price = 20;
}
} else {
int price;
if (choice.equalsIgnoreCase("orchestra")) {
price = 50;
} else {
price = 30;
}
}
System.out.println("The price is $" + price);

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 165


Now Let's Make It Structured
public static void main(String[] args) {
...

int age = console.nextInt();


System.out.print("orchestra or balcony? ");
String choice = console.next();
int price;
if (age < 25) {
____________________________________________________;
} else {

}
System.out.println("The price is $" + price);
}
public static ________ discountPrice(__________________) {

Expanded Ticket Sales Problem


• One additional case:
• persons younger than 13 cannot buy a ticket
• persons whose age is 13-24 receive discounted prices:
• $20 for balcony seats
• $35 for orchestra seats
• everyone else pays the regular prices:
• $30 for balcony seats
• $50 for orchestra seats

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 166


... Here's the Unfactored Version
if (age < 13) {
System.out.println("You cannot buy a ticket.");
} else if (age < 25) {
System.out.print("orchestra or balcony? ");
String choice = console.next();
int price; We now have code
if (choice.equalsIgnoreCase("orchestra")) { common to the
price = 35;
2nd and 3rd cases,
} else {
price = 20; but not the 1st.
}
System.out.println("The price is $" + price);
} else {
System.out.print("orchestra or balcony? ");
String choice = console.next();
int price;
if (choice.equalsIgnoreCase("orchestra")) {
price = 50;
} else {
price = 30;
}
System.out.println("The price is $" + price);
}

Group the Second and Third Cases Together


...
if (age < 13) {
System.out.println("You cannot buy a ticket.");
} else {
if (age < 25) {
System.out.print("orchestra or balcony? ");
String choice = console.next();
int price;
if (choice.equalsIgnoreCase("orchestra")) {
price = 35;
} else {
price = 20;
}
System.out.println("The price is $" + price);
} else {
System.out.print("orchestra or balcony? ");
String choice = console.next();
...
System.out.println("The price is $" + price);
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 167


Then Factor Out the Common Code
...
if (age < 13) {
System.out.println("You cannot buy a ticket.");
} else {
System.out.print("orchestra or balcony? ");
String choice = console.next();
int price;
if (age < 25) {
if (choice.equalsIgnoreCase("orchestra")) {
price = 35;
} else {
price = 20;
}
} else {
if (choice.equalsIgnoreCase("orchestra")) {
price = 50;
} else {
price = 30;
}
}
System.out.println("The price is $" + price);
}

Case Study: Coffee Shop Price Calculator


• Relevant info:
• brewed coffee prices by size:
• tiny: $1.60
• medio: $1.80
• gigundo: $2.00
• latte prices by size:
• tiny: $2.80
• medio: $3.20
• gigundo: $3.60
plus, add 50 cents for a latte with flavored syrup
• sales tax:
• students: no tax
• non-students: 6.25% tax

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 168


Case Study: Coffee Shop Price Calculator (cont.)
• Developing a solution:
1. Begin with an unstructured solution.
• everything in the main method
• use if-else-if statement(s) to handle the various cases

2. Next, factor out code that is common to multiple cases.


• put it either before or after the appropriate
if-else-if statement

3. Finally, create a fully structured solution.


• use procedural decomposition to capture
logical pieces of the solution

Case Study: Coffee Shop Price Calculator (cont.)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 169


Optional: Comparing Floating-Point Values
• Because the floating-point types have limited precision, it's
possible to end up with roundoff errors.

• Example:
double sum = 0.1 + 0.1 + 0.1 + 0.1 + 0.1;
sum = sum + 0.1 + 0.1 + 0.1 + 0.1 + 0.1;
System.out.println(sum);
// get 0.9999999999999999!

• Thus when trying to determine if two floating-point values are


equal, we usually do not use the == operator.

• Instead, we test if the difference between the two values is


less than some small threshold value:
threshold
if (Math.abs(sum – 1.0) < 0.0000001) {
System.out.println(sum + " == 1.0");
}

Optional: Another Cumulative Computation


• The same pattern can be used for other types of computations.

• Example: counting the occurrences of a character in a string.

• Let's write a static method called numOccur that does this.


• examples:
numOccur('l', "hello") should return 2
numOccur('s', "Mississippi") should return 4
public static ___ numOccur(_____________________) {

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 170


Unit 3, Part 4

Indefinite Loops
and Boolean Expressions

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Review: Definite Loops


• The loops that we've seen thus far have been definite loops.
• we know exactly how many iterations will be performed
before the loop even begins

• In an indefinite loop, the number of iterations is either:


• not as obvious
• impossible to determine before the loop begins

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 171


Sample Problem: Finding Multiples
• Problem: Print all multiples of a number (call it num) that are
less than 100.
• output for num = 9:
9 18 27 36 45 54 63 72 81 90 99

• Pseudocode for one possible algorithm:


mult = num
repeat as long as mult < 100:
print mult + " "
mult = mult + num
print a newline

Sample Problem: Finding Multiples (cont.)


• Pseudocode:
mult = num
repeat as long as mult < 100:
print mult + " "
mult = mult + num
print a newline

• Here's how we would write this in Java:


int mult = num;
while (mult < 100) {
System.out.print(mult + " ");
mult = mult + num;
}
System.out.println();

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 172


while Loops
• In general, a while loop has the form
while (test) {
one or more statements
}

• As with for loops, the statements in the block of a while loop


are known as the body of the loop.

Evaluating a while Loop

Steps:
1. evaluate the test false
test
condition
2. if it's false, skip the
statements in the body
true
3. if it's true, execute the
statements in the body,
and go back to step 1 body of the loop

next statement

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 173


Tracing a while Loop
• Let's trace through our code when num has the value 15:
int mult = num;
while (mult < 100) {
System.out.print(mult + " ");
mult = mult + num;
}

output thus far mult


before entering the loop 15
after the first iteration 15 30
after the second iteration 15 30 45
after the third iteration 15 30 45 60
after the fourth iteration 15 30 45 60 75
after the fifth iteration 15 30 45 60 75 90
after the sixth iteration 15 30 45 60 75 90 105
and now (mult < 100) is false, so we exit the loop

Comparing if and while


if statement while statement

false false
condition
test condition
test

true true

true block while


while block
body

next statement next statement

• The true block of an if statement is evaluated at most once.


• The body of a while statement can be evaluated multiple times,
provided the test remains true.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 174


Typical while Loop Structure
• Typical structure:
initialization statement(s)
while (test) {
other statements
update statement(s)
}

• In our example:
int mult = num; // initialization
while (mult < 100) {
System.out.print(mult + " ");
mult = mult + num; // update
}

Comparing for and while loops


• while loop (typical structure):
initialization
while (test) {
other statements
update
}

• for loop:
for (initialization; test; update) {
one or more statements
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 175


Infinite Loops
• Let's say that we change the condition for our while loop:
int mult = num;
while (mult != 100) { // replaced < with !=
System.out.print(mult + " ");
mult = mult + num;
}

• When num is 15, the condition will always be true.


• why?

• an infinite loop – the program will hang (or repeatedly output


something), and needs to be stopped manually
• what class of error is this (syntax or logic)?

• It's generally better to use <, <=, >, >= in a loop condition,
rather than == or !=

Infinite Loops (cont.)


• Another common source of infinite loops is forgetting the
update statement:
int mult = num;
while (mult < 100) {
System.out.print(mult + " ");
// update should go here
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 176


A Need for Error-Checking
• Let's return to our original version:
int mult = num;
while (mult < 100) {
System.out.print(mult + " ");
mult = mult + num;
}

• This could still end up in an infinite loop! How?

Using a Loop When Error-Checking


• We need to check that the user enters a positive integer.

• If the number is <= 0, ask the user to try again.

• Here's one way of doing it using a while loop:


Scanner console = new Scanner(System.in);
System.out.print("Enter a positive integer: ");
int num = console.nextInt();
while (num <= 0) {
System.out.print("Enter a positive integer: ");
num = console.nextInt();
}

• Note that we end up duplicating code.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 177


Error-Checking Using a do-while Loop
• Java has a second type of loop statement that allows us to
eliminate the duplicated code in this case:
Scanner console = new Scanner(System.in);
int num;
do {
System.out.print("Enter a positive integer: ");
num = console.nextInt();
} while (num <= 0);

• The code in the body of a do-while loop is always executed


at least once.

do-while Loops
• In general, a do-while statement has the form
do {
one or more statements
} while (test);

• Note the need for a semi-colon after the condition.

• We do not need a semi-colon after the condition in a


while loop.
• beware of using one – it can actually create an infinite loop!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 178


Evaluating a do-while Loop

Steps:
1. execute the statements
in the body
while
body block
of the loop
2. evaluate the test
3. if it's true, go back to
step 1
(if it's false, continue to the true
test
condition
next statement)
false

next statement

Formulating Loop Conditions


• We often need to repeat actions until a condition is met.
• example: keep reading a value until the value is positive
• such conditions are termination conditions –
they indicate when the repetition should stop

• However, loops in Java repeat actions while a condition is met.


• they use continuation conditions

• As a result, you may need to convert a termination condition


into a continuation condition.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 179


Which Type of Loop Should You Use?
• Use a for loop when the number of repetitions is known in
advance – i.e., for a definite loop.

• Otherwise, use a while loop or do-while loop:


• use a while loop if the body of the loop may not be
executed at all
• i.e., if the condition may be false at the start of the loop
• use a do-while loop if:
• the body will always be executed at least once
• doing so will allow you to avoid duplicating code

Find the Error…


• Where is the syntax error below?
Scanner console = new Scanner(System.in);
do {
System.out.print("Enter a positive integer: ");
int num = console.nextInt();
} while (num <= 0);
System.out.println("\nThe multiples of " + num +
" less than 100 are:");
int mult = num;
while (mult < 100) {
System.out.print(mult + " ");
mult = mult + num;
}
System.out.println();

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 180


Practice with while loops
• What does the following loop output?
int a = 10;
while (a > 2) {
a = a – 2;
System.out.println(a * 2);
}

a > 2 a output
before loop
1st iteration
2nd iteration
3rd iteration
4th iteration

boolean Data Type


• A condition like mult < 100 has one of two values:
true or false

• In Java, these two values are represented using the


boolean data type.
• one of the primitive data types (like int, double, and char)
• true and false are its two literal values

• This type is named after the 19th-century


mathematician George Boole, who developed
the system of logic called boolean algebra.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 181


boolean Expressions
• We have seen a number of constructs that use a "test".
• loops
• if statements

• A more precise term for a "test" is a boolean expression.

• A boolean expression is any expression that evaluates to


true or false.
• examples: num > 0
false
firstChar == 'P'
score != 20

boolean Expressions (cont.)


• Recall this line from our ticket-price program:
if (choice.equals("orchestra")) …
a boolean expression, because
it evaluates to true or false

• if we look at the String class in the Java API, we see


that the equals method has this header:
public boolean equals(...)
it returns either true or false

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 182


Forming More Complex Conditions
• We often need to make a decision based on more than one
condition – or based on the opposite of a condition.
• examples in pseudocode:
if the number is even AND it is greater than 100…
if it is NOT the case that your grade is > 80…

• Java provides three logical operators for this purpose:


operator name example
&& and age >= 18 && age <= 35

|| or age < 3 || age > 65

! not !(grade > 80)

Truth Tables
• The logical operators operate on boolean expressions.
• let a and b represent two such expressions

• We can define the logical operators using truth tables.


truth table for && (and) truth table for || (or)
a b a && b a b a || b
false false false false false false
false true false false true true
true false false true false true
true true true true true true

truth table for ! (not)


a !a
false true
true false

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 183


Truth Tables (cont.)
• Example: evaluate the following expression:
(20 >= 0) && (30 % 4 = = 1)

• First, evaluate each of the operands:


(20 >= 0) && (30 % 4 = = 1)
true && false

• Then, consult the appropriate row of the truth table:

a b a && b
false false false
false true false
true false false
true true true

• Thus, (20 >= 0) && (30 % 4 == 1) evaluates to false

Practice with Boolean Expressions


• Let's say that we wanted to express the following English
condition in Java:
"num is not equal to either 0 or 1"

• Which of the following boolean expression(s) would work?


a) num != 0 || 1
b) num != 0 || num != 1
c) !(num == 0 || num == 1)

• Is there a different boolean expression that would work here?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 184


boolean Variables
• We can declare variables of type boolean, and assign
the values of boolean expressions to them:
int num = 10;
boolean isPos = (num > 0);
boolean isDone = false;

• these statements give us the following picture in memory:


isPos true isDone false

• Using a boolean variable can make your code more readable:


if (value % 2 == 0) {
...

boolean isEven = (value % 2 == 0);


if (isEven == true) {
...

boolean Variables (cont.)


• Instead of doing this:
boolean isEven = (num % 2 == 0);
if (isEven == true) {
...
you could just do this:
boolean isEven = (num % 2 == 0);
if (isEven) {
...
The extra comparison isn't necessary!

• Similarly, instead of writing:


if (isEven == false) {
...
you could just write this:
if (!isEven) {
...

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 185


Input Using a Sentinel
• Example problem: averaging an arbitrary number of grades.

• Instead of having the user tell us the number of grades


in advance, we can let the user indicate that there are no more
grades by entering a special sentinal value.

• When we encounter the sentinel, we break out of the loop


• example interaction:
Enter grade (-1 to end): 10
Enter grade (-1 to end): 8
Enter grade (-1 to end): 9
Enter grade (-1 to end): 5
Enter grade (-1 to end): -1
The average is: 8.0

Input Using a Sentinel (cont.)


• Here's one way to do this:
Scanner console = new Scanner(System.in);
int total = 0;
int numGrades = 0;

System.out.print("Enter grade (or -1 to quit): ");


int grade = console.nextInt();
while (grade != -1) {
total += grade;
numGrades++;
System.out.print("Enter grade (or -1 to quit): ");
grade = console.nextInt();
}

if (numGrades > 0) {
System.out.print("The average is ");
System.out.println((double)total/numGrades);
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 186


Input Using a Sentinel and a Boolean Flag
• Here's another way, using what is known as a boolean flag,
which is a variable that keeps track of some condition:
Scanner console = new Scanner(System.in);
int total = 0;
int numGrades = 0;
boolean done = false;

while (!done) {
System.out.print("Enter grade (or -1 to quit): ");
int grade = console.nextInt();
if (grade == -1) {
done = true;
} else {
total += grade;
numGrades++;
}
}

if (numGrades > 0) {
...

Input Using a Sentinel and a break Statement


• Here's another way, using what is known as a break statement,
which "breaks out" of the loop:
Scanner console = new Scanner(System.in);
int total = 0;
int numGrades = 0;

while (true) {
System.out.print("Enter grade (or -1 to quit): ");
int grade = console.nextInt();
if (grade == -1) {
break;
}
total += grade;
numGrades++;
}

// after the break statement, the flow of control


// resumes here...
if (numGrades > 0) {
...

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 187


Unit 4, Part 1

Arrays

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Collections of Data
• Recall our program for averaging quiz grades:
public static void main(String[] args) {
Scanner console = new Scanner(System.in);
int total = 0;
int numGrades = 0;
while (true) {
System.out.print("Enter a grade (or -1 to quit): ");
int grade = console.nextInt();
if (grade == -1) {
break;
}
total += grade;
numGrades++;
}
if (numGrades > 0) {
...
}

• What if we wanted to store the individual grades?


• an example of a collection of data

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 188


Arrays
• An array is a collection of data values of the same type.

• In the same way that we think of a variable as a single box,


an array can be thought of as a sequence of boxes:
0 1 2 3 4 5 6 7 indices
7 8 9 6 10 7 9 5 elements

• Each box contains one of the data values in the collection


• referred to as the elements of the array

• Each element has a numeric index


• the first element has an index of 0,
the second element has an index of 1,
etc.
• example: the value 6 above has an index of 3
• like the index of a character in a String

Declaring and Creating an Array


• We use a variable to represent the array as a whole.

• Example of declaring an array variable:


int[] grades;
• the [] indicates that it will represent an array
• the int indicates that the elements will be ints

• Declaring the array variable does not create the array.

• Example of creating an array:


grades = new int[8];

the length of the array –


i.e., the number of elements

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 189


Declaring and Creating an Array (cont.)
• We often declare and create an array in the same statement:
int[] grades = new int[8];

• General syntax:
type[] array = new type[length];
where
type is the type of the individual elements
array is the name of the variable used for the array
length is the number of elements in the array

The Length of an Array


• The length of an array is the number of elements in the array.

• The length of an array can be obtained as follows:


array.length
• example:
grades.length

• note: it is not a method


grades.length() won't work!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 190


Auto-Initialization
• When you create an array in this way:
int[] grades = new int[8];
the runtime system gives the elements default values:
0 1 2 3 4 5 6 7
0 0 0 0 0 0 0 0

• The value used depends on the type of the elements:


int 0
double 0.0
char '\0'
boolean false
objects null

Accessing an Array Element


• To access an array element, we use an expression of the form
array[index]

• Examples:
grades[0] accesses the first element
grades[1] accesses the second element
grades[5] accesses the sixth element

• Here's one way of setting up the array we showed earlier:


0 1 2 3 4 5 6 7
7 8 9 6 10 7 9 5

int[] grades = new int[8];


grades[0] = 7; grades[1] = 8; grades[2] = 9;
grades[3] = 6; grades[4] = 10; grades[5] = 7;
grades[6] = 9; grades[7] = 5;

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 191


Accessing an Array Element (cont.)
• Acceptable index values:
integers from 0 to array.length – 1

• If we specify an index outside that range, we'll get an


ArrayIndexOutOfBoundsException at runtime.
• example:
int[] grades = int[8];
grades[8] = 5;

0 1 2 3 4 5 6 7 8
no such
0 0 0 0 0 0 0 0
element!

Accessing an Array Element (cont.)


• The index can be any integer expression.
• example:
int lastGrade = grades[grades.length – 1];

• We can operate on an array element in the same way that


we operate on any other variable of that type.
• example: applying a 10% late penalty to the grade
at index i
grades[i] = (int)(grades[i] * 0.9);

• example: adding 5 points of extra credit to the grade


at index i
grades[i] += 5;

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 192


Another Way to Create an Array
• If we know that we want an array to contain specific values,
we can specify them when create the array.

• Example: here's another way to create and initialize our


grades array:
int[] grades = {7, 8, 9, 6, 10, 7, 9, 5};

• The list of values is known as an initialization list.


• it can only be specified when the array is declared
• we don't use the new operator in this case
• we don't specify the length of the array – it is determined
from the number of values in the initialization list

• Other examples:
double[] heights = {65.2, 72.0, 70.6, 67.9};
boolean[] isPassing = {true, true, false, true};

Storing Grades Entered by the User


• We need to know how big to make the array.
• one way: ask the user for the maximum number of values
public static void main(String[] args) {
Scanner console = new Scanner(System.in);
System.out.print("How many grades? ");
int maxNumGrades = console.nextInt();
int[] grades = new int[maxNumGrades];
int total = 0;
int numGrades = 0;
while (numGrades < maxNumGrades) {
System.out.print("Enter a grade (or -1 to quit): ");
grades[numGrades] = console.nextInt();
if (grades[numGrades] == -1) {
break;
}
total += grades[numGrades];
numGrades++;
}
...
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 193


Processing the Values in an Array
• We often use a for loop to process the values in an array.

• Example: print out all of the grades


int[] grades = new int[maxNumGrades];
...
for (int i = 0; i < grades.length; i++) {
System.out.println("grade " + i + ": " + grades[i]);
}

• General pattern:
for (int i = 0; i < array.length; i++) {
do something with array[i];
}

• Processing array elements sequentially from first to last


is known as traversing the array.
• noun = traversal

Another Example of Traversing an Array


• Let's write code to find the highest quiz grade in the array:
int max = __________________;

for (_________; _________________; ______) {

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 194


Another Example of Traversing an Array (cont.)

grades array: 7 8 9 6 10 7 9 5

• Let's trace through our code:


int max = grades[0];
for (int i = 1; i < grades.length; i++) {
if (grades[i] > max) {
max = grades[i];
}
}

i grades[i] max
7
1 8 8
2 9 9
3 6 9
4 10 10
5 7 10
...

Review: What Is a Variable?


• We've seen that a variable is like a named "box" in memory
that can be used to store a value.

int count = 10; count 10

• If a variable represents a primitive-type value, the value is


stored in the variable itself, as shown above.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 195


Reference Variables
• If a variable represents an object, the object itself is
not stored inside the variable.

• Rather, the object is located somewhere else in memory, and


the variable holds the memory address of the object.
• we say that the variable stores a reference to the object
• such variables are called reference variables

Arrays and References


• An array is a type of object.

• Thus, an array variable is a reference variable.


• it stores a reference to the array

• Example:
int[] grades = new int[8];
might give the following picture:
memory location: 2000
grades 2000 0 0 0 0 0 0 0 0

• We usually use an arrow to represent a reference:

grades 0 0 0 0 0 0 0 0

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 196


Printing an Array
• What is the output of the following lines?
int[] grades = {7, 8, 9, 6, 10, 7, 9, 5};
System.out.println(grades);

• To print the contents of the array, we can use a for loop


as we showed earlier.

• We can also use the Arrays.toString() method,


which is part of Java's built in Arrays class.
int[] grades = {7, 8, 9, 6, 10, 7, 9, 5};
System.out.println(Arrays.toString(grades));

• doing so produces the following output:


[7, 8, 9, 6, 10, 7, 9, 5]

• To use this method, we need to import the java.util package.

What is the output of the full program?


import java.util.*;
public class FunWithArrays {
public static void main(String[] args) {
int[] temps = {51, 50, 36, 29, 30};
int first = temps[0];
int numTemps = temps.length;
int last = temps[numTemps - 1];
temps[2] = 40;
temps[3] += 5;
System.out.println(temps[3]);
System.out.println(Arrays.toString(temps));
}
}

temps
first
output:
numTemps
last

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 197


Copying References
• When we assign the value of one reference variable to
another, we copy the reference to the object.
We do not copy the object itself.

• Example involving objects:


String s1 = "hello, world";
String s2 = s1;

s1
"hello, world"
s2

Copying References (cont.)


• An example involving an array:
int[] grades = {7, 8, 9, 6, 10, 7, 9, 5};
int[] other = grades;

grades 7 8 9 6 10 7 9 5
other

• Given the lines of code above, what will the lines below print?
other[2] = 4;
System.out.println(grades[2] + " " + other[2]);

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 198


Changing the Internals vs. Changing a Variable
• When two variables hold a reference to the same array...
int[] list1 = {7, 8, 9};
int[] list2 = list1;
list1 7 8 9

list2

• ...if we change the internals of the array,


both variables will "see" the change:
list2[2] = 4;
System.out.println(Arrays.toString(list1));

list1 7 8 4 output of println:

list2

Changing the Internals vs. Changing a Variable (cont.)


• When two variables hold a reference to the same array...
int[] list1 = {7, 8, 9};
int[] list2 = list1;
list1 7 8 9

list2

• ...if we change one of the variables itself,


that does not change the other variable:
list2 = new int[3];
System.out.println(Arrays.toString(list1));

list1 7 8 9 output of println:

list2 0 0 0

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 199


Null References
• To indicate that a reference variable doesn't yet refer to any
object, we can assign it a special value called null.
int[] grades = null;
String s = null;

grades null s null

• Attempting to use a null reference to access an object


produces a NullPointerException.
• "pointer" is another name for reference
• examples:
int[] grades = null;
String s = null;
grades[3] = 10; // NullPointerException!
char ch = s.charAt(5); // NullPointerException!

Copying an Array
• To actually create a copy of an array, we can:
• create a new array of the same length as the first
• traverse the arrays and copy the individual elements

• Example:
int[] grades = {7, 8, 9, 6, 10, 7, 9, 5};
int[] other = new int[grades.length];
for (int i = 0; i < grades.length; i++) {
other[i] = grades[i];
}

grades 7 8 9 6 10 7 9 5
other 7 8 9 6 10 7 9 5

• What do the following lines print now?


other[2] = 4;
System.out.println(grades[2] + " " + other[2]);

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 200


Programming Style Point
• Here's how we copied the array:
int[] grades = {7, 8, 9, 6, 10, 7, 9, 5};
int[] other = new int[grades.length];
for (int i = 0; i < grades.length; i++) {
other[i] = grades[i];
}

• This would also work:


int[] grades = {7, 8, 9, 6, 10, 7, 9, 5};
int[] other = new int[8];
for (int i = 0; i < 8; i++) {
other[i] = grades[i];
}

• Why is the first way better?

Passing an Array to a Method


• Let's put our code for finding the highest grade into a method:
public class GradeAnalyzer {
public static _______ maxGrade(int[] grades) {
int max = grades[0];
for (int i = 1; i < grades.length; i++) {
if (grades[i] > max) {
max = grades[i];
}
}

_____________________;
}
public static void main(String[] args) {
...
int maxNumGrades = console.nextInt();
int[] grades = new int[maxNumGrades];
... // code to read in the values
System.out.println("max grade = " +
________________________________);

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 201


Passing an Array to a Method (cont.)
• What's wrong with this alternative approach?
public class GradeAnalyzer {
public static int maxGrade(int[] grades) {
int max = grades[0];
for (int i = 1; i < grades.length; i++) {
if (grades[i] > max) {
max = grades[i];
}
}

return max;
}
public static void main(String[] args) {
...
int maxNumGrades = console.nextInt();
int[] grades = new int[maxNumGrades];
... // code to read in the values
maxGrade(grades);
System.out.println("max grade = " + max);

Passing an Array to a Method (cont.)


• We could do this instead:
public class GradeAnalyzer {
public static int maxGrade(int[] grades) {
int max = grades[0];
for (int i = 1; i < grades.length; i++) {
if (grades[i] > max) {
max = grades[i];
}
}

return max;
}
public static void main(String[] args) {
...
int maxNumGrades = console.nextInt();
int[] grades = new int[maxNumGrades];
... // code to read in the values
int max = maxGrade(grades);
System.out.println("max grade = " + max);

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 202


Finding the Average Value in an Array
• Here's a method that computes the average grade:
public static double averageGrade(int[] grades) {
int total = 0;
for (int i = 0; i < grades.length; i++) {
total += grades[i];
}
return (double)total / grades.length;
}

Testing If An Array Meets Some Condition


• Let's say that we need to be able to determine
if there are any grades below a certain cutoff value.
• e.g., to determine if a retest should be given

• Does this method work?


public static boolean
anyGradesBelow(int[] grades, int cutoff) {
for (int i = 0; i < grades.length; i++) {
if (grades[i] < cutoff) {
return true;
} else {
return false;
}
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 203


Testing If An Array Meets Some Condition (cont.)
• We can return true as soon as we find a grade that
is below the threshold.

• We can only return false if none of the grades is below.

• Here is a corrected version:


public static boolean
anyGradesBelow(int[] grades, int cutoff) {
for (int i = 0; i < grades.length; i++) {
if (grades[i] < cutoff) {
return true;
}
}

// if we get here, none of the grades is below.


return false;
}

Testing If An Array Meets Some Condition (cont.)


• Here's a similar problem: write a method that determines
if all of the grades are perfect (assume perfect = 100).
public static boolean allPerfect(int[] grades) {

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 204


Using an Array to Count Things
• Let's say that we want to count how many times each of the
possible grade values appears in a collection of grades.

• We can use an array to store the counts.


• counts[i] will store the number of times that the grade i
appears
• for this grades array
grades 7
10 8 9 6 10 7 9 5

we would have this array of counts:


0 1 2 3 4 5 6 7 8 9 10
counts 0 0 0 0 0 1 1 2 1 2 1

Using an Array to Count Things (cont.)


grades 7
10 8 9 6 10 7 9 5

0 1 2 3 4 5 6 7 8 9 10
counts 0 0 0 0 0 1 1 2 1 2 1

• The size of the counts array should be one more than the
maximum value being counted:
int max = maxGrade(grades);
int[] counts = new int[max + 1];

• Given the array, here's how to do the actual counting:


for (int i = 0; i < grades.length; i++) {
counts[grades[i]]++;
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 205


Using an Array to Count Things (cont.)
grades 7
10 8 9 6 10 7 9 5

0 1 2 3 4 5 6 7 8 9 10
counts

• Let's trace through this code for the grades array shown above:
for (int i = 0; i < grades.length; i++) {
counts[grades[i]]++;
}

i grades[i] operation performed

A Method That Returns an Array


• We can write a method to create and return the array of counts:
public static int[] getCounts(int[] grades, int maxGrade) {
int[] counts = new int[maxGrade + 1];
for (int i = 0; i < grades.length; i++) {
counts[grades[i]]++;
}
return counts;
}

public static void main(String[] args) {


... // main method begins as in the earlier versions
int max = maxGrade(grades);
int[] counts = getCounts(grades, max);
...
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 206


Using a Method to Change an Array's Contents
public static void main(String[] args) {
int[] a = {1, 2, 3};
triple(a);
System.out.println(Arrays.toString(a));
}
public static void triple(int[] n) {
for (int i = 0; i < n.length; i++) {
n[i] = n[i] * 3;
}
}

• When a method is passed triple


an array as a parameter, n
it gets a copy of the reference,
not a copy of the array. main
a
• If the method changes the internals 1 2 3
of the array, those changes will
be there after the method returns.

Using a Method to Change an Array's Contents (cont.)


before method call
main
a
1 2 3

during method call


triple triple
n

main main
a a
1 2 3 3 6 9

after method call


main
a
3 6 9

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 207


Changing the Internals vs. Changing a Variable
public static void main(String[] args) {
int[] a = {1, 2, 3};
triple(a);
System.out.println(Arrays.toString(a));
}
public static void triple(int[] n) {
for (int i = 0; i < n.length; i++) {
n[i] = n[i] * 3; // changes internals
}
}

• If the method changes the internals triple


of the array, those changes will n
be there after the method returns.
main
a 3 6 9
1 2 3

Changing the Internals vs. Changing a Variable (cont.)


public static void main(String[] args) {
int[] a = {1, 2, 3};
triple(a);
System.out.println(Arrays.toString(a));
}
public static void method2(int[] n) {
n = new int[3]; // changes the variable
}

• However, if the method changes


its variable for the array, that method2
change does not affect the n
0 0 0
original array.
main x
• Changing what's in one a
variable doesn't affect 1 2 3
any other variable!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 208


Swapping Elements in an Array
• We sometimes need to be able to swap two elements in an array.

• Example: 0 1 2 3 4 5 6 7
arr 35 6 19 23 3 47 9 15

arr 35 6 47 23 3 19 9 15

• What's wrong with this code for swapping the two values?
arr[2] = arr[5];
arr[5] = arr[2];

• it gives this:

arr 35 6 47 23 3 47 9 15

Swapping Elements in an Array (cont.)


• To perform a swap, we need to use a temporary variable:
int temp = arr[2];
arr[2] = arr[5];
arr[5] = temp;
0 1 2 3 4 5 6 7
arr 35 6 19 23 3 47 9 15
temp 19

0 1 2 3 4 5 6 7
arr 35 6 47 23 3 47 9 15
temp 19

0 1 2 3 4 5 6 7
arr 35 6 47 23 3 19 9 15
temp 19

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 209


A Method for Swapping Elements
• Here's a method for swapping the elements at positions i and j
in the array arr:
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}

• We don't need to return anything, because the method changes


the internals of the array that is passed in.

• Here's an example of how we would use it:


int[] grades = {7, 8, 9, 6, 10, 7, 9, 5};
swap(grades, 2, 5);
System.out.println(Arrays.toString(grades));

• What would the output be?

Recall: A Method That Returns an Array


• We can write a method to create and return the array of counts:
public static int[] getCounts(int[] grades, int maxGrade) {
int[] counts = new int[maxGrade + 1];
for (int i = 0; i < grades.length; i++) {
counts[grades[i]]++;
}
return counts;
}

public static void main(String[] args) {


... // main method begins as in the earlier versions
int max = maxGrade(grades);
int[] counts = getCounts(grades, max);
...
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 210


An Alternative Approach for the Array of Counts
• Create the array ahead of time and pass it into the method:
public static void getCounts(int[] grades, int[] counts) {

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


counts[grades[i]]++;
}

public static void main(String[] args) {


... // main method begins as in the earlier versions
int max = maxGrade(grades);
int[] counts = new int[max];
getCounts(grades, counts);
...
}

• Because the method changes the internals of the array,


those changes will be there after the method returns.

Shifting Values in an Array


• Let's say a small business is using an array to store the
number of items sold over a 10-day period.

numSold 15 8 19 2 5 8 11 18 7 16

numSold[0] gives the number of items sold today


numSold[1] gives the number of items sold 1 day ago
numSold[2] gives the number of items sold 2 days ago

numSold[9] gives the number of items sold 9 days ago

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 211


Shifting Values in an Array (cont.)
• At the start of each day, it's necessary to shift the values over
to make room for the new day's sales.

numSold 15 8 19 2 5 8 11 18 7 16

numSold 0 15 8 19 2 5 8 11 18 7

• the last value is lost, since it's now 10 days old

• In order to shift the values over, we need to perform


assignments like the following:
numSold[9] = numSold[8];
numSold[6] = numSold[5];
numSold[2] = numSold[1];
• what is the general form (the pattern) of these assignments?

Shifting Values in an Array (cont.)


• Here's one attempt at code for shifting all of the elements:
for (int i = 0; i < numSold.length; i++) {
numSold[i] = numSold[i - 1];
}

• If we run this, we get an ArrayIndexOutOfBoundsException.


Why?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 212


Shifting Values in an Array (cont.)
• This version of the code eliminates the exception:
for (int i = 1; i < numSold.length; i++) {
numSold[i] = numSold[i – 1];
}

• Let's trace it to see what it does:

numSold 15 8 19 2 5 8 11 18 7 16

• when i == 1, we perform numSold[1] = numSold[0] to get:

numSold 15 15 19 2 5 8 11 18 7 16

• when i == 2, we perform numSold[2] = numSold[1] to get:

numSold 15 15 15 2 5 8 11 18 7 16

this obviously doesn't work!

Shifting Values in an Array (cont.)


• How can we fix this code so that it does the right thing?
for (int i = 1; i < numSold.length; i++) {
numSold[i] = numSold[i – 1];
}

for ( ; ; ) {

• After performing all of the shifts, we would do: numSold[0] = 0;


numSold 15 15 8 19 2 5 8 11 18 7

numSold 0 15 8 19 2 5 8 11 18 7

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 213


"Growing" an Array
• Once we have created an array, we can't increase its size.

• Instead, we need to do the following:


• create a new, larger array (use a temporary variable)
• copy the contents of the original array into the new array
• assign the new array to the original array variable

• Example for our grades array:


int[] grades = {7, 8, 9, 6, 10, 7, 9, 5};
...
int[] temp = new int[16];
for (int i = 0; i < grades.length; i++) {
temp[i] = grades[i];
}
grades = temp;

Arrays of Objects
• We can use an array to represent a collection of objects.

• In such cases, the cells of the array store references to


the objects.

• Example:
String[] suitNames = {"clubs", "spades",
"hearts", "diamonds"};

suitNames

"clubs" "spades" "hearts" "diamonds"

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 214


Two-Dimensional Arrays
• Thus far, we've been looking at single-dimensional arrays

• We can also create multi-dimensional arrays.

• The most common type is a two-dimensional (2-D) array.

• We can visualize it as a matrix consisting of rows and columns:


0 1 2 3 4 5 6 7 column
0 15 8 3 16 12 7 9 5 indices
1 6 11 9 4 1 5 8 13
2 17 3 5 18 10 6 7 21
3 8 14 13 6 13 12 8 4
4 1 9 5 16 20 2 3 9
row
indices

2-D Array Basics


• Example of declaring and creating a 2-D array:
int[][] scores = new int[5][8];

number number
of rows of columns

• To access an element, we use an expression of the form


array[row][column]
• example: scores[3][4] gives the score at row 3, column 4
0 1 2 3 4 5 6 7
0 15 8 3 16 12 7 9 5
1 6 11 9 4 1 5 8 13
2 17 3 5 18 10 6 7 21
3 8 14 13 6 13 12 8 4
4 1 9 5 16 20 2 3 9

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 215


Example Application: Maintaining a Game Board
• For a Tic-Tac-Toe board, we could use a 2-D array to keep
track of the state of the board:
char[][] board = new char[3][3];

• Alternatively, we could create and initialize it as follows:


char[][] board = {{' ', ' ', ' '},
{' ', ' ', ' '},
{' ', ' ', ' '}};

• If a player puts an X in the middle square, we could record


this fact by making the following assignment:
board[1][1] = 'X';

An Array of Arrays
• A 2-D array is really an array of arrays!
scores
15 8 3 16 12 7 9 5

6 11 9 4 1 5 8 13

17 3 5 18 10 6 7 21

8 14 13 6 13 12 8 4

1 9 5 16 20 2 3 9

• scores[0] represents the entire first row


scores[1] represents the entire second row, etc.

• array.length gives the number of rows


array[row].length gives the number of columns in that row

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 216


Processing All of the Elements in a 2-D Array
• To perform some operation on all of the elements in a 2-D
array, we typically use a nested loop.
• example: finding the maximum value in a 2-D array.
public static int maxValue(int[][] arr) {
int max = arr[0][0];
for (int r = 0; r < arr.length; r++) {
for (int c = 0; c < arr[r].length; c++) {
if (arr[r][c] > max) {
max = arr[r][c];
}
}
}
return max;
}

Optional: Other Multi-Dimensional Arrays


• It's possible to have a "ragged" 2-D array in which different
rows have different numbers of columns:
int[][] foo = {{11, 22, 33},
{7, 20, 30, 40},
{1, 2}};

foo
11 22 33

10 20 30 40

1 2

• We can also create arrays of higher dimensions.


• example: a three-dimensional matrix:
double[][][] matrix = new double[2][5][4];

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 217


Unit 4, Part 2

File Processing

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

A Class for Representing a File


• The File class in Java is used to represent a file on disk.

• To use it, we need to import the java.io package:


import java.io.*;

• Here's how we typically create a File object:


File f = new File("filename");

• Here are some useful methods from this class:


public boolean exists()
public boolean canRead()
public boolean canWrite()
public boolean delete()
public long length()
public String getName()
public String getPath()
See the Java API documentation for more info.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 218


Review: Scanner Objects
• We've been using a Scanner object to read from the console:
Scanner console = new Scanner(System.in);

tells the constructor to


construct a Scanner object
that reads from the console

• Scanner methods:
next()
nextInt()
nextDouble()
nextLine()

Reading from a Text File


• We can also use a Scanner object to read from a text file:
File f = new File("filename");
Scanner input = new Scanner(f);

tells the constructor to


construct a Scanner object
that reads from the file

• We can combine the two lines above into a single line:


Scanner input = new Scanner(new File("filename"));

• We use a different name for the Scanner (input),


to stress that we're reading from an input file.

• All of the same Scanner methods can be used.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 219


Scanner Lookahead and Files
• When reading a file, we often don't know how big the file is.

• Solution: use an indefinite loop and a Scanner "lookahead"


method.

• Basic structure:
Scanner input = new Scanner(new File(filename));
while (input.hasNextLine()) {
String line = input.nextLine();
// code to process the line goes here…
}

• hasNextLine() returns:
• true if there's at least one more line of the file to be read
• false if we've reached the end of the file

Sample Problem: Printing the Contents of a File


• Assume that we've already created a Scanner called input
that is connected to a file.

• Here's the code for printing its contents:


while (input.hasNextLine()) {
String line = input.nextLine();
System.out.println(line);
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 220


File-Processing Exceptions
• Recall: An exception is an error that occurs at runtime as a
result of some type of "exceptional" circumstance.

• We've seen several examples:


StringIndexOutOfBoundsException
IllegalArgumentException
TypeMismatchException

• When using a Scanner to process a file, we can get a


FileNotFoundException
• if the file that we specify isn't there
• if the file is inaccessible for some reason

Checked vs. Unchecked Exceptions


• Most of the exceptions we've seen thus far have been
unchecked exceptions.
• we do not need to handle them
• instead, we usually take steps to avoid them

• FileNotFoundException is a checked exception.


The compiler checks that we either:
1) handle it
2) declare that we don't handle it

• For now, we'll take option 2. We do this by adding a


throws clause to the header of any method in which a
Scanner for a file is created:
public static void main(String[] args)
throws FileNotFoundException {

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 221


Sample Program: Counting the Lines in a File
import java.util.*; // needed for Scanner
import java.io.*; // needed for File

public class CountLines {


public static void main(String[] args)
throws FileNotFoundException {
Scanner input = new Scanner(new File("romeo.txt"));
int count = 0;
while (input.hasNextLine()) {
input.nextLine(); // read line and throw away
count++;
}
System.out.println("The file has " + count +
" lines.");
}
}

Counting Lines in a File, version 2


import java.util.*; // needed for Scanner
import java.io.*; // needed for File

public class CountLines {


public static void main(String[] args)
throws FileNotFoundException {
Scanner console = new Scanner(System.in);
System.out.print("Name of file: ");
String fileName = console.next();

Scanner input = new Scanner(new File(fileName));


int count = 0;
while (input.hasNextLine()) {
input.nextLine(); // read line and throw away
count++;
}
System.out.println("The file has " + count +
" lines.");
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 222


Counting Lines in a File, version 3

public static void main(String[] args)
throws FileNotFoundException {
Scanner console = new Scanner(System.in);
System.out.print("Name of file: ");
String fileName = console.next();
System.out.println("The file has " +
numLines(fileName) + " lines.");
}
public static int numLines(String fileName)
throws FileNotFoundException {
Scanner input = new Scanner(new File(fileName));
int count = 0;
while (input.hasNextLine()) {
input.nextLine(); // read line and throw away
count++;
}
return count;
}

• We put the counting code in a separate method (numLines).


• Both numLines and main need a throws clause.

Extracting Data from a File


• Collections of data are often stored in a text file.

• Example: the results of a track meet might be summarized


in a text file that looks like this:
Mike Mercury,BU,mile,4:50:00
Steve Slug,BC,mile,7:30:00
Fran Flash,BU,800m,2:15:00
Tammy Turtle,UMass,800m,4:00:00

• Each line of the file represents a record.

• Each record is made up of multiple fields.

• In this case, the fields are separated by commas.


• known as a CSV file – comma separated values
• the commas serve as delimiters
• could also use spaces or tabs ('\t') instead of commas

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 223


Extracting Data from a File (cont.)
Mike Mercury,BU,mile,4:50:00
Steve Slug,BC,mile,7:30:00
Fran Flash,BU,800m,2:15:00
Tammy Turtle,UMass,800m,4:00:00

• We want a program that:


• reads in a results file like the one above
• extracts and prints only the results for a particular school
• with the name of the school omitted

• Basic approach:
• ask the user for the school of interest (the target school)
• read one line at a time from the file
• split the line into fields
• if the field corresponding to the school name matches
the target school, print out the other fields in that record

Splitting a String
• The String class includes a method named split().
• breaks a string into component strings
• takes a parameter indicating what delimiter should be
used when performing the split
• returns a String array containing the components

• Example:
> String sentence = "How now brown cow?";
> String[] words = sentence.split(" ");
> words[0]
"How"
> words[1]
"now"
> words[3]
"cow?"
> words.length
4

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 224


Extracting Data from a File (cont.)
import java.util.*; // needed for Scanner
import java.io.*; // needed for File
public class ExtractResults {
public static void main(String[] args)
throws FileNotFoundException {
Scanner console = new Scanner(System.in);
System.out.print("School to extract: ");
String targetSchool = console.nextLine();
Scanner input = new Scanner(new File("results.txt"));
while (input.hasNextLine()) {
String record = input.nextLine();
String[] fields = record.split(",");
if (fields[1].equals(targetSchool)) {
System.out.print(fields[0] + ",");
System.out.println(fields[2] + "," + fields[3]);
}
}
}
• How can we modify it to print a message when
}
no results are found for the target school?

Example Problem: Averaging Enrollments


• Let's say that we have a file showing how course enrollments
have changed over time:
cs111 90 100 120 115 140 170 130 135 125
cs105 14 8
cs108 40 35 30 42 38 26
cs101 180 200 175 190 200 230 160 154 120

• For each course, we want to compute the average enrollment.


• different courses have different numbers of values

• Initial pseudocode:
while (there is another course in the file) {
read the line corresponding to the course
split it into an array of fields
average the fields for the enrollments
print the course name and average enrollment
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 225


Example Problem: Averaging Enrollments (cont.)
cs108 40 35 30 42 38 26
cs111 90 100 120 115 140 170 130 135 125
cs105 14 8
cs101 180 200 175 190 200 230 160 154 120

• When we split a line into fields, we get an array of strings.


• example for the first line above:
{"cs108", "40", "35", "30", "42", "38", "26"}

• We can convert the enrollments from strings to integers using


a method called Integer.parseInt()
• example:
String[] fields = record.split(" ");
String courseName = fields[0];
int firstEnrollment = Integer.parseInt(fields[1]);
• note: parseInt() is a static method, so we call it using
its class name (Integer)

Example Problem: Averaging Enrollments (cont.)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 226


Other Details About Reading Text Files
• Although we think of a text file as being two-dimensional
(like a piece of paper), the computer treats it as a
one-dimensional string of characters.
• example: the file containing these lines
Hello, world.
How are you?
I'm tired.
is represented like this:
Hello, world.\nHow are you?\nI'm tired.\n

• When reading a file using a Scanner, you are limited to


sequential accesses in the forward direction.
• you can't back up
• you can't jump to an arbitrary location
• to go back to the beginning of the file,
you need to create a new Scanner object.

Optional Extra Topic: Writing to a Text File


• To write to a text file, we can use a PrintStream object,
which has the same methods that we've used with System.out:
• print(), println()

• Actually, System.out is a PrintStream that has been


constructed to print to the console.

• To instantiate a PrintStream for a file:


File f = new File("filename");
PrintStream output = new PrintStream(f);

• We can also combine these two steps:


PrintStream output = new PrintStream(
new File("filename"));

• If there's an existing file with the same name, it will be overwritten.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 227


Copying a Text File
import java.util.*; // needed for Scanner
import java.io.*; // needed for File
public class CopyFile {
public static void main(String[] args)
throws FileNotFoundException {
Scanner console = new Scanner(System.in);
System.out.print("Name of original file: ");
String original = console.next();
System.out.print("Name of copy: ");
String copy = console.next();

Scanner input = new Scanner(new File(original));


PrintStream output = new PrintStream(new File(copy));
while (input.hasNextLine()) {
String line = input.nextLine();
output.println(line);
}
} • How could we combine the two lines
} in the body of the while loop?

Our Track-Meet Program Revisited


import java.util.*; // needed for Scanner
import java.io.*; // needed for File
public class ExtractResults {
public static void main(String[] args)
throws FileNotFoundException {
Scanner console = new Scanner(System.in);
System.out.print("School to extract: ");
String targetSchool = console.nextLine();
Scanner input = new Scanner(new File("results.txt"));
while (input.hasNextLine()) {
String record = input.nextLine();
String[] fields = record.split(",");
if (fields[1].equals(targetSchool)) {
System.out.print(fields[0] + ",");
System.out.println(fields[2] + "," + fields[3]);
}
}
}
• How can we modify it to print the extracted results
}
to a separate file?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 228


Optional Extra Topic: Binary Files
• Not all files are text files.

• Binary files don't store the string representation of non-string


values.
• instead, they store their binary representation – the way
they are stored in memory

• Example: 125
• the text representation of 125 stores the string "125" –
i.e., the characters for the individual digits in the number
'1' '2' '5' 49 50 53

• the binary representation of 125 stores the four-byte


binary representation of the integer 125
0 0 0 125

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 229


Unit 4, Part 3

Recursion

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Review: Method Frames


• When you make a method call, the Java runtime sets aside
a block of memory known as the frame of that method call.
main

number otherNumber

• The frame is used to store:


• the formal parameters of the method
• any local variables - variables declared within the method

• A given frame can only be accessed by statements that are


part of the corresponding method call.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 230


Frames and the Stack
• The frames we've been speaking about are stored in a region
of memory known as the stack.

• For each method call, a new frame is added to the top of the
stack.
public class Foo {
public static int y(int i) {
int j = i * 3;
return j; i 8
} y(8)
j 24
public static int x(int i) {
int j = i - 2;
return y(i + j); i 5
} x(5)
public static void j 3
main(String[] args) {
System.out.println(x(5));
args
}
}

• When a method completes, its stack frame is removed.

Iteration
• Whenever we've encountered a problem that requires repetition,
we've used iteration - i.e., some type of loop.

• Sample problem: printing the series of integers from


n1 to n2, where n1 <= n2.
• example: printSeries(5, 10) should print the following:
5, 6, 7, 8, 9, 10

• Here's an iterative solution to this problem:


public static void printSeries(int n1, int n2) {
for (int i = n1; i < n2; i++) {
System.out.print(i + ", ");
}
System.out.println(n2);
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 231


Recursion
• An alternative approach to problems that require repetition
is to solve them using a method that calls itself.

• Applying this approach to the print-series problem gives:


public static void printSeries(int n1, int n2) {
if (n1 == n2) {
System.out.println(n2);
} else {
System.out.print(n1 + ", ");
printSeries(n1 + 1, n2);
}
}

• A method that calls itself is a recursive method.

• This approach to problem-solving is known as recursion.

Tracing a Recursive Method


public static void printSeries(int n1, int n2) {
if (n1 == n2) {
System.out.println(n2);
} else {
System.out.print(n1 + ", ");
printSeries(n1 + 1, n2);
}
}

• What happens when we execute printSeries(5, 7)?


printSeries(5, 7):
System.out.print(5 + ", ");
printSeries(6, 7):
System.out.print(6 + ", ");
printSeries(7, 7):
System.out.println(7);
return
return
return

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 232


Recursive Problem-Solving
• When we use recursion, we solve a problem by reducing it
to a simpler problem of the same kind.

• We keep doing this until we reach a problem that is


simple enough to be solved directly.

• This simplest problem is known as the base case.


public static void printSeries(int n1, int n2) {
if (n1 == n2) { // base case
System.out.println(n2);
} else {
System.out.print(n1 + ", ");
printSeries(n1 + 1, n2);
}
}

• The base case stops the recursion, because it doesn't


make another call to the method.

Recursive Problem-Solving (cont.)


• If the base case hasn't been reached, we execute the
recursive case.
public static void printSeries(int n1, int n2) {
if (n1 == n2) { // base case
System.out.println(n2);
} else { // recursive case
System.out.print(n1 + ", ");
printSeries(n1 + 1, n2);
}
}

• The recursive case:


• reduces the overall problem to one or more simpler problems
of the same kind
• makes recursive calls to solve the simpler problems

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 233


Structure of a Recursive Method
recursiveMethod(parameters) {
if (stopping condition) {
// handle the base case
} else {
// recursive case:
// possibly do something here
recursiveMethod(modified parameters);
// possibly do something here
}
}

• There can be multiple base cases and recursive cases.

• When we make the recursive call, we typically use


parameters that bring us closer to a base case.

Tracing a Recursive Method: Second Example


public static void mystery(int i) {
if (i <= 0) { // base case
return;
}
// recursive case
System.out.println(i);
mystery(i - 1);
System.out.println(i);
}

• What happens when we execute mystery(2)?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 234


Printing a File to the Console
• Here's a method that prints a file using iteration:
public static void print(Scanner input) {
while (input.hasNextLine()) {
System.out.println(input.nextLine());
}
}

• Here's a method that uses recursion to do the same thing:


public static void printRecursive(Scanner input) {
// base case
if (!input.hasNextLine()) {
return;
}
// recursive case
System.out.println(input.nextLine());
printRecursive(input); // print the rest
}

Printing a File in Reverse Order


• What if we want to print the lines of a file in reverse order?

• It's not easy to do this using iteration. Why not?

• It's easy to do it using recursion!

• How could we modify our previous method to make it


print the lines in reverse order?
public static void printRecursive(Scanner input) {
if (!input.hasNextLine()) { // base case
return;
}
String line = input.nextLine();
System.out.println(line);
printRecursive(input); // print the rest
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 235


Printing a File in Reverse Order (cont.)
• An iterative approach to reversing the file would need to:
• read all of the lines in the file and store them in a
temporary data structure (e.g., an array)
• retrieve the lines from the data structure and
print them in reverse order

• The recursive method doesn't need a separate data structure.


• the lines are stored in the stack frames for the
recursive method calls!

A Recursive Method That Returns a Value


• Simple example: summing the integers from 1 to n
public static int sum(int n) {
if (n <= 0) {
return 0;
}
int rest = sum(n - 1);
return n + rest;
}

• Example of this approach to computing the sum:


sum(6) = 6 + sum(5)
= 6 + 5 + sum(4)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 236


Tracing a Recursive Method
public static int sum(int n) {
if (n <= 0) {
return 0;
}
int rest = sum(n - 1);
return n + rest;
}

• What happens when we execute int x = sum(3);


from inside the main() method?

Tracing a Recursive Method on the Stack


public static int sum(int n) {
if (n <= 0) {
return 0;
} The final result
int rest = sum(n - 1); gets built up
return n + rest; on the way back
} from the base case!
base case
Example: sum(3) n 0
rest
return 0 rest = sum(0)
n 1 n 1 n 1 =0
rest rest rest 0
return 1+0
n 2 n 2 n 2 n 2 n 2
rest rest rest rest rest 1
return 2+1
n 3 n 3 n 3 n 3 n 3 n 3 n 3
rest rest rest rest rest rest rest 3
return 3+3
time final result: 6

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 237


Another Option for Tracing a Recursive Method
public static int sum(int n) {
if (n <= 0) {
return 0;
}
int rest = sum(n - 1);
return n + rest;
}

Infinite Recursion
• We have to ensure that a recursive method will eventually
reach a base case, regardless of the initial input.

• Otherwise, we can get infinite recursion.


• produces stack overflow - there's no room for
more frames on the stack!

• Example: here's a version of our sum() method that uses


a different test for the base case:
public static int sum(int n) {
if (n == 0) {
return 0;
}
int rest = sum(n - 1);
return n + rest;
}
• what values of n would cause infinite recursion?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 238


Designing a Recursive Method
1. Start by programming the base case(s).
• What instance(s) of this problem can I solve directly
(without looking at anything smaller)?

2. Find the recursive substructure.


• How could I use the solution to any smaller version
of the problem to solve the overall problem?

3. Solve the smaller problem using a recursive call!


• store its result in a variable

4. Do your one step.


• build your solution from the result of the recursive call
• use concrete cases to figure out what you need to do

Processing a String Recursively


• A string is a recursive data structure. It is either:
• empty ("")
• a single character, followed by a string

• Thus, we can easily use recursion to process a string.


• process one or two of the characters ourselves
• make a recursive call to process the rest of the string

• Example: print a string vertically, one character per line:


public static void printVertical(String str) {
if (str == null || str.equals("")) {
return;
}

System.out.println(str.charAt(0)); // first char


printVertical(str.substring(1)); // rest of string
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 239


Short-Circuited Evaluation
• The second operand of both the && and || operators
will not be evaluated if the result can be determined on the
basis of the first operand alone.

• expr1 || expr2
if expr1 evaluates to true, expr2 is not evaluated,
because we already know that expr1 || expr2 is true
• example from the last slide:
if (str == null || str.equals("")) {
return;
}
// if str is null, we won't check for empty string.
// This prevents a null pointer exception!

• expr1 && expr2


if expr1 evaluates to , expr2 is not evaluated,
because we already know that expr1 && expr2 is .

Counting Occurrences of a Character in a String


• numOccur(c, s) should return the number of times that
the character c appears in the string s
• numOccur('n', "banana") should return 2
• numOccur('a', "banana") should return 3

• Take the approach outlined earlier:


• base case: empty string (or null)
• delegate s.substring(1) to the recursive call
• we're responsible for handling s.charAt(0)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 240


Applying the String-Processing Template
public static int numOccur(char c, String s) {
if (s == null || s.equals("")) { // base case
return __________;
} else { // recursive case
int rest = __________________;
// do our one step!
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 241


Determining Our One Step
public static int numOccur(char c, String s) {
if (s == null || s.equals("")) {
return 0;
} else {
int rest = numOccur(c, s.substring(1));
// do our one step!

• In our one step, we take care of s.charAt(0).


• we build the solution to the larger problem on the
solution to the smaller problem (in this case, rest)
• does what we do depend on the value of s.charAt(0)?

• Use concrete cases to figure out the logic!

Consider this concrete case…


public static int numOccur(char c, String s) {
if (s == null || s.equals("")) {
return 0;
} else {
int rest = numOccur(c, s.substring(1));
// do our one step!
...

numOccur('r', "recurse")

numOccur('r', "recurse")
c = 'r', s = "recurse"

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 242


Consider Concrete Cases
numOccur('r', "recurse") # first char is a match
• what is its solution?
• what is the next smaller subproblem?
• what is the solution to that subproblem?
• how can we use the solution to the subproblem?
What is our one step?

numOccur('a', "banana") # first char is not a match


• what is its solution?
• what is the next smaller subproblem?
• what is the solution to that subproblem?
• how can we use the solution to the subproblem?
What is our one step?

Now complete the method!


public static int numOccur(char c, String s) {
if (s == null || s.equals("")) {
return 0;
} else {
int rest = numOccur(c, s.substring(1));
if (s.charAt(0) == c) {

return ___________________;
} else {

return ___________________;
}
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 243


Tracing a Recursive Method on the Stack
public static int numOccur(char c, String s) {
if (s == null || s.equals("")) {
return 0;
} else {
int rest = numOccur(c, s.substring(1));
The final result
if (s.charAt(0) == c) { gets built up
return 1 + rest; on the way back
} else {
return rest;
from the base case!
}
}
base case
} s ""
numOccur('a', "aha") rest
return 0
s "a" s "a" s "a"
rest rest rest 0
return 1+0
s "ha" s "ha" s "ha" s "ha" s "ha"
rest rest rest rest rest 1
return 1
s "aha" s "aha" s "aha" s "aha" s "aha" s "aha" s "aha"
rest rest rest rest rest rest rest 1
return 1+1
time

Common Mistake
• This version of the method does not work:
public static int numOccur(char c, String s) {
if (s == null || s.equals("")) {
return 0;
}

int count = 0;
if (s.charAt(0) == c) {
count++;
}

numOccur(c, s.substring(1));
return count;
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 244


Another Faulty Approach
• Some people make count "global" to fix the prior version:
public static int count = 0;
public static int numOccur(char c, String s) {
if (s == null || s.equals("")) {
return 0;
}
if (s.charAt(0) == c) {
count++;
}
numOccur(c, s.substring(1));
return count;
}

• Not recommended, and not allowed on the problem sets!

• Problems with this approach?

Recursion vs. Iteration


• Some problems are much easier to solve using recursion.

• Other problems are just as easy to solve using iteration.

• Recursion is a bit more costly because of the overhead involved


in invoking a method.
• also: in some cases, there may not be room on the stack

• Rule of thumb:
• if it's easier to formulate a solution recursively, use recursion,
unless the cost of doing so is too high
• otherwise, use iteration

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 245


Extra Practice: A Recursive Palindrome Checker
• A palindrome is a string that reads the same forward and
backward.
• examples: "radar", "mom", "abcddcba"

• isPal(s) should return true if s is a palindrome,


and false otherwise.

• We need more than one base case. What are they?

• How should we reduce the problem in the recursive call?

Consider Concrete Cases!


isPal("radar")
• what is its solution?
• what is the next smaller subproblem?
• what is the solution to that subproblem?
• how can we use the solution to the subproblem...?
What is our one step?

isPal("modem")
• what is its solution?
• what is the next smaller subproblem?
• what is the solution to that subproblem?
• how can we use the solution to the subproblem...?
What is our one step?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 246


A Recursive Palindrome Checker (cont.)
• Method definition (assuming no nulls):

public static boolean isPal(String s) {


int len = s.length();
if (len <= 1) {

return __________;

} else if (_________________________________) {

return __________;
} else {

boolean isPalRest = _________________________;

// do our one step!

}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 247


Unit 5, Part 1

Classes as Blueprints:
How to Define New Types of Objects

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Types of Decomposition
• When writing a program, it's important to decompose it into
manageable pieces.

• We've already seen how to use procedural decomposition.


• break a task into smaller subtasks, each of which gets
its own method

• Another way to decompose a program is to view it as a


collection of objects.
• referred to as object-oriented programming

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 248


Review: What is an Object?
• An object groups together:
• one or more data values (the object's fields)
• a set of operations that the object can perform
(the object's methods)

Review: Using an Object's Methods


• An object's methods are different from the static methods
that we've been writing thus far.
• they're called non-static or instance methods

• When using an instance method, we specify the object


to which the method belongs by using dot notation:
String firstName = "Perry";
int len = firstName.length();

• Using an instance method is like sending a message


to an object, asking it to perform an operation.

• We refer to the object on which the method is invoked


as either:
• the called object
• the current object

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 249


Review: Classes as Blueprints
• We've been using classes as containers for our programs.

• A class can also serve as a blueprint – as the definition of


a new type of object.
• specifying the fields and methods that objects of that type
will have

• The objects of a given class are built according to its blueprint.

• Objects of a class are referred to as instances of the class.

Rectangle Objects
• Java comes with a built-in Rectangle class.
• in the java.awt package

• Each Rectangle object has the following fields:


• x – the x coordinate of its upper left corner
• y – the y coordinate of its upper left corner
• width
• height

• Here's an example of one:


x 200
y 150

width 50
height 30

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 250


Rectangle Methods
• A Rectangle's methods include:
void grow(int h, int v)
void translate(int x, int y)
double getWidth()
double getHeight()
double getX()
double getY()

Writing a "Blueprint Class"


• To illustrate how to define a new type of object,
let's write our own class for Rectangle objects.
public class Rectangle {
...

• As always, the class definition goes in an appropriately named


text file.
• in this case: Rectangle.java

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 251


Using Fields to Capture an Object's State
• Here's the first version of our Rectangle class:
public class Rectangle {
int x;
int y;
int width;
int height;
}

• it declares four fields, x


each of which stores an int
y
• each Rectangle object gets width
its own set of these fields
height

• Another name for a field is an instance variable.

Using Fields to Capture an Object's State (cont.)


• For now, we'll create Rectangle objects like this:
Rectangle r1 = new Rectangle();

• The fields are initially filled with r1 x 0


the default values for their types.
y 0
• just like array elements
width 0
height 0

• Fields can be accessed


r1 x 10
using dot notation:
r1.x = 10; y 20
r1.y = 20; width 100
r1.width = 100;
r1.height = 50; height 50

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 252


Client Programs
• Our Rectangle class is not a program.
• it has no main method

• Instead, it will be used by code defined in other classes.


• referred to as client programs or client code

• More generally, when we define a new type of object,


we create a building block that can be used in other code.
• just like the objects from the built-in classes:
String, Scanner, File, etc.
• our programs have been clients of those classes

Initial Client Program


public class RectangleClient {
public static void main(String[] args) {
Rectangle r1 = new Rectangle();
r1.x = 10; r1.y = 20;
r1.width = 100; r1.height = 50;
Rectangle r2 = new Rectangle();
r2.x = 50; r2.y = 100;
r2.width = 20; r2.height = 80;
System.out.println("r1: " + r1.width + " x " + r1.height);
int area1 = r1.width * r1.height;
System.out.println("area = " + area1);
System.out.println("r2: " + r2.width + " x " + r2.height);
int area2 = r2.width * r2.height;
System.out.println("area = " + area2);
// grow both rectangles
r1.width += 50; r1.height += 10;
r2.width += 5; r2.height += 30;
System.out.println("r1: " + r1.width + " x " + r1.height);
System.out.println("r2: " + r2.width + " x " + r2.height);
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 253


Using Methods to Capture an Object's Behavior
• It would be useful to have a method for growing a Rectangle.

• One option would be to define a static method:


public static void grow(Rectangle r, int dWidth, int dHeight) {
r.width += dWidth;
r.height += dHeight;
}

• This would allow us to replace the statements


r1.width += 50;
r1.height += 10;
with the method call
Rectangle.grow(r1, 50, 10);

Using Methods to Capture an Object's Behavior


• It would be useful to have a method for growing a Rectangle.

• One option would be to define a static method in our


Rectangle class:
public static void grow(Rectangle r, int dWidth, int dHeight) {
r.width += dWidth;
r.height += dHeight;
}

• This would allow us to replace these statements in the client


r1.width += 50;
r1.height += 10;
with the method call
Rectangle.grow(r1, 50, 10);

(Note: We need to use the class name, because we're calling


the method from outside the Rectangle class.)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 254


Using Methods to Capture an Object's Behavior (cont.)
• A better approach is to give each Rectangle object
the ability to grow itself.

• We do so by defining a non-static or instance method.

• We'll use dot notation to call the instance method:


r1.grow(50, 10);
instead of Rectangle.grow(r1, 50, 10);

• This is like sending a message to r1, asking it to grow itself.

Using Methods to Capture an Object's Behavior (cont.)


• Here's our grow instance method:
public void grow(int dWidth, int dHeight) { // no static
this.width += dWidth;
this.height += dHeight;
}

• We don't pass the Rectangle object as an explicit parameter.

• Instead, the Java keyword this gives us access to


the called object.
• every instance method has this special variable
• referred to as the implicit parameter

• Example: r1.grow(50, 10)


• r1 is the called object
• this.width gives us access to r1's width field
• this.height gives us access to r1's height field

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 255


Comparing the Static and Non-Static Versions
• Static:
public static void grow(Rectangle r, int dWidth, int dHeight) {
r.width += dWidth;
r.height += dHeight;
}

• sample method call: Rectangle.grow(r1, 50, 10);

• Non-static:
public void grow(int dWidth, int dHeight) {
this.width += dWidth;
this.height += dHeight;
}

• there's no keyword static in the method header


• the Rectangle object is not an explicit parameter
• the implicit parameter this gives access to the object
• sample method call: r1.grow(50, 10);

Omitting the Keyword this


• The use of this to access the fields is optional.
• example:
public void grow(int dWidth, int dHeight) {
width += dWidth;
height += dHeight;
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 256


Another Example of an Instance Method
• Here's an instance method for getting the area of a Rectangle:
public int area() {
return this.width * this.height;
}

• Sample method calls: r1 x 10


int area1 = r1.area();
y 20
int area2 = r2.area();
width 100
• we're asking r1 and r2 to
give us their areas height 50

• no explicit parameters
r2 x 50
are needed because
the necessary info. y 100
is in the objects' fields! width 20
height 80

Types of Instance Methods


• There are two main types of instance methods:
• mutators – methods that change an object's internal state
• accessors – methods that retrieve information from an object
without changing its state

• Examples of mutators:
• grow() in our Rectangle class

• Examples of accessors:
• area() in our Rectangle class
• String methods: length(), substring(), charAt()

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 257


Second Version of our Rectangle Class
public class Rectangle {
int x;
int y;
int width;
int height;

public void grow(int dWidth, int dHeight) {


this.width += dWidth;
this.height += dHeight;
}

public int area() {


return this.width * this.height;
}
}

Which method call increases r's height by 5?


public class Rectangle {
int x;
int y;
int width;
int height;

public void grow(int dWidth, int dHeight) {


this.width += dWidth;
this.height += dHeight;
}

public int area() {


return this.width * this.height;
}
}

• Consider this client code:


Rectangle r = new Rectangle();
r.width = 10;
r.height = 15;
______???______;

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 258


Initial Client Program
public class RectangleClient {
public static void main(String[] args) {
Rectangle r1 = new Rectangle();
r1.x = 10; r1.y = 20;
r1.width = 100; r1.height = 50;
Rectangle r2 = new Rectangle();
r2.x = 50; r2.y = 100;
r2.width = 20; r2.height = 80;
System.out.println("r1: " + r1.width + " x " + r1.height);
int area1 = r1.width * r1.height;
System.out.println("area = " + area1);
System.out.println("r2: " + r2.width + " x " + r2.height);
int area2 = r2.width * r2.height;
System.out.println("area = " + area2);
// grow both rectangles
r1.width += 50; r1.height += 10;
r2.width += 5; r2.height += 30;
System.out.println("r1: " + r1.width + " x " + r1.height);
System.out.println("r2: " + r2.width + " x " + r2.height);
}
}

Revised Client Program


public class RectangleClient {
public static void main(String[] args) {
Rectangle r1 = new Rectangle();
r1.x = 10; r1.y = 20;
r1.width = 100; r1.height = 50;
Rectangle r2 = new Rectangle();
r2.x = 50; r2.y = 100;
r2.width = 20; r2.height = 80;
System.out.println("r1: " + r1.width + " x " + r1.height);
System.out.println("area = " + r1.area());
System.out.println("r2: " + r2.width + " x " + r2.height);
System.out.println("area = " + r2.area());
// grow both rectangles
r1.grow(50, 10);
r2.grow(5, 30);
System.out.println("r1: " + r1.width + " x " + r1.height);
System.out.println("r2: " + r2.width + " x " + r2.height);
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 259


Practice Defining Instance Methods
• Add a mutator method that moves the rectangle to the right
by a specified amount.

public ______ moveRight(___________________) {

• Add an accessor method that determines if the rectangle


is a square (true or false).

public __________ isSquare(____________) {

Defining a Constructor
• Our current client program has to use several lines
to initialize each Rectangle object:
Rectangle r1 = new Rectangle();
r1.x = 10; r1.y = 20;
r1.width = 100; r1.height = 50;

• We'd like to be able to do something like this instead:


Rectangle r1 = new Rectangle(10, 20, 100, 50);

• To do so, we need to define a constructor, a special method


that initializes the state of an object when it is created.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 260


Defining a Constructor (cont.)
• Here it is:
public Rectangle(int initialX, int initialY,
int initialWidth, int initialHeight) {
this.x = initialX;
this.y = initialY;
this.width = initialWidth;
this.height = initialHeight;
}

• General syntax for a constructor:


public ClassName(parameter list) {
body of the constructor
}

• Note that a constructor has no return type.

Third Version of our Rectangle Class


public class Rectangle {
int x;
int y;
int width;
int height;

public Rectangle(int initialX, int initialY,


int initialWidth, int initialHeight) {
this.x = initialX;
this.y = initialY;
this.width = initialWidth;
this.height = initialHeight;
}

public void grow(int dWidth, int dHeight) {


this.width += dWidth;
this.height += dHeight;
}

public int area() {


return this.width * this.height;
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 261


Revised Client Program
public class RectangleClient {
public static void main(String[] args) {
Rectangle r1 = new Rectangle(10, 20, 100, 50);
Rectangle r2 = new Rectangle(50, 100, 20, 80);
System.out.println("r1: " + r1.width + " x " + r1.height);
System.out.println("area = " + r1.area());
System.out.println("r2: " + r2.width + " x " + r2.height);
System.out.println("area = " + r2.area());
// grow both rectangles
r1.grow(50, 10);
r2.grow(5, 30);
System.out.println("r1: " + r1.width + " x " + r1.height);
System.out.println("r2: " + r2.width + " x " + r2.height);
}
}

A Closer Look at Creating an Object


• What happens when the following line is executed?
Rectangle r1 = new Rectangle(10, 20, 100, 50);

• Several different things actually happen:


1) a new Rectangle object is created
• initially, all fields have their default values
2) the constructor is then called to assign values to the fields
3) a reference to the new object is stored in the variable r1

r1 x 0 r1 x 10 r1 x 10
y 0 y 20 y 20
width 0 width 100 width 100
height 0 height 50 height 50

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 262


Limiting Access to Fields
• The current version of our Rectangle class allows clients
to directly access a Rectangle object's fields:
r1.width = 100;
r1.height += 20;

• This means that clients can make inappropriate changes:


r1.width = -100;

• To prevent this, we can declare the fields to be private:


public class Rectangle {
private int x;
private int y;
private int width;
private int height;
...
}

• This indicates that these fields can only be accessed or


modified by methods that are part of the Rectangle class.

Limiting Access to Fields (cont.)


• Now that the fields are private, our client program won't compile:

public class RectangleClient {


public static void main(String[] args) {
Rectangle r1 = new Rectangle(10, 20, 100, 50);
Rectangle r2 = new Rectangle(50, 100, 20, 80);
System.out.println("r1: " + r1.width + " x " + r1.height);
System.out.println("area = " + r1.area());
System.out.println("r2: " + r2.width + " x " + r2.height);
System.out.println("area = " + r2.area());
// grow both rectangles
r1.grow(50, 10);
r2.grow(5, 30);
System.out.println("r1: " + r1.width + " x " + r1.height);
System.out.println("r2: " + r2.width + " x " + r2.height);
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 263


Adding Accessor Methods for the Fields
public class Rectangle {
private int x;
private int y;
private int width;
private int height;
...
public int getX() {
return this.x;
}
public int getY() {
return this.y;
}
public int getWidth() {
return this.width;
}
public int getHeight() {
return this.height;
}
}

• These methods are public, which indicates that they can be


used by code that is outside the Rectangle class.

Revised Client Program


public class RectangleClient {
public static void main(String[] args) {
Rectangle r1 = new Rectangle(10, 20, 100, 50);
Rectangle r2 = new Rectangle(50, 100, 20, 80);
System.out.println("r1: " + r1.getWidth() + " x " +
r1.getHeight());
System.out.println("area = " + r1.area());
System.out.println("r2: " + r2.getWidth() + " x " +
r2.getHeight());
System.out.println("area = " + r2.area());
// grow both rectangles
r1.grow(50, 10);
r2.grow(5, 30);
System.out.println("r1: " + r1.getWidth() + " x " +
r1.getHeight());
System.out.println("r2: " + r2.getWidth() + " x " +
r2.getHeight());
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 264


Access Modifiers
• public and private are known as access modifiers.
• they specify where a class, field, or method can be used

• A class is usually declared to be public:


public class Rectangle {
• indicates that objects of the class can be used anywhere,
including in other classes

• Fields are usually declared to be private.

• Methods are usually declared to be public.

• We occasionally define private methods.


• serve as helper methods for the public methods
• cannot be invoked by code that is outside the class

Allowing Only Appropriate Changes


• To allow for appropriate changes to an object,
we add whatever mutator methods make sense.

• These methods can prevent inappropriate changes:


public void setLocation(int newX, int newY) {
if (newX < 0 || newY < 0) {
throw new IllegalArgumentException();
}
this.x = newX;
this.y = newY;
}

• Throwing an exception ends the method early.

• If the caller of the method doesn't handle the exception,


it will crash.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 265


Allowing Only Appropriate Changes (cont.)
• Here are two other mutator methods:
public void setWidth(int newWidth) {
if (newWidth <= 0) {
throw new IllegalArgumentException();
}
this.width = newWidth;
}

public void setHeight(int newHeight) {


if (newHeight <= 0) {
throw new IllegalArgumentException();
}
this.height = newHeight;
}

Instance Methods Calling Other Instance Methods


• Here's another mutator method that we already had:
public void grow(int dWidth, int dHeight) {
this.width += dWidth;
this.height += dHeight;
}

• However, it doesn't prevent inappropriate changes.

• Rather than adding error-checking to it, we can have it call


the new mutator methods:
public void grow(int dWidth, int dHeight) {
this.setWidth(this.width + dWidth);
this.setHeight(this.height + dHeight);
}
• we use this to call another method in the same object
• those other methods perform the necessary error-checking

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 266


Revised Constructor
• To prevent invalid values in the fields of a Rectangle object,
we also need to modify our constructor.

• Here again, we take advantage of the error-checking code


that's already present in the mutator methods:
public Rectangle(int initialX, int initialY,
int initialWidth, int initialHeight)
{
this.setLocation(initialX, initialY);
this.setWidth(initialWidth);
this.setHeight(initialHeight);
}

• setLocation, setWidth, and setHeight operate on


the newly created Rectangle object

Encapsulation
• Encapsulation is one of the key principles of object-oriented
programming.

• It refers to the practice of “hiding” the implementation of


a class from users of the class.
• prevent direct access to the internals of an object
• making the fields private
• provide limited, indirect access through a set of methods
• making them public

• In addition to preventing inappropriate changes,


encapsulation allows us to change the implementation
of a class without breaking the client code that uses it.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 267


Abstraction
• Abstraction involves focusing on the essential properties of
something, rather than its inner or low-level details.
• an important concept in computer science

• Encapsulation leads to abstraction.


• example: rather than treating a Rectangle as four ints,
we treat it as an object that's capable of growing itself,
changing its location, etc.

Practice Defining Instance Methods


• Add a mutator method that scales the dimensions of
a Rectangle object by a specified factor.
• make the factor a double, to allow for fractional values
• take advantage of existing mutator methods
• use a type cast to turn the result back into an integer
public _________ scale(___________________) {

• Add an accessor method that gets the perimeter of


a Rectangle object.

public _________ perimeter(___________________) {

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 268


Testing for Equivalent Objects
• Let's say that we have two different Rectangle objects,
both of which represent equivalent rectangles:
Rectangle rect1 = new Rectangle(10, 100, 20, 55);
Rectangle rect2 = new Rectangle(10, 100, 20, 55);

x 10
y 100
rect1 width 20
x 10
height 55
rect2 y 100

width 20
height 55

• What is the value of the following condition?


rect1 == rect2

Testing for Equivalent Objects (cont.)


• The condition
rect1 == rect2
compares the references stored in rect1 and rect2.

memory location: 2000


x 10
y 100
memory location: 3152
rect1 2000 width 20
x 10
height 55
rect2 3152 y 100

width 20
height 55

• It doesn't compare the objects themselves.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 269


Testing for Equivalent Objects (cont.)
• Recall: to test for equivalent objects, we need to use
the equals method:
rect1.equals(rect2)

• Java's built-in classes have equals methods that:


• return true if the two objects are equivalent to each other
• return false otherwise

Default equals() Method


• If we don't write an equals() method for a class,
objects of that class get a default version of this method.

• The default equals() just tests if the memory addresses


of the two objects are the same.
• the same as what == does!

• To ensure that we're able to test for equivalent objects,


we need to write our own equals() method.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 270


equals() Method for Our Rectangle Class
public boolean equals(Rectangle other) {
if (other == null) {
return false;
} else if (this.x != other.x) {
return false;
} else if (this.y != other.y) {
return false;
} else if (this.width != other.width) {
return false;
} else if (this.height != other.height) {
return false;
} else {
return true;
}
}
• Note: The method is able to access the fields in other
directly (without using accessor methods).
• Instance methods can access the private fields of any object
from the same class as the method.

equals() Method for Our Rectangle Class (cont.)

• Here's an alternative version:


public boolean equals(Rectangle other) {
return (other != null
&& this.x == other.x
&& this.y == other.y
&& this.width == other.width
&& this.height == other.height);
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 271


Converting an Object to a String
• The toString() method allows objects to be displayed
in a human-readable format.
• it returns a string representation of the object

• This method is called implicitly when you attempt to print an


object or when you perform string concatenation:
Rectangle r1 = new Rectangle(10, 20, 100, 80);
System.out.println(r1);

// the second line above is equivalent to:


System.out.println(r1.toString());

• If we don't write a toString() method for a class,


objects of that class get a default version of this method.
• here again, it usually makes sense to write
our own version

toString() Method for Our Rectangle Class


public String toString() {
return this.width + " x " + this.height;
}

• Note: the method does not do any printing.

• It returns a String that can then be printed.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 272


Revised Client Program
public class RectangleClient {
public static void main(String[] args) {
Rectangle r1 = new Rectangle(10, 20, 100, 50);
Rectangle r2 = new Rectangle(50, 100, 20, 80);
System.out.println("r1: " + r1);
System.out.println("area = " + r1.area());
System.out.println("r2: " + r2);
System.out.println("area = " + r2.area());
// grow both rectangles
r1.grow(50, 10);
r2.grow(5, 30);
System.out.println("r1: " + r1);
System.out.println("r2: " + r2);
}
}

Conventions for Accessors and Mutators


• Accessors:
• usually have no parameters
• all of the necessary info. is inside the called object
• have a non-void return type
• often have a name that begins with "get" or "is"
• examples: getWidth(), isSquare()
• but not always: area(), perimeter()

• Mutators:
• usually have one or more parameter
• usually have a void return type
• often have a name that begins with "set"
• examples: setLocation(), setWidth()
• but not always: grow(), scale()

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 273


The Implicit Parameter and Method Frames
• When we call an instance method, the implicit parameter
is included in its method frame.
• example: r1.grow(50, 10)

r1

x 10
grow
this y 20

dWidth 50 width 100

dHeight 10 height 50

• The method uses this to access the fields in the called object.
• even if the code doesn't explicitly use it
width += dWidth; this.width += dWidth;
height += dHeight; this.height += dHeight;

Example: Method Frames for Instance Methods


public class RectangleClient {
public static void main(String[] args) {
Rectangle r1 = new Rectangle(10, 20, 100, 50);
Rectangle r2 = new Rectangle(50, 100, 20, 80);
...
r1.grow(50, 10);
r2.grow(5, 30);
...

• After the objects are created:

x 10

y 20

width 100
x 50
height 50
main y 100

r1 width 20

r2 height 80

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 274


Example: Method Frames for Instance Methods
public class RectangleClient {
public static void main(String[] args) {
Rectangle r1 = new Rectangle(10, 20, 100, 50);
Rectangle r2 = new Rectangle(50, 100, 20, 80);
...
r1.grow(50, 10);
r2.grow(5, 30);
...

• During the method call r1.grow(50, 10):

grow x 10
this
y 20
dWidth 50
width 100
dHeight 10 x 50
height 50
main y 100

r1 width 20

r2 height 80

Example: Method Frames for Instance Methods


public class RectangleClient {
public static void main(String[] args) {
Rectangle r1 = new Rectangle(10, 20, 100, 50);
Rectangle r2 = new Rectangle(50, 100, 20, 80);
...
r1.grow(50, 10);
r2.grow(5, 30);
...

• After the method call r1.grow(50, 10):

x 10

y 20

width 150
x 50
height 60
main y 100

r1 width 20

r2 height 80

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 275


Example: Method Frames for Instance Methods
public class RectangleClient {
public static void main(String[] args) {
Rectangle r1 = new Rectangle(10, 20, 100, 50);
Rectangle r2 = new Rectangle(50, 100, 20, 80);
...
r1.grow(50, 10);
r2.grow(5, 30);
...

• During the method call r2.grow(5, 30):

grow x 10
this
y 20
dWidth 5
width 150
dHeight 30 x 50
height 60
main y 100

r1 width 20

r2 height 80

Example: Method Frames for Instance Methods


public class RectangleClient {
public static void main(String[] args) {
Rectangle r1 = new Rectangle(10, 20, 100, 50);
Rectangle r2 = new Rectangle(50, 100, 20, 80);
...
r1.grow(50, 10);
r2.grow(5, 30);
...

• After the method call r2.grow(5, 30):

x 10

y 20

width 150
x 50
height 60
main y 100

r1 width 25

r2 height 110

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 276


Why Mutators Don't Need to Return Anything
• A mutator operates directly on the called object,
so any changes it makes will be there after the method returns.
• example: the call r2.grow(5, 30) from the last slide

grow x 10
this
y 20
dWidth 5
width 150
dHeight 30 x 50
height 60
main y 100

r1 width 25

r2 height 110

• during this call, grow gets a copy of the reference in r2,


so it changes the object to which r2 refers

Variable Scope: Static vs. Non-Static Methods


public class Foo {
private int x;
public static int bar(int b, int c, Foo f) {
c = c + this.x; // would not compile
return 3*b + f.x; // would compile
}
public int boo(int d, Foo f) {
d = d + this.x + f.x; // would compile
return 2 * d;
}
}
• Static methods (like bar above) do NOT have a called object,
so they can't access its fields.
• Instance/non-static methods (like boo above) do have a called
object, so they can access its fields.
• Any method of a class can access fields in an object of that class
that is passed in as a parameter (like the parameter f above).

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 277


A Common Use of the Implicit Parameter
• Here's our setLocation method:
public void setLocation(int newX, int newY) {
if (newX < 0 || newY < 0) {
throw new IllegalArgumentException();
}
this.x = newX;
this.y = newY;
}

• Here's an equivalent version:


public void setLocation(int x, int y) {
if (x < 0 || y < 0) {
throw new IllegalArgumentException();
}
this.x = x;
this.y = y;
}

• When the parameters have the same names as the fields,


we must use this to access the fields.

Defining a Second Constructor


• Here's our Rectangle constructor:
public Rectangle(int initialX, int initialY,
int initialWidth, int initialHeight) {
this.setLocation(initialX, initialY);
this.setWidth(initialWidth);
this.setHeight(initialHeight);
}

• It requires four parameters:


Rectangle r1 = new Rectangle(10, 20, 100, 50);

• A class can have an arbitrary number of constructors,


provided that each of them has a distinct parameter list.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 278


Defining a Second Constructor (cont.)
• Here's a constructor that only takes values for width and height:
public Rectangle(int width, int height) {
this.setWidth(width);
this.setHeight(height);
this.x = 0;
this.y = 0;
}
• it puts the rectangle at the location (0, 0)

• Equivalently, we can call the original constructor,


and let it perform the actual assignments:
public Rectangle(int width, int height) {
this(0, 0, width, height); // call other constr.
}
• we use the keyword this instead of Rectangle
• this is the way that one constructor calls another

Practice Exercise: Writing Client Code


• Write a static method called processRectangle() that:
• takes a Rectangle object (call it r) and an integer
(call it delta) as parameters
• prints the existing dimensions and area of the Rectangle
(hint: take advantage of the toString() method)
• increases both of the Rectangle's dimensions by delta
• prints the new dimensions and area

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 279


Collections of Data
• There are many situations in which we need a program
to maintain a collection of data.

• Examples include:
• all of the grades on a given assignment/exam
• a simple database of song info (e.g., in a music player)

Using an Array for a Collection


• We've used an array to maintain a collection of primitive
data values.

grades 7 8 9 6 10 7 9 5

• It's also possible to have an array of objects:

suitNames

"clubs" "spades" "hearts" "diamonds"

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 280


A Class for a Collection
• Rather than just using an array, it's often helpful to create a
blueprint class for the collection.

• Example: a GradeSet class for a collection of grades from


a single assignment or exam
• possible field definitions:
public class GradeSet {
private String name;
private int possiblePoints;
private double[] grades;
private int gradeCount;

• The array of values is "inside" the collection object, along with


other relevant information associated with the collection.

• In addition, we would add methods for maintaining and


processing the collection.

A Blueprint Class for Grade Objects


• Rather than just representing the grades as ints or doubles,
we'll use a separate blueprint class for a single grade:
public class Grade {
private double rawScore;
private int latePenalty; // as a percent

• This allows us to store both the raw score


and the late penalty (if any).

• Constructors and methods include:


Grade(double raw, int late)
Grade(double raw)
getRawScore()
getLatePenalty()
setRawScore(double newScore)
setLatePenalty(int newPenalty)
getAdjustedScore() // with late penalty

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 281


Revised GradeSet Class
public class GradeSet {
private String name;
private int possiblePoints;
private Grade[] grades;
private int gradeCount;

• Here's what one of these objects would look like in memory:

q2 name "quiz 2"


possiblePoints 30
grades null null null …
gradeCount 1

rawScore 26
latePenalty 0

GradeSet Constructor/Methods
• Constructor:
GradeSet(String name, int possPts, int numGrades)

• Accessor methods:
String getName()
int getPossiblePoints()
int getGradeCount()
Grade getGrade(int i) // get grade at position i
double averageGrade(boolean includePenalty)

• Mutator methods:
void setName(String name)
void setPossiblePoints(int possPoints)
void addGrade(Grade g)
Grade removeGrade(int i) // remove grade at posn i

• Let's review the code for these, and write some of them
together.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 282


GradeSet Constructor/Methods

GradeSet Constructor/Methods

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 283


GradeSet: Adding a Grade

ps4 name "PS 4"


possiblePoints 100
grades null null null null
gradeCount 0

GradeSet ps4 = new GradeSet("PS 4", 100, 4);


ps4.addGrade(new Grade(95, 0));
ps4.addGrade(new Grade(80, 10));

GradeSet: Adding a Grade

ps4 name "PS 4"


possiblePoints 100
grades null null null
gradeCount 1

rawScore 95
latePenalty 0

GradeSet ps4 = new GradeSet("PS 4", 100, 4);


ps4.addGrade(new Grade(95, 0));
ps4.addGrade(new Grade(80, 10));

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 284


GradeSet: Adding a Grade

ps4 name "PS 4"


possiblePoints 100
grades null null
gradeCount 2

rawScore 95
latePenalty 0

rawScore 80
latePenalty 10

GradeSet ps4 = new GradeSet("PS 4", 100, 4);


ps4.addGrade(new Grade(95, 0));
ps4.addGrade(new Grade(80, 10));

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 285


Unit 5, Part 2

Inheritance and Polymorphism

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

A Class for Modeling an Automobile


public class Automobile {
private String make;
private String model;
private int year;
private int mileage;
private String plateNumber;
private int numSeats;
private boolean isSUV;
public Automobile(String make, String model, int year,
int numSeats, boolean isSUV) {
this.make = make;
this.model = model;
if (year < 1900) {
throw new IllegalArgumentException();
}
this.year = year;
this.numSeats = numSeats;
this.isSUV = isSUV;
this.mileage = 0;
this.plateNumber = "unknown";
}
public Automobile(String make, String model, int year) {
this(make, model, year, 5, false);
} // continued…

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 286


A Class for Modeling an Automobile (cont.)
public String getMake() {
return this.make;
}
public String getModel() {
return this.model;
}
public int getYear() {
return this.year;
}
public int getMileage() {
return this.mileage;
}
public String getPlateNumber() {
return this.plateNumber;
}
public int getNumSeats() {
return this.numSeats;
}
public boolean isSUV() {
return this.isSUV;
} // continued…

A Class for Modeling an Automobile (cont.)


public void setMileage(int newMileage) {
if (newMileage < this.mileage) {
throw new IllegalArgumentException();
}
this.mileage = newMileage;
}
public void setPlateNumber(String plate) {
this.plateNumber = plate;
}
public String toString() {
String str = this.make + " " + this.model;
str += "( " + this.numSeats + " seats)";
return str;
}
}

• There are no mutators for the other fields. Why not?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 287


Modeling a Related Class
• What if we now want to write a class to represent a taxi?

• The Taxi class will have the same fields and methods
as the Automobile class.

• It will also have its own fields and methods:


taxiID getID, setID
fareTotal getFareTotal, addFare
numFares getNumFares, getAverageFare
resetFareInfo

• We may also want the Taxi versions of some of the


Automobile methods to behave differently. Examples:
• we may want the toString method to include values
from different fields
• we may want the getNumSeats method to return only
the number of seats available for passengers

Inheritance
• To avoid redefining all of the Automobile fields and methods,
we specify that the Taxi class extends the Automobile class:
public class Taxi extends Automobile {

• The Taxi class will inherit the fields and methods of the
Automobile class.
• it doesn't have to redefine them

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 288


A Class for Modeling a Taxi
public class Taxi extends Automobile {
// We don't need to include the fields
// from Automobile!
private String taxiID;
private double fareTotal;
private int numFares;
// constructor goes here...
// We don't need to include the methods
// from Automobile!
public String getID() {
return this.taxiID;
}
public void addFare(double fare) {
if (fare < 0) {
throw new IllegalArgumentException();
}
this.fareTotal += fare;
this.numFares++;
}
...

Using Inherited Methods


• Because Taxi extends Automobile, we can invoke a method
defined in the Automobile class on a Taxi object.
• example:
Taxi t = new Taxi(…);
t.setMileage(25000);

• This works even though there is no setMileage method


defined in the Taxi class!
• Taxi inherits it from Automobile

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 289


Overriding an Inherited Method
• A subclass can override an inherited method,
replacing it with its own version.

• To override a method, the new method must have the same:


• return type
• name
• number and types of parameters

• Example: our Taxi class can define its own toString method:
public String toString() {
return "Taxi (id = " + this.taxiID + ")";
}
• it overrides the toString method inherited from Automobile

Rethinking Our Design


• What if we also want to be able to capture information
about other types of vehicles?
• motorcycles
• trucks

• The classes for these other vehicles should not inherit from
Automobile. Why not?

• Solution: define a Vehicle class


• fields and methods common to all vehicles are defined there
• leave automobile-specific state and behavior in Automobile
• everything else is inherited from Vehicle
• define Motorcycle and Truck classes that also inherit
from Vehicle

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 290


A Class for Modeling a Vehicle
public class Vehicle {
private String make;
private String model;
private int year;
private int mileage;
private String plateNumber;
private int numWheels; // this was not in Automobile
public Vehicle(String make, String model, int year,
int numWheels) {
this.make = make;
this.model = model;
if (year < 1900) {
throw new IllegalArgumentException();
}
this.year = year;
this.numWheels = numWheels;
this.mileage = 0;
this.plateNumber = "unknown";
}
public String getMake() {
return this.make;
}
// etc.

Revised Automobile Class


public class Automobile extends Vehicle {
// make, model, etc. are now inherited from Vehicle

// The following are specific to automobiles,


// so we leave them here.
private int numSeats;
private boolean isSUV;

// constructor goes here...

// getMake(), etc. are now inherited from Vehicle

// The following are specific to automobiles,


// so we leave them here.
public int getNumSeats() {
return this.numSeats;
}
public boolean isSUV() {
return this.isSUV;
}
...
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 291


Inheritance Hierarchy
• Inheritance leads classes to be organized in a hierarchy:

Vehicle

Motorcycle Automobile Truck

Limousine Taxi MovingVan TractorTrailer

• A class in Java inherits directly from at most one class.

• However, a class can inherit indirectly from a class higher up


in the hierarchy.
• example: Taxi inherits indirectly from Vehicle

Terminology
• When class C extends class D (directly or indirectly):
• class D is known as a superclass or base class of C
• super – comes above it in the hierarchy
• class C is known as a subclass or derived class of D
• sub – comes below it in the hierarchy

• Examples:
• Automobile is a superclass of Vehicle
Taxi and Limosine
• Taxi is a subclass of
Automobile and Vehicle Automobile

Limousine Taxi

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 292


Deciding Where to Define a Method
• Assume we only care about the number of axles in truck vehicles.

• Thus, we define the getNumAxles method in the Truck class,


rather than in the Vehicle class.
public int getNumAxles() {
return this.getNumWheels() / 2;
}
• it will be inherited by subclasses of Truck
• it won't be available to non-truck subclasses of Vehicle

• We override this method in the TractorTrailer class,


because tractor trailers have four wheels on all but the front axle:
public int getNumAxles() {
int numBackWheels = this.getNumWheels() – 2;
return 1 + numBackWheels/4;
}

What is Accessible From a Superclass?


• A subclass has direct access to the public fields and methods
of a superclass.

• A subclass does not have direct access to the private


fields and methods of a superclass.

• Example: we can think of an Automobile object as follows:


make
model
year
private fields inherited from Vehicle.
mileage They cannot be accessed directly
plateNumber by methods in Automobile.
numWheels
numSeats
fields defined in Automobile.
isSUV
They can be accessed directly
by methods in Automobile.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 293


What is Accessible From a Superclass? (cont.)
• Example: now that make and model are defined in Vehicle,
we're no longer able to access them directly in the
Automobile version of toString: won't compile
public String toString() {
String str = this.make + " " + this.model;
str += " ( " + this.numSeats + " seats)";
return str;
}

• Instead, we need to make method calls to access the


inherited fields:
public String toString() {
String str = this.getMake() + " " +
this.getModel();
str += " ( " + this.numSeats + " seats)";
return str;
}

What is Accessible From a Superclass? (cont.)


• Faulty approach: redefine the inherited fields in the subclass
public class Vehicle {
private String make;
private String model;

}

public class Automobile extends Vehicle {


private String make; // NOT a good idea!
private String model;

}

• You should NOT do this!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 294


Writing a Constructor for a Subclass
• Another example of illegally accessing inherited private fields:
public Automobile(String make, String model, int year,
int numSeats, boolean isSUV) {
this.make = make;
this.model = model;
...
}

• To initialize inherited fields, a constructor should invoke


a constructor from the superclass.
public Automobile(String make, String model, int year,
int numSeats, boolean isSUV) {
super(make, model, year, 4); // 4 is for numWheels
this.numSeats = numSeats;
this.isSUV = isSUV;
}
• use the keyword super followed by appropriate
parameters for the superclass constructor
• must be done as the very first line of the constructor

Writing a Constructor for a Subclass (cont.)


• If a subclass constructor doesn't explicitly invoke a
superclass constructor, the compiler tries to insert a call
to the superclass constructor with no parameters.

• If there isn't such a constructor, we get a compile-time error.


• example: this constructor won't compile:
public Taxi(String make, String model, int year, String ID)
{
this.taxiID = ID;
}

• the compiler attempts to insert the following call:


super();
• there isn't an Automobile constructor with no parameters

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 295


The Object Class
• If a class doesn't explicitly extend another class,
it implicitly extends a special class called Object.

• Thus, the Object class is at the top of the class hierarchy.


• all classes are subclasses of this class
• the default toString and equals methods are defined
in this class

Object

Vehicle String Temperature

Motorcycle Automobile Truck


... ...

Inheritance in the Java API

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 296


More Examples of Method Overriding
• Vehicle inherits the fields and methods of Object.

• The inherited toString method isn't very helpful.

• We define a Vehicle version that overrides the inherited one:


public String toString() { // Vehicle version
String str = this.make + " " + this.model;
return str;
}

• When toString is invoked on a Vehicle object,


the Vehicle version is executed:
Vehicle v = new Vehicle("Radio Flyer",
"Classic Tricycle", 2002, 3);
System.out.println(v);

outputs: Radio Flyer Classic Tricycle

More Examples of Method Overriding (cont.)


• The Automobile class inherits the Vehicle version of
toString.

• If we didn't define a toString() method in Automobile,


the inherited version would be used.

• The Automobile version overrides the Vehicle version


so that the number of seats can be included in the string:
public String toString() {
String str = this.getMake() + " " +
this.getModel();
str += " ( " + this.numSeats + " seats)";
return str;
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 297


Invoking an Overriden Method
• When a subclass overrides an inherited method, we can
invoke the inherited version by using the keyword super.

• Example: the Automobile version of toString() begins with


the same fields as the Vehicle version:
public String toString() {
String str = this.getMake() + " " +
this.getModel();
str += " ( " + this.numSeats + " seats)";
return str;
}
• instead of calling the accessor methods, we can do this:
public String toString() {
String str = super.toString();
str += " ( " + this.numSeats + " seats)";
return str;
}

Another Example of Inheritance


• A square is a special type of rectangle.
• but the width and height must be the same

• Assume that we also want Square objects


to have a field for the unit of measurement (e.g., "cm").

• Square objects should mostly behave like Rectangle objects:


Rectangle r = new Rectangle(20, 30);
int area1 = r.area();
Square sq = new Square(40, "cm");
int area2 = sq.area();

• But there may be differences as well:


output:
System.out.println(r);
20 x 30

output:
System.out.println(sq); square with 40-cm sides

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 298


Another Example of Inheritance (cont.)
public class Rectangle {
private int width;
private int height;
...
public Rectangle(int initWidth, int initHeight) {
...
}
public int getWidth() {
...
}
... // other methods
}
public class Square extends Rectangle {
private String unit; // inherits other fields
public Square(int side, String unit) {
super(side, side);
this.unit = unit;
}
public String toString() { // overrides
String s = "square with ";
s += this.getWidth() + "-";
s += this.unit + " sides";
return s;
} // inherits other methods
}

Another Example of Inheritance (cont.)


public class Rectangle {
private int width;
private int height;
...
public Rectangle(int initWidth, int initHeight) {
...
}
public int getWidth() {
...
}
... // other methods
}
public class Square extends Rectangle {
private String unit; // inherits other fields
public Square(int side, String unit) {
super(side, side);
this.unit = unit;
}
public String toString() { // overrides
String s = "square with ";
s += this.getWidth() + "-";
s += this.unit + " sides";
return s;
} // inherits other methods
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 299


Another Example of Method Overriding
• The Rectangle class has the following mutator method:
public void setWidth(int w) {
if (w <= 0) {
throw new IllegalArgumentException();
}
this.width = w;
}

• The Square class inherits it. Why should we override it?

Which of these works?


A. // Square version, which overrides
// the version inherited from Rectangle
public void setWidth(int w) {
this.width = w;
this.height = w;
}

B. // Square version, which overrides


// the version inherited from Rectangle
public void setWidth(int w) {
this.setWidth(w);
this.setHeight(w);
}

C. either version would work

D. neither version would work

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 300


Accessing Methods from the Superclass
• The solution: use super to access the inherited version
of the method – the one we are overriding:
// Square version
public void setWidth(int w) {
super.setWidth(w); // call the Rectangle version
super.setHeight(w);
}

• Only use super if you want to call a method from


the superclass that has been overridden.

• If the method has not been overridden, use this as usual.

Accessing Methods from the Superclass


• We need to override all of the inherited mutators:
// Square versions
public void setWidth(int w) {
super.setWidth(w);
super.setHeight(w);
}
public void setHeight(int h) {
super.setWidth(h);
super.setHeight(h);
}
public void grow(int dw, int dh) {
if (dw != dh) {
throw new IllegalArgumentException();
}
super.setWidth(this.getWidth() + dw);
super.setHeight(this.getHeight() + dh);
}
getWidth() and getHeight()
are not overridden, so we use this.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 301


is-a Relationships
• We use inheritance to capture is-a relationships.
• an automobile is a vehicle
• a taxi is an automobile
• a tractor trailer is a truck

Object

Vehicle

Motorcycle Automobile Truck

Limousine Taxi MovingVan TractorTrailer

has-a Relationships
• Another type of relationship is a has-a relationship.
• one type of object "owns" another type of object
• example: a driver has a vehicle

• Inheritance should not be used to capture has-a relationships.


• it does not make sense to make the Driver class
a subclass of Vehicle

• Instead, we give the "owner" object a field that refers to


the "owned" object:
public class Driver {
String name;
String ID;
Vehicle v;
...

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 302


Polymorphism
• We've been using reference variables like this:
Automobile a = new Automobile("Ford", "Model T", …);
• variable a is declared to be of type Automobile
• it holds a reference to an Automobile object

• In addition, a reference variable of type T can hold a reference


to an object from a subclass of T:
Automobile a = new Taxi("Ford", "Tempo", …);
• this works because Taxi is a subclass of Automobile
• a taxi is an automobile!

• The name for this feature of Java is polymorphism.


• from the Greek for “many forms”
• the same code can be used with objects of different types!

Polymorphism and Collections of Objects


• Polymorphism is useful when we have a collection of objects
of different but related types.

• Example:
• let's say that a company has a collection of vehicles
of different types
• we can store all of them in an array of type Vehicle:
Vehicle[] fleet = new Vehicle[5];
fleet[0] = new Automobile("Honda", "Civic", …);
fleet[1] = new Motorcycle("Harley", ...);
fleet[2] = new TractorTrailer("Mack", ...);
fleet[3] = new Taxi("Ford", …);
fleet[4] = new Truck("Dodge", …);

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 303


Processing a Collection of Objects
• We can determine the average age of the vehicles in the
company's fleet by doing the following:
int totalAge = 0;
for (int i = 0; i < fleet.length; i++) {
int age = CURRENT_YEAR – fleet[i].getYear();
totalAge += age;
}
double averageAge = (double)totalAge / fleet.length;

• We can invoke getYear() on each object in the array,


regardless of its type.
• their classes are all subclasses of Vehicle
• thus, they must all have a getYear() method

Practice with Polymorphism


Object

Vehicle

Motorcycle Automobile Truck

Limousine Taxi MovingVan TractorTrailer

• Which of these assignments would be allowed?


Vehicle v1 = new Motorcycle(…);
TractorTrailer t1 = new Truck(…);
Truck t2 = new MovingVan(…);
Taxi t3 = new Automobile(…);
Vehicle v2 = new TractorTrailer(…);
MovingVan m1 = new TractorTrailer(…);

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 304


Declared Type vs. Actual Type
• An object's declared type may not match its actual type:
• declared type: type specified when declaring a variable
• actual type: type specified when creating an object

• Recall this client code:


Vehicle[] fleet = new Vehicle[5];
fleet[0] = new Automobile("Honda", "Civic", 2005);
fleet[1] = new Motorcycle("Harley", …);
fleet[2] = new TractorTrailer("Mack", …);

• Here are the types:


object declared type actual type
fleet[0] Vehicle Automobile
fleet[1] Vehicle Motorcycle
fleet[2] Vehicle TractorTrailer

Determining if a Method Call is Valid


• The compiler uses the declared type of an object
to determine if a method call is valid.
• starts at the declared type, and goes up
the inheritance hierarchy as needed Vehicle
looking for a version of the method
• if it can't find a version, the method call Truck
will not compile

• Example: the following would not work: TractorTrailer


Vehicle[] fleet = new Vehicle[5];
...
fleet[2] = new TractorTrailer("Mack",…);
...
System.out.println(fleet[2].getNumAxles());
• the declared type of fleet[2] is Vehicle
• there's no getNumAxles() method defined in
or inherited by Vehicle

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 305


Determining if a Method Call is Valid (cont.)
• In such cases, we can use casting to create a reference with
the necessary declared type:
Vehicle[] fleet = new Vehicle[5];
...
fleet[2] = new TractorTrailer("Mack", …);
...
TractorTrailer t = (TractorTrailer)fleet[2];

• The following will work:


System.out.println(t.getNumAxles());
• the declared type of t is TractorTrailer
• there is a getNumAxles() method defined in
TractorTrailer, so the compiler is happy

Determining Which Method to Execute


• Truck also has a getNumAxles method, so this would be
another way to handle the previous problem:
Vehicle[] fleet = new Vehicle[5];
...
fleet[2] = new TractorTrailer("Mack", …);
...
Truck t2 = (Truck)fleet[2];
System.out.println(t2.getNumAxles());

• The object represented by t2 has:


• a declared type of ______________
• an actual type of _______________

• Both Truck and TractorTrailer have a getNumAxles.


Which version will be executed?

• More generally, how does the interpreter decide which version


of a method should be used?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 306


Dynamic Binding
• At runtime, the Java interpreter selects the version of a method
that is appropriate to the actual type of the object.
• starts at the actual type, and goes up the inheritance
hierarchy as needed until it finds a version of the method
• known as dynamic binding

• Given the code from the previous slide


Vehicle[] fleet = new Vehicle[5]
...
fleet[2] = new TractorTrailer("Mack", …);
...
Truck t2 = (Truck)fleet[2];
System.out.println(t2.getNumAxles());
the TractorTrailer version of getNumAxles would be run
• TractorTrailer is the actual type of t2, and that class has
its own version of getNumAxles

Dynamic Binding (cont.)


• Another example:
public static void printFleet(Vehicle[] fleet) {
for (int i = 0; i < fleet.length; i++) {
System.out.println(fleet[i]);
}
}

• the toString() method is implicitly invoked on each


element of the array when we go to print it.
• the appropriate version is selected by dynamic binding
• note: the selection must happen at runtime, because
the actual types of the objects may not be known when
the code is compiled

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 307


Dynamic Binding (cont.)
• Recall our initialization of the array:
Vehicle[] fleet = new Vehicle[5];
fleet[0] = new Automobile("Honda", "Civic", …);
fleet[1] = new Motorcycle("Harley", …);
fleet[2] = new TractorTrailer("Mack", …);
...

• System.out.println(fleet[0]); will invoke the


Automobile version of the toString() method.
• Motorcycle does not define its own toString() method,
so System.out.println(fleet[1]); will invoke the Vehicle
version of toString(), which is inherited by Motorcycle.
• TractorTrailer does not define its own toString()
but Truck does, so System.out.println(fleet[2]);
will invoke the Truck version of toString(), which is inherited
by TractorTrailer.

Dynamic Binding (cont.)


• Dynamic binding also applies to method calls on the
called object that occur within other methods.

• Example: the Truck class has the following toString method:


public String toString() {
String str = this.getMake() + " " +
this.getModel();
str = str + ", capacity = " + this.capacity;
str = str + ", " + this.getNumAxles() + " axles";
return str;
}
• The TractorTrailer class inherits it and does not override it.

• When toString is called on a TractorTrailer object:


• this Truck version of toString() will run
• the TractorTrailer version of getNumAxles()
will run when the code above is executed

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 308


The Power of Polymorphism
• Recall our printFleet method:
public static void printFleet(Vehicle[] fleet) {
for (int i = 0; i < fleet.length; i++) {
System.out.println(fleet[i]);
}
}
• polymorphism allows this method to use a single println
statement to print the appropriate info. for any kind of vehicle.

• Without polymorphism, we would need a large if-else-if:


if (fleet[i] is an Automobile) {
print the appropriate info for Automobiles
} else if (fleet[i] is a Truck) {
print the appropriate info for Trucks
} else if ...

• Polymorphism allows us to easily write code that works for


more than one type of object.

Polymorphism and the Object Class


• The Object class is a superclass of every other class.

• Thus, we can use an Object variable to store a reference


to any object.
Object o1 = "Hello World";
Object o2 = new Temperature(20, 'C');
Object o3 = new Taxi("Ford", "Tempo", 2000, "T253");

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 309


Summary and Extra Practice
• To determine if a method call is valid:
• start at the declared type
• go up the hierarchy as needed to see if you can find the
specified method in the declared type or a superclass
• if you don't find it, the method call is not valid

• Given the following:


TractorTrailer t1 = new TractorTrailer(…);
Vehicle v = new Truck(…);
MovingVan m = new MovingVan(…); Vehicle
getMake
Truck t2 = new TractorTrailer(…);

• Which of the following are valid? Truck


getNumAxles
v.getNumAxles()
m.getNumAxles()
t1.getMake() MovingVan TractorTrailer
t1.isSleeper() getNumAxles
t2.isSleeper() isSleeper

Summary and Extra Practice (cont.)


• To determine which version of a method will run (dynamic binding):
• start at the actual type
• go up the hierarchy as needed until you find the method
• the first version you encounter is the one that will run

• Given the following:


TractorTrailer t1 = new TractorTrailer(…);
Vehicle v = new Truck(…);
MovingVan m = new MovingVan(…);
Truck t2 = new TractorTrailer(…); Vehicle
getMake

• Which version of the method will run?


m.getNumAxles() Truck
t1.getNumAxles() getNumAxles
t2.getNumAxles()
v.getMake()
MovingVan TractorTrailer
t2.getMake() getNumAxles
isSleeper

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 310


More Practice
public class E extends G {
public void method2() {
System.out.print("E 2 ");
this.method1();
}
public void method3() {
System.out.print("E 3 ");
this.method1();
}
}
public class F extends G {
public void method2() {
System.out.print("F 2 ");
}
}
public class G {
public void method1() {
System.out.print("G 1 ");
}
public void method2() {
System.out.print("G 2 ");
}
}
public class H extends E {
public void method1() {
System.out.print("H 1 ");
}
}

More Practice (cont.)


• Which of these would compile and which would not?
E e1 = new E();
E e2 = new H();
E e3 = new G();
E e4 = new F();
G g1 = new H();
G g2 = new F();
H h1 = new H();

• To answer these questions, draw the inheritance hierarchy:

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 311


Here are the classes again…
public class E extends G {
public void method2() {
System.out.print("E 2 ");
this.method1();
}
public void method3() {
System.out.print("E 3 ");
this.method1();
}
}
public class F extends G {
public void method2() {
System.out.print("F 2 ");
}
}
public class G {
public void method1() {
System.out.print("G 1 ");
}
public void method2() {
System.out.print("G 2 ");
}
}
public class H extends E {
public void method1() {
System.out.print("H 1 ");
}
}

More Practice (cont.)


E e1 = new E();
G g1 = new H();
G g2 = new F();

• Which of the following would compile and which would not?


For the ones that would compile, what is the output?
e1.method1();
e1.method2(); G
method1
e1.method3(); method2

g1.method1();
E F
g1.method2(); method2 method2
method3
g1.method3();
g2.method1(); H
method1
g2.method2();
g2.method3();

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 312


Unit 6, Part 1

Abstract Data Types


and Data Structures

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Congrats on completing the first half!


• In the second half, we will study fundamental data structures.
• ways of imposing order on a collection of information
• sequences: lists, stacks, and queues
• trees
• hash tables
• graphs

• We will also:
• study algorithms related to these data structures
• learn how to compare data structures & algorithms

• Goals:
• learn to think more intelligently about programming problems
• acquire a set of useful tools and techniques

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 313


Sample Problem I: Finding Shortest Paths
• Given a set of routes between pairs of cities, determine the
shortest path from city A to city B.

84 PORTLAND 49

CONCORD 74 PORTSMOUTH
83
63 54
134 44
ALBANY WORCESTER BOSTON
42
49
PROVIDENCE
NEW YORK 185

Sample Problem II: A Data "Dictionary"


• Given a large collection of data, how can we arrange it
so that we can efficiently:
• add a new item
• search for an existing item

• Some data structures provide better performance than others


for this application.

• More generally, we’ll learn how to characterize the efficiency


of different data structures and their associated algorithms.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 314


Example of Comparing Algorithms
• Consider the problem of finding a phone number in a
phonebook.

• Let’s informally compare the time efficiency of two algorithms


for this problem.

Algorithm 1 for Finding a Phone Number


findNumber(person) {
for (p = number of first page; p <= number of the last page; p++) {
if person is found on page p {
return the person's phone number
}
}
return NOT_FOUND
}

• If there were 1,000 pages in the phonebook, how many pages


would this look at in the worst case?
• What if there were 1,000,000 pages?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 315


Algorithm 2 for Finding a Phone Number
findNumber(person) {
min = the number of the first page
max = the number of the last page
while (min <= max) {
mid = (min + max) / 2 // page number of the middle page
if person is found on page mid {
return the person's number
} else if the person’s name comes earlier in the book {
max = mid – 1
} else {
min = mid + 1
}
}
return NOT_FOUND
}
• If there were 1,000 pages in the phonebook, how many pages
would this look at in the worst case?
• What if there were 1,000,000 pages?

Searching a Collection of Data


• The phonebook problem is one example of a common task:
searching for an item in a collection of data.
• another example: searching for a record in a database

• Algorithm 1 is known as sequential search.


• also called linear search

• Algorithm 2 is known as binary search.


• only works if the items in the data collection are sorted

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 316


Abstract Data Types
• An abstract data type (ADT) is a model of a data structure
that specifies:
• the characteristics of the collection of data
• the operations that can be performed on the collection

• It’s abstract because it doesn’t specify how the ADT will be


implemented.
• does not commit to any low-level details

• A given ADT can have multiple implementations.

A Simple ADT: A Bag


• A bag is just a container for a group of data items.
• analogy: a bag of candy

• The positions of the data items don’t matter (unlike a list).


• {3, 2, 10, 6} is equivalent to {2, 3, 6, 10}

• The items do not need to be unique (unlike a set).


• {7, 2, 10, 7, 5} isn’t a set, but it is a bag

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 317


A Simple ADT: A Bag (cont.)
• The operations we want a Bag to support:
• add(item): add item to the Bag
• remove(item): remove one occurrence of item (if any)
from the Bag
• contains(item): check if item is in the Bag
• numItems(): get the number of items in the Bag
• grab(): get an item at random, without removing it
• reflects the fact that the items don’t have a position
(and thus we can’t say "get the 5th item in the Bag")
• toArray(): get an array containing the current contents
of the bag

• We want the bag to be able to store objects of any type.

Specifying an ADT Using an Interface


• In Java, we can use an interface to specify an ADT:
public interface Bag {
boolean add(Object item);
boolean remove(Object item);
boolean contains(Object item);
int numItems();
Object grab();
Object[] toArray();
}

• An interface specifies a set of methods.


• includes only the method headers
• does not typically include the full method definitions

• Like a class, it must go in a file with an appropriate name.


• in this case: Bag.java

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 318


Implementing an ADT Using a Class
• To implement an ADT, we define a class:
public class ArrayBag implements Bag {

public boolean add(Object item) {

}

• When a class header includes an implements clause,


the class must define all of the methods in the interface.
• if the class doesn't define them, it won't compile

All Interface Methods Are Public


• Methods specified in an interface must be public,
so we don't use the keyword public in the definition:
public interface Bag {
boolean add(Object item);
boolean remove(Object item);
boolean contains(Object item);
int numItems();
Object grab();
Object[] toArray();
}

• However, when we actually implement the methods


in a class, we do need to use public:
public class ArrayBag implements Bag {

public boolean add(Object item) {

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 319


One Possible Bag Implementation
• One way to store the items in the bag is to use an array:
public class ArrayBag {

private ______________[] items;

...
}

• What type should the array be?

• This allows us to store any type of object in the items array,


thanks to the power of polymorphism:
ArrayBag bag = new ArrayBag();
bag.add("hello");
bag.add(new Rectangle(20, 30));

• How could we keep track of how many items are in a bag?

Another Example of Polymorphism


• An interface name can be used as the type of a variable:
Bag b;

• Variables with an interface type can refer to objects


of any class that implements the interface:
Bag b = new ArrayBag();

• Using the interface as the type allows us to write code


that works with any implementation of an ADT:
public void processBag(Bag b) {
for (int i = 0; i < b.numItems(); i++) {

}

• the param can be an instance of any Bag implementation


• we must use method calls to access the object's internals,
because the fields are not part of the interface

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 320


Memory Management: Looking Under the Hood
• To understand how data structures are implemented,
you need to understand how memory is managed.

• There are three main types of memory allocation in Java.

• They correspond to three different regions of memory.

Memory Management, Type I: Static Storage


• Static storage is used for class variables, which are declared
outside any method using the keyword static:
public class MyMethods {
public static int numCompares;
public static final double PI = 3.14159;

• There is only one copy of each class variable.


• shared by all objects of the class
• Java's version of a global variable

• The Java runtime allocates memory for class variables


when the class is first encountered.
• this memory stays fixed for the duration of the program

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 321


Memory Management, Type II: Stack Storage
• Method parameters and local variables are stored in a region
of memory known as the stack.

• For each method call, a new stack frame is added to the top
of the stack.
public class Foo {
public static int x(int i) {
int j = i - 2;
j 6
if (i >= 6) { i 8 x(8)
return i;
} return addr
return x(i + j); j 3
}
public static void i 5 x(5)
main(String[] args) {
return addr
System.out.println(x(5));
} args
}

• When a method completes, its stack frame is removed.

Memory Management, Type III: Heap Storage


• Objects are stored in a memory region known as the heap.

• Memory on the heap is allocated using the new operator:


int[] values = new int[3];
ArrayBag b = new ArrayBag();

• new returns the memory address of the start of the object


on the heap.
• a reference!
• An object stays on the heap until there are no remaining
references to it.

• Unused objects are automatically reclaimed by a process


known as garbage collection.
• makes their memory available for other objects

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 322


Two Constructors for the ArrayBag Class
public class ArrayBag implements Bag {
private Object[] items;
private int numItems;
public static final int DEFAULT_MAX_SIZE = 50;

public ArrayBag() {
this.items = new Object[DEFAULT_MAX_SIZE];
this.numItems = 0;
}
public ArrayBag(int maxSize) {
...
}

• As we've seen before, we can have multiple constructors.


• the parameters must differ in some way

• The first one is useful for small bags.


• creates an array with room for 50 items.

• The second one allows the client to specify the max # of items.

Two Constructors for the ArrayBag Class


public class ArrayBag implements Bag {
private Object[] items;
private int numItems;
public static final int DEFAULT_MAX_SIZE = 50;

public ArrayBag() {
this.items = new Object[DEFAULT_MAX_SIZE];
this.numItems = 0;
}
public ArrayBag(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException(
"maxSize must be > 0");
}
this.items = new Object[maxSize];
this.numItems = 0;
}
...
}

• If the user inputs an invalid maxSize, we throw an exception.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 323


Example: Creating Two ArrayBag Objects
// client
public static void main(String[] args) {
ArrayBag b1 = new ArrayBag(2);
ArrayBag b2 = new ArrayBag(4);
… // constructor
} public ArrayBag(int maxSize) {
... // error-checking
this.items = new Object[maxSize];
this.numItems = 0;
stack heap }

maxSize 2
b2
b1
args
items null null

numItems 0

Example: Creating Two ArrayBag Objects


public static void main(String[] args) {
// client
ArrayBag
public static voidb1 = new ArrayBag(2);
main(String[] args) {
ArrayBag
ArrayBag b2 =ArrayBag(2);
b1 = new new ArrayBag(4);

ArrayBag b2 = new ArrayBag(4);
}…
}

• After the objects have been created, here’s what we have:


stack heap

items null null null null


numItems 0
b2
b1
args
items null null

numItems 0

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 324


Adding Items
• We fill the array from left to right. Here's an empty bag:

items null null null null


numItems 0

• After adding the first item:

items null null null


numItems 1

"hello, world"

• After adding the second item:

items null null


numItems 2

"hello, world" "howdy"

Adding Items (cont.)


• After adding the third item:
items null
numItems 3

"hello, world" "howdy" "bye"

• After adding the fourth item:


items
numItems 4

"hello, world" "howdy" "bye" "see ya!"

• At this point, the ArrayBag is full!


• it's non-trivial to "grow" an array, so we don't!
• additional items cannot be added until one is removed

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 325


A Method for Adding an Item to a Bag
public class ArrayBag implements Bag {
private Object[] items; • takes an object of any type!
private int numItems; • returns a boolean to
... indicate if the operation
public boolean add(Object item) { succeeded
if (item == null) {
throw new IllegalArgumentException("no nulls");
} else if (this.numItems == this.items.length) {
return false; // no more room!
} else {
this.items[this.numItems] = item;
this.numItems++;
return true; // success!
}
}
...
}
items
numItems 4

"hello, world" "howdy" "bye" "see ya!"

A Method for Adding an Item to a Bag


public class ArrayBag implements Bag {
private Object[] items; • takes an object of any type!
private int numItems; • returns a boolean to
... indicate if the operation
public boolean add(Object item) { succeeded
if (item == null) {
throw new IllegalArgumentException("no nulls");
} else if (this.numItems == this.items.length) {
return false; // no more room!
} else {
this.items[this.numItems] = item;
this.numItems++;
return true; // success!
}
}
...
}

• Initially, this.numItems is 0, so the first item goes in position 0.


• We increase this.numItems because we now have 1 more item.
• and so the next item added will go in the correct position!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 326


Example: Adding an Item
public static void main(String[] args) {
String message = "hello, world";
ArrayBag b = new ArrayBag(4);
b.add(message);
… public boolean add(Object item) {
} …
else {
this.items[this.numItems] = item;
this.numItems++;
return true;
} …
stack heap
items null null null null
numItems 0

b "hello, world"
message
args …

Example: Adding an Item (cont.)


public
public static
static void void main(String[]
main(String[] args) {args) {
ArrayBag
String messageb1
= = new ArrayBag(2);
"hello, world";
ArrayBag b = new
ArrayBag ArrayBag(4);
b2 = new ArrayBag(4);
b.add(message);

… public boolean add(Object item) {
} } …
else {
this.items[this.numItems] = item;
this.numItems++;
return true;
} …
stack heap
items null null null null
this
item numItems 0

b "hello, world"
message
args …

• add's stack frame includes:


• item, which stores…
• this, which stores…

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 327


Example: Adding an Item (cont.)
public
public staticstatic
void void main(String[]
main(String[] args) {args) {
ArrayBag
String messageb1
= = new ArrayBag(2);
"hello, world";
ArrayBag b = new
ArrayBag ArrayBag(4);
b2 = new ArrayBag(4);
b.add(message);

… public boolean add(Object item) {
} } …
else {
this.items[this.numItems] = item;
this.numItems++;
return true;
} …
stack heap
items null null null
this
item numItems 1

b "hello, world"
message
args …

• The method modifies the items array and numItems.


• note that the array holds a copy of the reference to the item,
not a copy of the item itself.

Example: Adding an Item (cont.)


public
public staticstatic
void void main(String[]
main(String[] args) {args) {
ArrayBag
String messageb1
= = new ArrayBag(2);
"hello, world";
ArrayBag b = new
ArrayBag ArrayBag(4);
b2 = new ArrayBag(4);
b.add(message);

… public boolean add(Object item) {
} } …
else {
this.items[this.numItems] = item;
this.numItems++;
return true;
} …
stack heap
items null null null
numItems 1

b "hello, world"
message
args …

• After the method call returns, add's stack frame is removed


from the stack.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 328


Extra Practice: Determining if a Bag Contains an Item

items null null null null …


numItems 3

"hello, world" "oh my!" "what's in the bag?"

• Let’s write the ArrayBag contains() method together.


• should return true if an object equal to item is found,
and false otherwise.

_________________ contains(_____________ item) {

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 329


Would this work instead?

items null null null null …


numItems 3

"hello, world" "oh my!" "what's in the bag?"

• Let’s write the ArrayBag contains() method together.


• should return true if an object equal to item is found,
and false otherwise.

public boolean contains(Object item) {


for (int i = 0; i < this.items.length; i++) {
if (this.items[i].equals(item)) { // not ==
return true;
}
}
return false;
}

Another Incorrect contains() Method


public boolean contains(Object item) {
for (int i = 0; i < this.numItems; i++) {
if (this.items[i].equals(item)) {
return true;
} else {
return false;
}
}
return false;
}

• What's the problem with this?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 330


A Method That Takes a Bag as a Parameter
public boolean containsAll(Bag otherBag) {
if (otherBag == null || otherBag.numItems() == 0) {
return false;
}
Object[] otherItems = otherBag.toArray();
for (int i = 0; i < otherItems.length; i++) {
if (! this.contains(otherItems[i])) {
return false;
}
}
return true;
}

• We use Bag instead of ArrayBag as the type of the parameter.


• allows this method to be part of the Bag interface
• allows us to pass in any object that implements Bag

• We must use methods in the interface to manipulate otherBag.


• we can't use the fields, because they're not in the interface

A Type Mismatch
• Here are the headers of two ArrayBag methods:
public boolean add(Object item)
public Object grab()

• Polymorphism allows us to pass String objects into add():


ArrayBag stringBag = new ArrayBag();
stringBag.add("hello");
stringBag.add("world");

• However, this will not work:


String str = stringBag.grab(); // compiler error
• the return type of grab() is Object
• Object isn’t a subclass of String, so polymorphism doesn't help!

• Instead, we need to use a type cast:


String str = (String)stringBag.grab();
• this cast doesn't actually change the value being assigned
• it just reassures the compiler that the assignment is okay

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 331


Unit 6, Part 2

Recursion Revisited;
Recursive Backtracking

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Review: Recursive Problem-Solving


• When we use recursion, we reduce a problem to
a simpler problem of the same kind.

• We keep doing this until we reach a problem that is


simple enough to be solved directly.

• This simplest problem is known as the base case.


public static void printSeries(int n1, int n2) {
if (n1 == n2) { // base case
System.out.println(n2);
} else {
System.out.print(n1 + ", ");
printSeries(n1 + 1, n2);
}
}

• The base case stops the recursion, because it doesn't


make another call to the method.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 332


Review: Recursive Problem-Solving (cont.)
• If the base case hasn't been reached, we execute the
recursive case.
public static void printSeries(int n1, int n2) {
if (n1 == n2) { // base case
System.out.println(n2);
} else { // recursive case
System.out.print(n1 + ", ");
printSeries(n1 + 1, n2);
}
}

• The recursive case:


• reduces the overall problem to one or more simpler problems
of the same kind
• makes recursive calls to solve the simpler problems

Raising a Number to a Power


• We want to write a recursive method to compute
xn = x*x*x*…*x
n of them

where x and n are both integers and n >= 0.

• Examples:
• 210 = 2*2*2*2*2*2*2*2*2*2 = 1024
• 105 = 10*10*10*10*10 = 100000

• Computing a power recursively: 210 = 2*29


= 2*(2 * 28)
= …

• Recursive definition: xn = x * xn-1 when n > 0


x0 = 1

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 333


Power Method: First Try
public static int power1(int x, int n) {
if (n < 0) {
throw new IllegalArgumentException();
} else if (n == 0) {
return 1;
} else {
int pow_rest = power1(x, n-1);
return x * pow_rest;
}
}

Example: power1(5,3)
x5 n0
return 1
x5 n1 x5 n1 x5 n1
return 5*1
x5 n2 x5 n2 x5 n2 x5 n2 x5 n2
return 5*5
x5 n3 x5 n3 x5 n3 x5 n3 x5 n3 x5 n3 x5 n3
return 5*25
time

Power Method: Second Try


• There’s a better way to break these problems into subproblems.
For example: 210 = (2*2*2*2*2)*(2*2*2*2*2)
= (25) * (25) = (25)2
• A more efficient recursive definition of xn (when n > 0):
xn = (xn/2)2 when n is even
xn = x * (xn/2)2 when n is odd (using integer division for n/2)

public static int power2(int x, int n) {


// code to handle n < 0 goes here...

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 334


Analyzing power2
• How many method calls would it take to compute 21000 ?
power2(2, 1000)
power2(2, 500)
power2(2, 250)
power2(2, 125)
power2(2, 62)
power2(2, 31)
power2(2, 15)
power2(2, 7)
power2(2, 3)
power2(2, 1)
power2(2, 0)

• Much more efficient than


power1() for large n.
• It can be shown that
it takes approx. log2n
method calls.

An Inefficient Version of power2


• What's wrong with the following version of power2()?
public static int power2(int x, int n) {
// code to handle n < 0 goes here...
if (n == 0) {
return 1;
} else {
// int pow_rest = power2(x, n/2);
if (n % 2 == 0) {
return power2(x, n/2) * power2(x, n/2);
} else {
return x * power2(x, n/2) * power2(x, n/2);
}
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 335


Review: Processing a String Recursively
• A string is a recursive data structure. It is either:
• empty ("")
• a single character, followed by a string

• Thus, we can easily use recursion to process a string.


• process one or two of the characters ourselves
• make a recursive call to process the rest of the string

• Example: print a string vertically, one character per line:


public static void printVertical(String str) {
if (str == null || str.equals("")) {
return;
}

System.out.println(str.charAt(0)); // first char


printVertical(str.substring(1)); // rest of string
}

Removing Vowels From a String


• removeVowels(s) - removes the vowels from the string s,
returning its "vowel-less" version!
removeVowels("recursive") should return "rcrsv"

removeVowels("vowel") should return "vwl"

• Can we take the usual approach to recursive string processing?


• base case: empty string
• delegate s.substring(1) to the recursive call
• we're responsible for handling s.charAt(0)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 336


Applying the String-Processing Template
public static String removeVowels(String s) {
if (s.equals("")) { // base case

return __________;
} else { // recursive case

String rem_rest = __________________;


// do our one step!
}
}

Consider Concrete Cases


removeVowels("after") # first char is a vowel
• what is its solution?
• what is the next smaller subproblem?
• what is the solution to that subproblem?
• how can we use the solution to the subproblem?
What is our one step?

removeVowels("recurse") # first char is not a vowel


• what is its solution?
• what is the next smaller subproblem?
• what is the solution to that subproblem?
• how can we use the solution to the subproblem?
What is our one step?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 337


removeVowels()
public static String removeVowels(String s) {
if (s.equals("")) { // base case
return "";
} else { // recursive case
String rem_rest = removeVowels(s.substring(1));
if ("aeiou".indexOf(s.charAt(0)) != -1) {

____________________________________
} else {

____________________________________
}
}
}

The n-Queens Problem


• Goal: to place n queens on an n x n chessboard
so that no two queens occupy:
• the same row
• the same column
• the same diagonal.

• Sample solution for n = 8: Q


Q
Q
Q
Q
Q
Q
Q

• This problem can be solved using a technique called


recursive backtracking.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 338


Recursive Strategy for n-Queens
• findSolution(row) – to place a queen in the specified row:
• try one column at a time, looking for a "safe" one
• if we find one: – place the queen there
– make a recursive call to go to the next row
• if we can’t find one: – backtrack by returning from the call
– try to find another safe column
in the previous row
• Example: Q
• row 0:

col 0: safe

• row 1: Q Q Q
Q Q Q

col 0: same col col 1: same diag col 2: safe

4-Queens Example (cont.)


• row 2: Q Q Q Q
Q Q Q Q
Q Q Q Q

col 0: same col col 1: same diag col 2: same col/diag col 3: same diag

• We’ve run out of columns in row 2!


• Backtrack to row 1 by returning from the recursive call.
• pick up where we left off
• we had already tried columns 0-2, so now we try column 3:
Q Q
Q

we left off in col 2 try col 3: safe

• Continue the recursion as before.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 339


4-Queens Example (cont.)
• row 2: Q Q
Q Q
Q Q

col 0: same col col 1: safe

• row 3: Q Q Q Q
Q Q Q Q
Q Q Q Q
Q Q Q Q
col 0: same col/diag col 1: same col/diag col 2: same diag col 3: same col/diag

• Backtrack to row 2:
Q Q Q
Q Q Q
Q Q

we left off in col 1 col 2: same diag col 3: same col

• Backtrack to row 1. No columns left, so backtrack to row 0!

4-Queens Example (cont.)


• row 0: Q

• row 1: Q Q Q Q

Q Q Q Q

• row 2: Q
Q
Q

• row 3: Q Q Q
Q Q Q
Q Q Q
Q Q Q

A solution!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 340


A Blueprint Class for an N-Queens Solver
public class NQueens {
private boolean[][] board; // state of the chessboard
// other fields go here...
public NQueens(int n) {
this.board = new boolean[n][n];
// initialize other fields here...
}
...

• Here's what the object false false false false


looks like initially:
false false false false
NQueens object

board false false false false

//other fields
false false false false

A Blueprint Class for an N-Queens Solver


public class NQueens {
private boolean[][] board; // state of the chessboard
// other fields go here...
public NQueens(int n) {
this.board = new boolean[n][n];
// initialize other fields here...
} Q
private void placeQueen(int row, int col) { Q
this.board[row][col] = true; Q
// modify other fields here...
}

• Here's what it looks like true false false false


after placing some queens:
false false false true
NQueens object

board false true false false

//other fields
false false false false

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 341


A Blueprint Class for an N-Queens Solver
public class NQueens {
private boolean[][] board; // state of the chessboard
// other fields go here...
public NQueens(int n) {
this.board = new boolean[n][n];
// initialize other fields here...
}
private void placeQueen(int row, int col) {
this.board[row][col] = true; private helper methods
// modify other fields here... that will only be called
} by code within the class.
private void removeQueen(int row, int col){ Making them private
this.board[row][col] = false; means we don't need
// modify other fields here... to do error-checking!
}
private boolean isSafe(int row, int col) {
// returns true if [row][col] is "safe", else false
}
private boolean findSolution(int row) {
// see next slide!
...

Recursive-Backtracking Method
private boolean findSolution(int row) {
if (row == this.board.length) {
this.displayBoard();
return true;
}
for (int col = 0; col < this.board.length; col++) {
if (this.isSafe(row, col)) {
this.placeQueen(row, col);
if (this.findSolution(row + 1)) {
return true;
}
this.removeQueen(row, col);
}
}
return false;
}

• takes the index of a row (initially 0)


• uses a loop to consider all possible columns in that row
• makes a recursive call to move onto the next row
• returns true if a solution has been found; false otherwise

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 342


Q
Tracing findSolution() Q

private boolean findSolution(int row) { Q


if (row == this.board.length) {
// code to process a solution goes here...
}
for (int col = 0; col < this.board.length; col++) {
if (this.isSafe(row, col)) { Note: row++
this.placeQueen(row, col); will not work
if (this.findSolution(row + 1)) { here!
return true;
}
this.removeQueen(row, col);
} We can pick up backtrack!
} where we left off, row: 3
return false; col:0,1,2,3,4
} backtrack! because row and
col are stored in return false
row: 2
col:0,1,2,3,4 the stack frame! row: 2 row: 2
return false col: 0,1 col: 0,1
row: 1 row: 1 row: 1 row: 1 row: 1 row: 1

col: 0,1,2 col: 0,1,2 col: 0,1,2 col: 0,1,2,3 col: 0,1,2,3 col: 0,1,2,3
row: 0 row: 0 row: 0 row: 0 row: 0 row: 0 row: 0
col: 0 col: 0 col: 0 col: 0 col: 0 col: 0 col: 0
time

Q
Once we place a queen in the last row… Q

private boolean findSolution(int row) { Q


if (row == this.board.length) { Q
this.displayBoard();
return true;
}
for (int col = 0; col < this.board.length; col++) {
if (this.isSafe(row, col)) {
this.placeQueen(row, col);
if (this.findSolution(row + 1)) {
return true;
}
this.removeQueen(row, col);
}
} row: 3
return false; col:0,1,2
}
row: 2
col: 0
… row: 1
col: 0,1,2,3
row: 0
col: 1
time

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 343


Q
…we make one more recursive call… Q

private boolean findSolution(int row) { Q


if (row == this.board.length) { Q
this.displayBoard();
return true;
}
for (int col = 0; col < this.board.length; col++) {
if (this.isSafe(row, col)) {
this.placeQueen(row, col);
if (this.findSolution(row + 1)) {
return true;
}
row: 4 col);
this.removeQueen(row,
}
} row: 3 row: 3
return false; col:0,1,2 col:0,1,2
}
row: 2 row: 2
col: 0 col: 0
… row: 1 row: 1
col: 0,1,2,3 col: 0,1,2,3
row: 0 row: 0
col: 1 col: 1
time

Q
…and hit the base case! Q

private boolean findSolution(int row) { Q


if (row == this.board.length) { Q
this.displayBoard();
return true;
}
for (int col = 0; col < this.board.length; col++) {
if (this.isSafe(row, col)) {
this.placeQueen(row, col);
if (this.findSolution(row + 1)) {
return true;
}
row: 4 col);
this.removeQueen(row,
} return true
} row: 3 row: 3
return false; col:0,1,2 col:0,1,2
}
row: 2 row: 2
col: 0 col: 0
… row: 1 row: 1
col: 0,1,2,3 col: 0,1,2,3
row: 0 row: 0
col: 1 col: 1
time

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 344


Q
true is sent back… Q

private boolean findSolution(int row) { Q


if (row == this.board.length) { Q
this.displayBoard();
return true;
}
for (int col = 0; col < this.board.length; col++) {
if (this.isSafe(row, col)) {
this.placeQueen(row, col);
if (this.findSolution(row + 1)) { // if (true)
return true;
}
row: 4 col);
this.removeQueen(row,
} return true
} row: 3 row: 3 row: 3
return false; col:0,1,2 col:0,1,2 col:0,1,2
}
row: 2 row: 2 row: 2
col: 0 col: 0 col: 0
… row: 1 row: 1 row: 1
col: 0,1,2,3 col: 0,1,2,3 col: 0,1,2,3
row: 0 row: 0 row: 0
col: 1 col: 1 col: 1
time

Q
...and all the earlier calls also return true! Q

private boolean findSolution(int row) { Q


if (row == this.board.length) { Q
this.displayBoard();
return true;
}
for (int col = 0; col < this.board.length; col++) {
if (this.isSafe(row, col)) {
this.placeQueen(row, col);
if (this.findSolution(row + 1)) { // if (true)
return true;
}
row: 4 col);
this.removeQueen(row,
} return true
row: 3
} row: 3 row: 3 col:0,1,2
return false; col:0,1,2 col:0,1,2 return true
} row: 2
row: 2 row: 2 row: 2 col: 0
col: 0 col: 0 col: 0 return true
… row: 1 row: 1 row: 1 row: 1

col: 0,1,2,3 col: 0,1,2,3 col: 0,1,2,3 col: 0,1,2,3
row: 0 row: 0 row: 0 row: 0
col: 1 col: 1 col: 1 col: 1
time

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 345


Using a "Wrapper" Method
• The key recursive method is private:
private boolean findSolution(int row) {
...
}

• We use a separate, public "wrapper" method


to start the recursion:
public boolean findSolution() {
return this.findSolution(0);
}

• an example of overloading – two methods with


the same name, but different parameters
• this method takes no parameters
• it makes the initial call to the recursive method
and returns whatever that call returns
• it allows us to ensure that the correct initial value
is passed into the recursive method

Recursive Backtracking in General


• Useful for constraint satisfaction problems
• involve assigning values to variables according to
a set of constraints
• n-Queens: variables = Queen’s position in each row
constraints = no two queens in same row/col/diag
• many others: factory scheduling, room scheduling, etc.

• Backtracking greatly reduces the number of possible solutions


that we consider.
• ex: Q • there are 16 possible solutions that
Q
begin with queens in these two positions
• backtracking doesn't consider any of them!

• Recursion makes it easy to handle an arbitrary problem size.


• stores the state of each variable in a separate stack frame

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 346


Template for Recursive Backtracking
// n is the number of the variable that the current
// call of the method is responsible for
boolean findSolution(int n, possibly other params) {
if (found a solution) {
this.displaySolution();
return true;
}
// loop over possible values for the nth variable
for (val = first to last) { Note: n++
if (this.isValid(val, n)) { will not work
this.applyValue(val, n); here!
if (this.findSolution(n + 1, other params)) {
return true;
}
this.removeValue(val, n);
}
}
return false; // backtrack!
}

Template for Finding Multiple Solutions


(up to some target number of solutions)
boolean findSolutions(int n, possibly other params) {
if (found a solution) {
this.displaySolution();
this.solutionsFound++;
return (this.solutionsFound >= this.target);
}
// loop over possible values for the nth variable
for (val = first to last) {
if (isValid(val, n)) {
this.applyValue(val, n);
if (this.findSolutions(n + 1, other params)) {
return true;
}
this.removeValue(val, n);
}
}
return false;
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 347


Data Structures for n-Queens
• Three key operations:
• isSafe(row, col): check to see if a position is safe
• placeQueen(row, col)
• removeQueen(row, col)

• In theory, our 2-D array of booleans would be sufficient:


public class NQueens {
private boolean[][] board;

• It's easy to place or remove a queen:


private void placeQueen(int row, int col) {
this.board[row][col] = true;
}
private void removeQueen(int row, int col) {
this.board[row][col] = false;
}

• Problem: isSafe() takes a lot of steps. What matters more?

Additional Data Structures for n-Queens


• To facilitate isSafe(), add three arrays of booleans:
private boolean[] colEmpty;
private boolean[] upDiagEmpty;
private boolean[] downDiagEmpty;

• An entry in one of these arrays is:


– true if there are no queens in the column or diagonal
– false otherwise
• Numbering diagonals to get the indices into the arrays:
upDiag = row + col downDiag =
(boardSize – 1) + row – col
0 1 2 3 0 1 2 3

0 0 1 2 3 0 3 2 1 0
1 1 2 3 4 1 4 3 2 1
2 2 3 4 5 2 5 4 3 2
3 3 4 5 6 3 6 5 4 3

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 348


Using the Additional Arrays
• Placing and removing a queen now involve updating four
arrays instead of just one. For example:
private void placeQueen(int row, int col) {
this.board[row][col] = true;
this.colEmpty[col] = false;
this.upDiagEmpty[row + col] = false;
this.downDiagEmpty[
(this.board.length - 1) + row - col] = false;
}

• However, checking if a square is safe is now more efficient:


private boolean isSafe(int row, int col) {
return (this.colEmpty[col]
&& this.upDiagEmpty[row + col]
&& this.downDiagEmpty[
(this.board.length - 1) + row - col]);
}

Recursive Backtracking II: Map Coloring


• We want to color a map using only four colors.

• Bordering states or countries cannot have the same color.


• example:

not allowed!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 349


Applying the Template to Map Coloring
boolean findSolution(n, perhaps other params) {
if (found a solution) {
this.displaySolution();
return true;
}
for (val = first to last) {
if (this.isValid(val, n)) {
this.applyValue(val, n);
if (this.findSolution(n + 1, other params)) {
return true;
}
this.removeValue(val, n);
}
} template element meaning in map coloring
return false;
} n
found a solution
val
isValid(val, n)
applyValue(val, n)
removeValue(val, n)

Map Coloring Example


consider the states in alphabetical order. colors = { red, yellow, green, blue }.

We color Colorado through No color works for Wyoming,


Utah without a problem. so we backtrack…
Colorado:
Idaho:
Kansas:
Montana:
Nebraska:
North Dakota:
South Dakota:
Utah:

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 350


Map Coloring Example (cont.)

Now we can complete


the coloring:

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 351


Unit 7, Part 1

A First Look at
Sorting and Algorithm Analysis

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Sorting an Array of Integers


0 1 2 n-2 n-1
arr 15 7 36 … 40 12

• Ground rules:
• sort the values in increasing order
• sort “in place,” using only a small amount of additional storage

• Terminology:
• position: one of the memory locations in the array
• element: one of the data items stored in the array
• element i: the element at position i

• Goal: minimize the number of comparisons C and the number


of moves M needed to sort the array.
• move = copying an element from one position to another
example: arr[3] = arr[5];

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 352


Defining a Class for our Sort Methods
public class Sort {
public static void bubbleSort(int[] arr) {
...
}
public static void insertionSort(int[] arr) {
...
}
...
}

• Our Sort class is simply a collection of methods like Java’s


built-in Math class.

• Because we never create Sort objects, all of the methods in


the class must be static.
• outside the class, we invoke them using the class name:
e.g., Sort.bubbleSort(arr)

Defining a Swap Method


• It would be helpful to have a method that swaps two elements
of the array.

• Why won’t the following work?


public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 353


An Incorrect Swap Method
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}

• Trace through the following lines to see the problem:


int[] arr = {15, 7, …};
swap(arr[0], arr[1]);

stack heap

arr
15 7 ...

A Correct Swap Method


• This method works:
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}

• Trace through the following with a memory diagram to convince


yourself that it works:
int[] arr = {15, 7, …};
swap(arr, 0, 1);

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 354


Selection Sort
• Basic idea:
• consider the positions in the array from left to right
• for each position, find the element that belongs there and put it
in place by swapping it with the element that’s currently there
• Example:
0
0 1 2 3 4
15 6 2 12 4

0 1
1 2 3 4
2 6 15 12 44

0 1 2
2 3 4 0 1 2 3
3 4
2 4 15 12 66 2 4 6 12 15

Why don’t we need to consider position 4?

Selecting an Element
• When we consider position i, the elements in positions
0 through i – 1 are already in their final positions.
0 1 2 3 4 5 6
example for i = 3: 2 4 7 21 25 10 17

• To select an element for position i:


• consider elements i, i+1,i+2,…,arr.length – 1, and
keep track of indexMin, the index of the smallest element
seen thus far
0 1 2 3 4 5 6
indexMin: 3, 5 2 4 7 21 25 10 17
• when we finish this pass, indexMin is the index of the
element that belongs in position i.
• swap arr[i] and arr[indexMin]:
0 1 2 3 4 5 6
2 4 7 21
10 25 10
21 17

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 355


Implementation of Selection Sort
• Use a helper method to find the index of the smallest element:
private static int indexSmallest(int[] arr, int start) {
int indexMin = start;
for (int i = start + 1; i < arr.length; i++) {
if (arr[i] < arr[indexMin]) {
indexMin = i;
}
}
return indexMin;
}

• The actual sort method is very simple:


public static void selectionSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int j = indexSmallest(arr, i);
swap(arr, i, j);
}
}

Time Analysis
• Some algorithms are much more efficient than others.

• The time efficiency or time complexity of an algorithm is some


measure of the number of operations that it performs.
• for sorting, we’ll focus on comparisons and moves

• We want to characterize how the number of operations


depends on the size, n, of the input to the algorithm.
• for sorting, n is the length of the array
• how does the number of operations grow as n grows?

• We'll express the number of operations as functions of n


• C(n) = number of comparisons for an array of length n
• M(n) = number of moves for an array of length n

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 356


Counting Comparisons by Selection Sort
private static int indexSmallest(int[] arr, int start){
int indexMin = start;
for (int i = start + 1; i < arr.length; i++) {
if (arr[i] < arr[indexMin]) {
indexMin = i;
}
}
return indexMin;
}
public static void selectionSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int j = indexSmallest(arr, i);
swap(arr, i, j);
}
}

• To sort n elements, selection sort performs n - 1 passes:


on 1st pass, it performs ____ comparisons to find indexSmallest
on 2nd pass, it performs ____ comparisons

on the (n-1)st pass, it performs 1 comparison
• Adding them up: C(n) = 1 + 2 + … + (n - 2) + (n - 1)

Counting Comparisons by Selection Sort (cont.)


• The resulting formula for C(n) is the sum of an arithmetic
sequence:
n - 1
C(n) = 1 + 2 + … + (n - 2) + (n - 1) = i
i 1

• Formula for the sum of this type of arithmetic sequence:


m
m(m  1)
i  2
i 1

• Thus, we can simplify our expression for C(n) as follows:


n - 1
C(n) = i
i 1

(n - 1)((n - 1)  1)
=
2
(n - 1)n 2
= C(n) = n 2 - n 2
2

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 357


Focusing on the Largest Term
• When n is large, mathematical expressions of n are dominated
by their “largest” term — i.e., the term that grows fastest as a
function of n.
• example: n n2/2 n/2 n2/2 – n/2
10 50 5 45
100 5000 50 4950
10000 50,000,000 5000 49,995,000

• In characterizing the time complexity of an algorithm,


we’ll focus on the largest term in its operation-count expression.
• for selection sort, C(n) = n2/2 - n/2  n2/2

• In addition, we’ll typically ignore the coefficient of the largest term


(e.g., n2/2  n2).

Big-O Notation
• We specify the largest term using big-O notation.
• e.g., we say that C(n) = n2/2 – n/2 is O(n2)

• Common classes of algorithms:


name example expressions big-O notation
constant time 1, 7, 10 O(1)
logarithmic time 3log10n, log2n + 5 O(log n)
linear time 5n, 10n – 2log2n O(n)
slower

nlogn time 4nlog2n, nlog2n + n O(nlog n)


quadratic time 2n2 + 3n, n2 – 1 O(n2)
exponential time 2n, 5en + 2n2 O(cn)

• For large inputs, efficiency matters more than CPU speed.


• e.g., an O(log n) algorithm on a slow machine will
outperform an O(n) algorithm on a fast machine

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 358


Ordering of Functions
• We can see below that: n2 grows faster than nlog2n
nlog2n grows faster than n
n grows faster than log2n
160

140

120

100 n^2
n log n
80
n
60 log n

40

20

0
0 1 2 3 4 5 6 7 8 9 10 11 12
n

Ordering of Functions (cont.)


• Zooming in, we see that: n2 >= n for all n >= 1
nlog2n >= n for all n >= 2
n > log2n for all n >= 1
10
9
8
7
6 n^2
n log n
5
n
4 log n
3
2
1
0
0 1 2 3 4 5 6

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 359


Big-O Time Analysis of Selection Sort
• Comparisons: we showed that C(n) = n2/2 – n/2
• selection sort performs O(n2) comparisons

• Moves: after each of the n-1 passes, the algorithm does one swap.
• n-1 swaps, 3 moves per swap
• M(n) = 3(n-1) = 3n-3
• selection sort performs O(n) moves.

• Running time (i.e., total operations): ?

Mathematical Definition of Big-O Notation


• f(n) = O(g(n)) if there exist positive constants c and n0
such that f(n) <= c g(n) for all n >= n0

• Example: f(n) = n2/2 – n/2 is O(n2), because


n2/2 – n/2 <= n2 for all n >= 0.
c=1 n0 = 0

g(n) = n2

f(n) = n2/2 – n/2

n
• Big-O notation specifies an upper bound on a function f(n)
as n grows large.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 360


Big-O Notation and Tight Bounds
• Strictly speaking, big-O notation provides an upper bound,
not a tight bound (upper and lower).

• Example:
• 3n – 3 is O(n2) because 3n – 3 <= n2 for all n >= 1
• 3n – 3 is also O(2n) because 3n – 3 <= 2n for all n >= 1

• However, it is common to use big-O notation to characterize


a function as closely as possible – as if it specified a tight bound.
• for our example, we would say that 3n – 3 is O(n)
• this is how you should use big-O in this class!

Insertion Sort
• Basic idea:
• going from left to right, “insert” each element into its proper
place with respect to the elements to its left
• “slide over” other elements to make room

• Example:
0 1 2 3 4
15 4 2 12 6

4 15 2 12 6

2 4 15 12 6

2 4 12 15 66

2 4 6 12 15

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 361


Comparing Selection and Insertion Strategies
• In selection sort, we start with the positions in the array and
select the correct elements to fill them.
• In insertion sort, we start with the elements and determine
where to insert them in the array.
• Here’s an example that illustrates the difference:
0 1 2 3 4 5 6
18 12 15 9 25 2 17

• Sorting by selection:
• consider position 0: find the element (2) that belongs there
• consider position 1: find the element (9) that belongs there
• …
• Sorting by insertion:
• consider the 12: determine where to insert it
• consider the 15; determine where to insert it
• …

Inserting an Element
• When we consider element i, elements 0 through i – 1
are already sorted with respect to each other.
0 1 2 3 4
example for i = 3: 6 14 19 9 …

• To insert element i:
• make a copy of element i, storing it in the variable toInsert:
0 1 2 3
toInsert 9 6 14 19 9

• consider elements i-1, i-2, …


• if an element > toInsert, slide it over to the right
• stop at the first element <= toInsert
0 1 2 3
toInsert 9 6 14 14
19 19
9
0 1 2 3
• copy toInsert into the resulting “hole”: 6 9 14 19

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 362


Insertion Sort Example (done together)
description of steps 12 5 2 13 18 4

Implementation of Insertion Sort


public class Sort {
...
public static void insertionSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
if (arr[i] < arr[i-1]) {
int toInsert = arr[i];

int j = i;
do {
arr[j] = arr[j-1];
j = j - 1;
} while (j > 0 && toInsert < arr[j-1]);

arr[j] = toInsert;
}
}
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 363


Time Analysis of Insertion Sort
• The number of operations depends on the contents of the array.
• best case: array is sorted
• each element is only compared to the element to its left
• we never execute the do-while loop!
• C(n) =_______, M(n) = _______, running time = ______
also true if array
• worst case: array is in reverse order is almost sorted
• each element is compared to all of the elements to its left:
arr[1] is compared to 1 element (arr[0])
arr[2] is compared to 2 elements (arr[0] and arr[1])

arr[n-1] is compared to n-1 elements
• C(n) = 1 + 2 + … + (n - 1) = _______
• similarly, M(n) = ______, running time = _______
• average case: elements are randomly arranged
• on average, each element is compared to half
of the elements to its left
• still get C(n) = M(n) = _______, running time = _______

Shell Sort
• Developed by Donald Shell

• Improves on insertion sort


• takes advantage of the fact that it's fast for almost-sorted arrays
• eliminates a key disadvantage: an element may need
to move many times to get to where it belongs.

• Example: if the largest element starts out at the beginning of the


array, it moves one place to the right on every insertion!
0 1 2 3 4 5 … 1000
999 42 56 30 18 23 … 11

• Shell sort uses larger moves that allow elements to quickly get
close to where they belong in the sorted array.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 364


Sorting Subarrays
• Basic idea:
• use insertion sort on subarrays that contain elements
separated by some increment incr
• increments allow the data items to make larger “jumps”
• repeat using a decreasing sequence of increments
• Example for an initial increment of 3:
0 1 2 3 4 5 6 7
36 18 10 27 3 20 9 8

• three subarrays:
1) elements 0, 3, 6 2) elements 1, 4, 7 3) elements 2 and 5

• Sort the subarrays using insertion sort to get the following:


0 1 2 3 4 5 6 7
6
9 23
3 14
10 27 18
8 20 9 18
36 3

• Next, we complete the process using an increment of 1.

Shell Sort: A Single Pass


• We don’t actually consider the subarrays one at a time.
• For each element from position incr to the end of the array,
we insert the element into its proper place with respect to
the elements from its subarray that come before it.
0 1 2 3 4 5 6 7
• The same
example 36 18 10 27 3 20 9 8
(incr = 3):
27 18 10 36 3 20 9 8

27 3 10 36 18 20 9 8

27 3 10 36 18 20 9 8

9 3 10 27 18 20 36 8

9 3 10 27 8 20 36 18

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 365


Inserting an Element in a Subarray
• When we consider element i, the other elements in its subarray
are already sorted with respect to each other.
0 1 2 3 4 5 6 7
example for i = 6:
(incr = 3) 27 3 10 36 18 20 9 8
the other element’s in 9’s subarray (the 27 and 36)
are already sorted with respect to each other
• To insert element i:
• make a copy of element i, storing it in the variable toInsert:
0 1 2 3 4 5 6 7
toInsert 9 27 3 10 36 18 20 9 8

• consider elements i-incr, i-(2*incr), i-(3*incr),…


• if an element > toInsert, slide it right within the subarray
• stop at the first element <= toInsert
0 1 2 3 4 5 6 7
toInsert 9 27 3 10 27
36 18 20 36
9 8
0 1 2 3 4
• copy toInsert into the “hole”: 9 3 10 27 18 …

The Sequence of Increments


• Different sequences of decreasing increments can be used.

• Our version uses values that are one less than a power of two.
• 2k – 1 for some k
• … 63, 31, 15, 7, 3, 1
• can get to the next lower increment using integer division:
incr = incr/2;

• Should avoid numbers that are multiples of each other.


• otherwise, elements that are sorted with respect to each other
in one pass are grouped together again in subsequent passes
• repeat comparisons unnecessarily
• get fewer of the large jumps that speed up later passes
• example of a bad sequence: 64, 32, 16, 8, 4, 2, 1
• what happens if the largest values are all in odd positions?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 366


Implementation of Shell Sort
public static void shellSort(int[] arr) {
int incr = 1;
while (2 * incr <= arr.length) {
incr = 2 * incr;
}
incr = incr - 1;
while (incr >= 1) {
for (int i = incr; i < arr.length; i++) {
if (arr[i] < arr[i-incr]) {
int toInsert = arr[i];

int j = i;
do {
arr[j] = arr[j-incr];
j = j - incr;
} while (j > incr-1 &&
toInsert < arr[j-incr]);

arr[j] = toInsert; (If you replace incr with 1


} in the for-loop, you get the
} code for insertion sort.)
incr = incr/2;
}
}

Time Analysis of Shell Sort


• Difficult to analyze precisely
• typically use experiments to measure its efficiency
• With a bad interval sequence, it’s O(n2) in the worst case.
• With a good interval sequence, it’s better than O(n2).
• at least O(n1.5) in the average and worst case
• some experiments have shown average-case running times
of O(n1.25) or even O(n7/6)
• Significantly better than insertion or selection for large n:
n n2 n1.5 n1.25
10 100 31.6 17.8
100 10,000 1000 316
10,000 100,000,000 1,000,000 100,000
106 1012 109 3.16 x 107

• We’ve wrapped insertion sort in another loop and increased its


efficiency! The key is in the larger jumps that Shell sort allows.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 367


Practicing Time Analysis
• Consider the following static method:
public static int mystery(int n) {
int x = 0;
for (int i = 0; i < n; i++) {
x += i; // statement 1
for (int j = 0; j < i; j++) {
x += j;
}
}
return x;
}

• What is the big-O expression for the number of times that


statement 1 is executed as a function of the input n?

What about now?


• Consider the following static method:
public static int mystery(int n) {
int x = 0;
for (int i = 0; i < 3*n + 4; i++) {
x += i; // statement 1
for (int j = 0; j < i; j++) {
x += j;
}
}
return x;
}

• What is the big-O expression for the number of times that


statement 1 is executed as a function of the input n?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 368


Practicing Time Analysis
• Consider the following static method:
public static int mystery(int n) {
int x = 0;
for (int i = 0; i < n; i++) {
x += i; // statement 1
for (int j = 0; j < i; j++) {
x += j; // statement 2
}
}
return x;
}

• What is the big-O expression for the number of times that


statement 2 is executed as a function of the input n?
value of i number of times statement 2 is executed

Bubble Sort
• Perform a sequence of passes from left to right
• each pass swaps adjacent elements if they are out of order
• larger elements “bubble up” to the end of the array

• At the end of the kth pass:


• the k rightmost elements are in their final positions
• we don’t need to consider them in subsequent passes.

• Example: 0 1 2 3 4
28 24 37 15 5
after the first pass: 24 28 15 5 37
after the second: 24 15 5 28 37
after the third: 15 5 24 28 37
after the fourth: 5 15 24 28 37

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 369


Implementation of Bubble Sort
public class Sort {
...
public static void bubbleSort(int[] arr) {
for (int i = arr.length - 1; i > 0; i--) {
for (int j = 0; j < i; j++) {
if (arr[j] > arr[j+1]) {
swap(arr, j, j+1);
}
}
}
}
}

• Nested loops:
• the inner loop performs a single pass
• the outer loop governs:
• the number of passes (arr.length - 1)
• the ending point of each pass (the current value of i)

Time Analysis of Bubble Sort


• Comparisons (n = length of array):
• they are performed in the inner loop
• how many repetitions does each execution
of the inner loop perform?
value of i number of comparisons
n–1 n–1
n–2 n–2
… … 1+2+…+n–1=
2 2
1 1
public static void bubbleSort(int[] arr) {
for (int i = arr.length - 1; i > 0; i--) {
for (int j = 0; j < i; j++) {
if (arr[j] > arr[j+1]) {
swap(arr, j, j+1);
}
}
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 370


Time Analysis of Bubble Sort
• Comparisons: the kth pass performs n - k comparisons,
n - 1
so we get C(n) = i = n2/2 – n/2 = O(n2)
i 1

• Moves: depends on the contents of the array


• in the worst case:

• M(n) =
• in the best case:

• Running time:
• C(n) is always O(n2), M(n) is never worse than O(n2)
• therefore, the largest term of C(n) + M(n) is O(n2)

• Bubble sort is a quadratic-time or O(n2) algorithm.


• can’t do much worse than bubble!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 371


Unit 7, Part 2

Sorting II:
Divide-and-Conquer Algorithms,
Distributive Sorting

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Quicksort
• Like bubble sort, quicksort uses an approach based on swapping
out-of-order elements, but it’s more efficient.

• A recursive, divide-and-conquer algorithm:


• divide: rearrange the elements so that we end up with
two subarrays that meet the following criterion:
each element in left array <= each element in right array
example:

12 8 14 4 6 13 6 8 4 14 12 13

• conquer: apply quicksort recursively to the subarrays,


stopping when a subarray has a single element
• combine: nothing needs to be done, because of the way
we formed the subarrays

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 372


Partitioning an Array Using a Pivot
• The process that quicksort uses to rearrange the elements
is known as partitioning the array.

• It uses one of the values in the array as a pivot,


rearranging the elements to produce two subarrays:
• left subarray: all values <= pivot equivalent to the criterion
• right subarray: all values >= pivot on the previous page.

7 15 4 9 6 18 9 12
partition using a pivot of 9

7 9 4 6 9 18 15 12
all values <= 9 all values >= 9

• The subarrays will not always have the same length.

• This approach to partitioning is one of several variants.

Possible Pivot Values


• First element or last element
• risky, can lead to terrible worst-case behavior
• especially poor if the array is almost sorted

4 8 14 12 6 18 4 8 14 12 6 18
pivot = 18

• Middle element (what we will use)

• Randomly chosen element

• Median of three elements


• left, center, and right elements
• three randomly selected elements
• taking the median of three decreases the probability of
getting a poor pivot

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 373


Partitioning an Array: An Example
first last
arr 7 15 4 9 6 18 9 12
pivot = 9
• Maintain indices i and j, starting them “outside” the array:
i = first – 1 i j
j = last + 1 7 15 4 9 6 18 9 12

• Find “out of place” elements:


• increment i until arr[i] >= pivot
• decrement j until arr[j] <= pivot
i j
7 15 4 9 6 18 9 12

• Swap arr[i] and arr[j]:


i j
7 9 4 9 6 18 15 12

Partitioning Example (cont.)


i j
from prev. page: 7 9 4 9 6 18 15 12
i j
• Find: 7 9 4 9 6 18 15 12
i j
• Swap: 7 9 4 6 9 18 15 12
j i
• Find: 7 9 4 6 9 18 15 12
and now the indices have crossed, so we return j.

• Subarrays: left = from first to j, right = from j+1 to last


first j i last
7 9 4 6 9 18 15 12

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 374


Partitioning Example 2
• Start i j
(pivot = 13): 24 5 2 13 18 4 20 19
i j
• Find: 24 5 2 13 18 4 20 19
i j
• Swap: 4 5 2 13 18 24 20 19

i j
• Find: 4 5 2 13 18 24 20 19
and now the indices are equal, so we return j.

i j
• Subarrays: 4 5 2 13 18 24 20 19

Partitioning Example 3 (done together)


• Start i j
(pivot = 5): 4 14 7 5 2 19 26 6

• Find: 4 14 7 5 2 19 26 6

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 375


Partitioning Example 4
• Start i j
(pivot = 15): 8 10 7 15 20 9 6 18

• Find: 8 10 7 15 20 9 6 18

partition() Helper Method


private static int partition(int[] arr, int first, int last)
{
int pivot = arr[(first + last)/2];
int i = first - 1; // index going left to right
int j = last + 1; // index going right to left
while (true) {
do {
i++;
} while (arr[i] < pivot);
do {
j--;
} while (arr[j] > pivot);
if (i < j) {
swap(arr, i, j);
} else {
return j; // arr[j] = end of left array
}
}
} first last
… 7 15 4 9 6 18 9 12 …

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 376


Implementation of Quicksort
public static void quickSort(int[] arr) { // "wrapper" method
qSort(arr, 0, arr.length - 1);
}

private static void qSort(int[] arr, int first, int last) {


int split = partition(arr, first, last);

if (first < split) { // if left subarray has 2+ values


qSort(arr, first, split); // sort it recursively!
}
if (last > split + 1) { // if right has 2+ values
qSort(arr, split + 1, last); // sort it!
}
} // note: base case is when neither call is made,
// because both subarrays have only one element!

split
first (j) last
… 7 9 4 6 9 18 15 12 …

A Quick Review of Logarithms


• logbn = the exponent to which b must be raised to get n
• logbn = p if bp = n
• examples: log28 = 3 because 23 = 8
log1010000 = 4 because 104 = 10000

• Another way of looking at logs:


• let's say that you repeatedly divide n by b (using integer division)
• logbn is an upper bound on the number of divisions
needed to reach 1
• example: log218 is approx. 4.17
18/2 = 9 9/2 = 4 4/2 = 2 2/2 = 1

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 377


A Quick Review of Logs (cont.)
• O(log n) algorithm – one in which the number of operations
is proportional to logbn for any base b
• logbn grows much more slowly than n
n log2n
2 1
1024 (1K) 10
1024*1024 (1M) 20
1024*1024*1024 (1G) 30

• Thus, for large values of n:


• a O(log n) algorithm is much faster than a O(n) algorithm
• log n << n
• a O(n log n) algorithm is much faster than a O(n2) algorithm
• n * log n << n*n it's also faster than a O(n1.5)
n log n << n2 algorithm like Shell sort

Time Analysis of Quicksort


• Partitioning an array of length n requires approx. n comparisons.
• most elements are compared with the pivot once; a few twice
• best case: partitioning always divides the array in half
• repeated recursive calls give:
comparisons
n n

n/2 n/2 2*(n/2) = n

n/4 n/4 n/4 n/4 4*(n/4) = n


... ... ... ... ... ... ... ...

1 1 1 1 1 1 … 1 1 1 1 0

• at each "row" except the bottom, we perform n comparisons


• there are _______ rows that include comparisons
• C(n) = ?
• Similarly, M(n) and running time are both __________

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 378


Time Analysis of Quicksort (cont.)
• worst case: pivot is always the smallest or largest element
• one subarray has 1 element, the other has n - 1
• repeated recursive calls give: comparisons
n n

1 n-1 n-1

1 n-2 n-2

1 n-3 n-3
....... ...
1 2 2

1 1
n
• C(n) =  i = O(n2). M(n) and run time are also O(n2).
i 2

• average case is harder to analyze


• C(n) > n log2n, but it’s still O(n log n)

Mergesort
• The algorithms we've seen so far have sorted the array in place.
• use only a small amount of additional memory

• Mergesort requires an additional temporary array


of the same size as the original one.
• it needs O(n) additional space, where n is the array size

• It is based on the process of merging two sorted arrays.


• example:
2 8 14 24

2 5 7 8 9 11 14 24

5 7 9 11

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 379


Merging Sorted Arrays
• To merge sorted arrays A and B into an array C, we maintain
three indices, which start out on the first elements of the arrays:
i
A 2 8 14 24 k
j C
B 5 7 9 11

• We repeatedly do the following:


• compare A[i] and B[j]
• copy the smaller of the two to C[k]
• increment the index of the array whose element was copied
• increment k
i
A 2 8 14 24 k
j C 2
B 5 7 9 11

Merging Sorted Arrays (cont.)


• Starting point:
i
A 2 8 14 24 k
j C
B 5 7 9 11

• After the first copy:


i
A 2 8 14 24 k
j C 2
B 5 7 9 11

• After the second copy:


i
A 2 8 14 24 k
j C 2 5
B 5 7 9 11

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 380


Merging Sorted Arrays (cont.)
• After the third copy:
i
A 2 8 14 24 k
j C 2 5 7
B 5 7 9 11

• After the fourth copy:


i
A 2 8 14 24 k
j C 2 5 7 8
B 5 7 9 11

• After the fifth copy:


i
A 2 8 14 24 k
j C 2 5 7 8 9
B 5 7 9 11

Merging Sorted Arrays (cont.)


• After the sixth copy:
i
A 2 8 14 24 k
j C 2 5 7 8 9 11
B 5 7 9 11

• There's nothing left in B, so we simply copy the remaining


elements from A:
i
A 2 8 14 24 k
j C 2 5 7 8 9 11 14 24
B 5 7 9 11

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 381


Divide and Conquer
• Like quicksort, mergesort is a divide-and-conquer algorithm.
• divide: split the array in half, forming two subarrays
• conquer: apply mergesort recursively to the subarrays,
stopping when a subarray has a single element
• combine: merge the sorted subarrays

12 8 14 4 6 33 2 27
split 12 8 14 4 6 33 2 27

split 12 8 14 4 6 33 2 27

split 12 8 14 4 6 33 2 27

merge 8 12 4 14 6 33 2 27
merge 4 8 12 14 2 6 27 33
merge 2 4 6 8 12 14 27 33

Tracing the Calls to Mergesort


the initial call is made to sort the entire array:

12 8 14 4 6 33 2 27

split into two 4-element subarrays, and make a recursive call to sort the left subarray:

12 8 14 4 6 33 2 27

12 8 14 4

split into two 2-element subarrays, and make a recursive call to sort the left subarray:

12 8 14 4 6 33 2 27

12 8 14 4

12 8

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 382


Tracing the Calls to Mergesort
split into two 1-element subarrays, and make a recursive call to sort the left subarray:
12 8 14 4 6 33 2 27

12 8 14 4

12 8

12

base case, so return to the call for the subarray {12, 8}:

12 8 14 4 6 33 2 27

12 8 14 4

12 8

Tracing the Calls to Mergesort


make a recursive call to sort its right subarray:
12 8 14 4 6 33 2 27

12 8 14 4

12 8

base case, so return to the call for the subarray {12, 8}:

12 8 14 4 6 33 2 27

12 8 14 4

12 8

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 383


Tracing the Calls to Mergesort
merge the sorted halves of {12, 8}:
12 8 14 4 6 33 2 27

12 8 14 4

12 8 8 12

end of the method, so return to the call for the 4-element subarray, which now has
a sorted left subarray:

12 8 14 4 6 33 2 27

8 12 14 4

Tracing the Calls to Mergesort


make a recursive call to sort the right subarray of the 4-element subarray
12 8 14 4 6 33 2 27

8 12 14 4

14 4

split it into two 1-element subarrays, and make a recursive call to sort the left subarray:

12 8 14 4 6 33 2 27

8 12 14 4

14 4

14 base case…

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 384


Tracing the Calls to Mergesort
return to the call for the subarray {14, 4}:
12 8 14 4 6 33 2 27

8 12 14 4

14 4

make a recursive call to sort its right subarray:

12 8 14 4 6 33 2 27

8 12 14 4

14 4

4 base case…

Tracing the Calls to Mergesort


return to the call for the subarray {14, 4}:
12 8 14 4 6 33 2 27

8 12 14 4

14 4

merge the sorted halves of {14, 4}:

12 8 14 4 6 33 2 27

8 12 14 4

14 4 4 14

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 385


Tracing the Calls to Mergesort
end of the method, so return to the call for the 4-element subarray, which now has
two sorted 2-element subarrays:

12 8 14 4 6 33 2 27

8 12 4 14

merge the 2-element subarrays:

12 8 14 4 6 33 2 27

8 12 4 14 4 8 12 14

Tracing the Calls to Mergesort


end of the method, so return to the call for the original array, which now has a
sorted left subarray:

4 8 12 14 6 33 2 27

perform a similar set of recursive calls to sort the right subarray. here's the result:

4 8 12 14 2 6 27 33

finally, merge the sorted 4-element subarrays to get a fully sorted 8-element array:

4 8 12 14 2 6 27 33

2 4 6 8 12 14 27 33

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 386


Implementing Mergesort
• In theory, we could create new arrays for each new pair of
subarrays, and merge them back into the array that was split.

• Instead, we'll create a temp. array of the same size as the original.
• pass it to each call of the recursive mergesort method
• use it when merging subarrays of the original array:
arr 8 12 4 14 6 33 2 27

temp 4 8 12 14

• after each merge, copy the result back into the original array:
arr 4 8 12 14 6 33 2 27

temp 4 8 12 14

A Method for Merging Subarrays


private static void merge(int[] arr, int[] temp,
int leftStart, int leftEnd, int rightStart, int rightEnd) {
int i = leftStart; // index into left subarray
int j = rightStart; // index into right subarray
int k = leftStart; // index into temp
while (i <= leftEnd && j <= rightEnd) {
if (arr[i] < arr[j]) {
temp[k] = arr[i];
i++; k++;
} else {
temp[k] = arr[j];
j++; k++;
}
}
while (i <= leftEnd) {
temp[k] = arr[i];
i++; k++;
}
while (j <= rightEnd) {
temp[k] = arr[j];
j++; k++;
}
for (i = leftStart; i <= rightEnd; i++) {
arr[i] = temp[i];
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 387


A Method for Merging Subarrays
private static void merge(int[] arr, int[] temp,
int leftStart, int leftEnd, int rightStart, int rightEnd) {
int i = leftStart; // index into left subarray
int j = rightStart; // index into right subarray
int k = leftStart; // index into temp
while (i <= leftEnd && j <= rightEnd) { // both subarrays still have values
if (arr[i] < arr[j]) {
temp[k] = arr[i];
i++; k++;
} else {
temp[k] = arr[j];
j++; k++;
}
}
...
}
leftStart leftEnd rightStart rightEnd

arr: … 4 8 12 14 2 6 27 33 …

temp: … …

Methods for Mergesort


• Here's the key recursive method:
private static void mSort(int[] arr, int[] temp, int start, int end){
if (start >= end) { // base case: subarray of length 0 or 1
return;
} else {
int middle = (start + end)/2;
mSort(arr, temp, start, middle);
mSort(arr, temp, middle + 1, end);
merge(arr, temp, start, middle, middle + 1, end);
}
}

start end

arr: … 12 8 14 4 6 33 2 27 …

temp: … …

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 388


Methods for Mergesort
• Here's the key recursive method:
private static void mSort(int[] arr, int[] temp, int start, int end){
if (start >= end) { // base case: subarray of length 0 or 1
return;
} else {
int middle = (start + end)/2;
mSort(arr, temp, start, middle);
mSort(arr, temp, middle + 1, end);
merge(arr, temp, start, middle, middle + 1, end);
}
}

• We use a "wrapper" method to create the temp array,


and to make the initial call to the recursive method:
public static void mergeSort(int[] arr) {
int[] temp = new int[arr.length];
mSort(arr, temp, 0, arr.length - 1);
}

Time Analysis of Mergesort


• Merging two halves of an array of size n requires 2n moves.
Why?
• Mergesort repeatedly divides the array in half, so we have the
following call tree (showing the sizes of the arrays):
moves
n 2n

n/2 n/2 2*2*(n/2) = 2n

n/4 n/4 n/4 n/4 4*2*(n/4) = 2n


... ... ... ... ... ... ... ...

1 1 1 1 1 1 … 1 1 1 1

• at all but the last level of the call tree, there are 2n moves
• how many levels are there?
• M(n) = ?
• C(n) = ?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 389


Summary: Sorting Algorithms
algorithm best case avg case worst case extra memory
selection sort O(n2) O(n2) O(n2) O(1)
insertion sort O(n) O (n2) O (n2) O(1)
Shell sort O(n log n) O (n1.5) O (n1.5) O(1)
bubble sort O (n2) O (n2) O (n2) O(1)
quicksort O(n log n) O(n log n) O(n2) best/avg: O(log n)
worst: O(n)
mergesort O(n log n) O(n log n) O(nlog n) O(n)

• Insertion sort is best for nearly sorted arrays.


• Mergesort has the best worst-case complexity, but requires
O(n) extra memory – and moves to and from the temp. array.
• Quicksort is comparable to mergesort in the best/average case.
• efficiency is also O(n log n), but less memory and fewer moves
• its extra memory is from…
• with a reasonable pivot choice, its worst case is seldom seen

Comparison-Based vs. Distributive Sorting


• All of the sorting algorithms we've considered have been
comparison-based:
• treat the values being sorted as wholes (comparing them)
• don’t “take them apart” in any way
• all that matters is the relative order of the values

• No comparison-based sorting algorithm can do better than


O(n log2n) on an array of length n.
• O(n log2n) is a lower bound for such algorithms

• Distributive sorting algorithms do more than compare values;


they perform calculations on the values being sorted.

• Moving beyond comparisons allows us to overcome


the lower bound.
• tradeoff: use more memory.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 390


Distributive Sorting Example: Radix Sort
• Breaks each value into a sequence of m components,
each of which has k possible values.
• Examples: m k
• integer in range 0 ... 999 3 10
• string of 15 upper-case letters 15 26
• 32-bit integer 32 2 (in binary)
4 256 (as bytes)
• Strategy: Distribute the values into "bins" according to their
last component, then concatenate the results:
33 41 12 24 31 14 13 42 34
get: 41 31 | 12 42 | 33 13 | 24 14 34
• Repeat, moving back one component each time:
get: 12 13 14 | 24 | 31 33 34 | 41 42

Analysis of Radix Sort


• m = number of components
k = number of possible values for each component
n = length of the array

• Time efficiency: O(m*n)


• perform m distributions, each of which processes all n values
• O(m*n) < O(n log n) when m < log n
so we want m to be small

• However, there is a tradeoff:


• as m decreases, k increases
• fewer components  more possible values per component
• as k increases, so does memory usage
• need more bins for the results of each distribution
• increased speed requires increased memory usage

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 391


Big-O Notation Revisited
• We've seen that we can group functions into classes by
focusing on the fastest-growing term in the expression for the
number of operations that they perform.
• e.g., an algorithm that performs n2/2 – n/2 operations is a
O(n2)-time or quadratic-time algorithm

• Common classes of algorithms:


name example expressions big-O notation
constant time 1, 7, 10 O(1)
logarithmic time 3log10n, log2n + 5 O(log n)
linear time 5n, 10n – 2log2n O(n)
nlogn time 4nlog2n, nlog2n + n O(nlog n)
slower

quadratic time 2n2 + 3n, n2 – 1 O(n2)


cubic time n2 + 3n3, 5n3 – 5 O(n3)
exponential time 2n, 5en + 2n2 O(cn)
factorial time 3n!, 5n + n! O(n!)

How Does the Number of Operations Scale?


• Let's say that we have a problem size of 1000, and we measure
the number of operations performed by a given algorithm.

• If we double the problem size to 2000, how would the number


of operations performed by an algorithm increase if it is:
• O(n)-time

• O(n2)-time

• O(n3)-time

• O(log2n)-time

• O(2n)-time

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 392


How Does the Actual Running Time Scale?
• How much time is required to solve a problem of size n?
• assume that each operation requires 1 sec (1 x 10-6 sec)
time problem size (n)
function 10 20 30 40 50 60
n .00001 s .00002 s .00003 s .00004 s .00005 s .00006 s
n2 .0001 s .0004 s .0009 s .0016 s .0025 s .0036 s
n5 .1 s 3.2 s 24.3 s 1.7 min 5.2 min 13.0 min
2n .001 s 1.0 s 17.9 min 12.7 days 35.7 yrs 36,600 yrs

• sample computations:
• when n = 10, an n2 algorithm performs 102 operations.
102 * (1 x 10-6 sec) = .0001 sec
• when n = 30, a 2n algorithm performs 230 operations.
230 * (1 x 10-6 sec) = 1073 sec = 17.9 min

What's the Largest Problem That Can Be Solved?


• What's the largest problem size n that can be solved in
a given time T? (again assume 1 sec per operation)
time time available (T)
function 1 min 1 hour 1 week 1 year
n 60,000,000 3.6 x 109 6.0 x 1011 3.1 x 1013
n2 7745 60,000 777,688 5,615,692
n5 35 81 227 500
2n 25 31 39 44

• sample computations:
• 1 hour = 3600 sec
that's enough time for 3600/(1 x 10-6) = 3.6 x 109 operations
• n2 algorithm:
n2 = 3.6 x 109  n = (3.6 x 109)1/2 = 60,000
• 2 algorithm:
n

2n = 3.6 x 109  n = log2(3.6 x 109) ~= 31

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 393


Unit 8, Part 1

Linked Lists

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Representing a Sequence of Data


• Sequence – an ordered collection of items (position matters)
• we will look at several types: lists, stacks, and queues

• Most common representation = an array

• Advantages of using an array:


• easy and efficient access to any item in the sequence
• items[i] gives you the item at position i in O(1) time
• known as random access
• very compact (but can waste space if positions are empty)

• Disadvantages of using an array:


• have to specify an initial array size and resize it as needed
• inserting/deleting items can require shifting other items
• ex: insert 63 between 52 and 72

item
items 31 52 72 ...

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 394


Alternative Representation: A Linked List
items
31 52 72
null

• A linked list stores a sequence of items in separate nodes.

• Each node is an object that contains:


• a single item 31
• a "link" (i.e., a reference) to
the node containing the next item

• The last node in the linked list has a link value of null.

• The linked list as a whole is represented by a variable that


holds a reference to the first node.
• e.g., items in the example above

Arrays vs. Linked Lists in Memory


• In an array, the elements occupy consecutive memory locations:
items 31 52 72 ...

0x100 0x104 0x108


items 0x100 31 52 72 ...

• In a linked list, the nodes are distinct objects.


• do not have to be next to each other in memory
• that's why we need the links to get from one node to the next!
31 52 72
items
null

0x520 0x812 0x208


31 52 72
items 0x520
0x812 0x208 null

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 395


Linked Lists in Memory
0x520 0x812 0x208
0x200
31 52 72
items
null

• Here's how the above linked list might actually look in memory:
0x200 0x520 the variable items
0x204
0x208 72
the last node
0x212 null
0x216
… …
0x520 31
the first node
0x524 0x812
0x528
… …
0x812 52
the second node
0x816 0x208

Features of Linked Lists


• They can grow without limit (provided there is enough memory).
• Easy to insert/delete an item – no need to "shift over" other items.
• for example, to insert 63 between 52 and 72:

before:
31 52 72
items
null

after:
31 52 72
items
null

63
• Disadvantages:
• they don't provide random access
• need to "walk down" the list to access an item
• the links take up additional memory

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 396


A String as a Linked List of Characters
'c' 'a' 't'
str1
null

• Each node represents one character.

• Java class for this type of node:


public class StringNode { ch 'c'
private char ch;
private StringNode next; next
same type as the node itself!
public StringNode(char c, StringNode n) {
this.ch = c;
this.next = n;
}
...
}

• The string as a whole is represented by a variable that holds


a reference to the node for the first character (e.g., str1 above).

A String as a Linked List (cont.)


• An empty string will be represented by a null value.
example:
StringNode str2 = null;

• We will use static methods that take the string as a parameter.


• e.g., we'll write length(str1) instead of str1.length()
• outside the class, call the methods using the class name:
StringNode.length(str1)

• This approach allows the methods to handle empty strings.


• if str1 == null:
• length(str1) will work
• str1.length() will throw a NullPointerException

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 397


Review of Variables
• A variable or variable expression represents both:
• a "box" or location in memory (the address of the variable)
• the contents of that "box" (the value of the variable)
0x200 0x520 0x812 0x208
• Practice:
str 'd' 'o' 'g'
ch
0x204 next null
temp

StringNode str; // points to the first node


StringNode temp; // points to the second node

expression address value Assumptions:


• ch field has the same
str 0x200 0x520 (ref to the 'd' node) memory address as
the node itself.
str.ch
• next field comes
str.next 2 bytes after the start
of the node.

More Complicated Expressions


0x200 0x520 0x812 0x208
str ‘d’ ‘o’ ‘g’
0x204 null
temp

• Example: temp.next.ch

• Start with the beginning of the expression: temp.next


It represents the next field of the node to which temp refers.
• address =
• value =

• Next, consider temp.next.ch


It represents the ch field of the node to which temp.next refers.
• address =
• value =

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 398


What are the address and value of str.next.next?
0x200 0x520 0x812 0x208
str ‘d’ ‘o’ ‘g’
0x204 null
temp
• str.next is…

• thus, str.next.next is…

What expression using t would give us 'e'?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 399


What expression using t would give us 'e'?

Working backwards…
• I know that I need the ch field in the 'e' node
• Where do I have a reference to the 'e' node?
• What expression can I use for the box containing that reference?

Review of Assignment Statements


• An assignment of the form
var1 = var2;
• takes the value inside var2
• copies it into var1

• Example involving integers: 0x400


int x = 5; x 5
int y = x; 0x804
5
y 5

• Example involving references: 0x600 0x320


int[] a1 = {3, 4, 5}; a1 3 4 5
int[] a2 = a1; 0x256
0x320 a2

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 400


What About These Assignments?
• Identify the two boxes.
• Determine the value in the box
specified by the right-hand side.
• Copy that value into the box
specified by the left-hand side.
1) str.next = temp.next;

2) temp.next = temp.next.next;

Writing an Appropriate Assignment


• If temp didn't already refer to the 'o' node, what assignment
would be needed to make it refer to that node?

0x200 0x520 0x812 0x208


str 'd' 'o' 'g'
0x204 null
temp

• start by asking: where do I currently have a reference


to the 'o' node?

• then ask: what expression can I use for that box?

• then write the assignment:

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 401


A Linked List Is a Recursive Data Structure!
• Recursive definition: a linked list is either
a) empty or
b) a single node, followed by a linked list

• Viewing linked lists in this way allows us to write recursive


methods that operate on linked lists.

Recursively Finding the Length of a String


• For a Java String object:

public static int length(String str) {


if (str.equals("")) {
return 0;
} else {
int lenRest = length(str.substring(1));
return 1 + lenRest;
}
}

• For a linked-list string:

public static int length(StringNode str) {


if (str == null) {
return 0;
} else {
int lenRest = length(str.next);
return 1 + lenRest;
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 402


An Alternative Version of the Method
• Original version:
public static int length(StringNode str) {
if (str == null) {
return 0;
} else {
int lenRest = length(str.next);
return 1 + lenRest;
}
}

• Version without a variable for the result of the recursive call:


public static int length(StringNode str) {
if (str == null) {
return 0;
} else {
return 1 + length(str.next);
}
}

Tracing length()
public static int length(StringNode str) {
if (str == null) {
return 0;
} else {
return 1 + length(str.next);
}
}

• Example: StringNode.length(str1)
str:null
return 0;
str:0x404 str:0x404 str:0x404
"t" "t" return 1+0
str:0x720 str:0x720 str:0x720 str:0x720 str:0x720
"at" "at" "at" "at"
return 1+1
str:0x128 str:0x128 str:0x128 str:0x128 str:0x128 str:0x128 str:0x128
"cat" "cat" "cat" "cat" "cat" "cat"
return 1+2
time

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 403


Using Iteration to Traverse a Linked List
• Many tasks require us to traverse or "walk down" a linked list.

• We just saw a method that used recursion to do this.

• It can also be done using iteration (for loops, while loops, etc.).

• We make use of a variable (call it trav) that keeps track of


where we are in the linked list.
‘w’ ‘a’ ‘l’ ‘k’
str
null

trav

• Template for traversing an entire linked list:


StringNode trav = str; // start with first node
while (trav != null) {
// process the current node here
trav = trav.next; // move trav to next node
}

Example of Iterative Traversal


• toUpperCase(str): converting str to all upper-case letters
‘f’ ‘i’ ‘n’ ‘e’
str
null

‘F’ ‘I’ ‘N’ ‘E’


str
null

• Java method:
public static void toUpperCase(StringNode str) {
StringNode trav = str;
while (trav != null) {
trav.ch = Character.toUpperCase(trav.ch);
trav = trav.next;
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 404


Tracing toUpperCase(): Part I
str 'f' 'i' 'n' 'e'
null

Calling StringNode.toUpperCase(str) adds a stack frame to the stack:


trav
str

str 'f' 'i' 'n' 'e'


null

StringNode trav = str;

trav
str

str 'f' 'i' 'n' 'e'


null

Tracing toUpperCase(): Part II


from the previous page:
trav
str

str 'f' 'i' 'n' 'e'


null

we enter the while loop:


while (trav != null) {
trav.ch = Character.toUpperCase(trav.ch);
trav = trav.next;
}

trav
str

str 'f'
'F' 'i' 'n' 'e'
null

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 405


Tracing toUpperCase(): Part II
from the previous page:
trav
str

str 'f' 'i' 'n' 'e'


null

we enter the while loop:


while (trav != null) {
trav.ch = Character.toUpperCase(trav.ch);
trav = trav.next;
}
results of the first pass through the loop:
trav
str x
str 'f'
'F' 'i' 'n' 'e'
null

Tracing toUpperCase(): Part III


while (trav != null) {
trav.ch = Character.toUpperCase(trav.ch);
trav = trav.next;
}

trav
str

str 'F' 'I' 'n' 'e'


null

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 406


Tracing toUpperCase(): Part III
while (trav != null) {
trav.ch = Character.toUpperCase(trav.ch);
trav = trav.next;
}
results of the second pass through the loop:
trav
str x

str 'F' 'i'


'I' 'n' 'e'
null

Tracing toUpperCase(): Part III


while (trav != null) {
trav.ch = Character.toUpperCase(trav.ch);
trav = trav.next;
}
results of the second pass through the loop:
trav
str x

str 'F' 'i'


'I' 'n' 'e'
null

trav
str

str 'F' 'I' 'N' 'e'


null

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 407


Tracing toUpperCase(): Part III
while (trav != null) {
trav.ch = Character.toUpperCase(trav.ch);
trav = trav.next;
}
results of the second pass through the loop:
trav
str x

str 'F' 'i'


'I' 'n' 'e'
null

results of the third pass:


trav
str x
str 'F' 'I' 'n'
'N' 'e'
null

Tracing toUpperCase(): Part IV


while (trav != null) {
trav.ch = Character.toUpperCase(trav.ch);
trav = trav.next;
}

trav
str

str 'F' 'I' 'N' 'E'


null

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 408


Tracing toUpperCase(): Part IV
while (trav != null) {
trav.ch = Character.toUpperCase(trav.ch);
trav = trav.next;
}
results of the fourth pass through the loop:
trav null
str x

str 'F' 'I' 'N' 'E'


'e'
null

Tracing toUpperCase(): Part IV


while (trav != null) {
trav.ch = Character.toUpperCase(trav.ch);
trav = trav.next;
}
results of the fourth pass through the loop:
trav null
str x

str 'F' 'I' 'N' 'E'


'e'
null

and now trav == null, so we end the loop and return:

str 'F' 'I' 'N' 'E'


null

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 409


Getting the Node at Position i in a Linked List
• getNode(str, i) – should return a reference to the ith node
in the linked list to which str refers

'f' 'i' 'n' 'e'


str
null

• Examples:
• getNode(str, 0) should return a ref. to the 'f' node
• getNode(str, 3) should return a ref. to the 'e' node
• getNode(str.next, 2) should return a ref. to…?

• More generally, when 0 < i < length of list,


getNode(str, i) is equivalent to getNode(str.next, i-1)

Getting the Node at Position i in a Linked List


'f' 'i' 'n' 'e'
str
null

• Recursive approach to getNode(str, i):


• if i == 0, return str (base case)
• else call getNode(str.next, i-1) and return what it returns!
• other base case?

• Here's the method:


private static StringNode getNode(StringNode str, int i) {
if (i < 0 || str == null) { // base case 1: no node i
return null;
} else if (i == 0) { // base case 2: just found
return str;
} else {
return getNode(str.next, i-1);
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 410


Deleting the Item at Position i
• Special case: i == 0 (deleting the first item)
• Update our reference to the first node by doing:
str = str.next;

str x 'j' 'a' 'v' 'a'


null

Deleting the Item at Position i (cont.)


• General case: i > 0
1. Obtain a reference to the previous node:
StringNode prevNode = getNode(i - 1);
(example for i == 1)

str 'j' 'a' 'v' 'a'


null

prevNode

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 411


Deleting the Item at Position i (cont.)
• General case: i > 0
2. Update the references to remove the node
(example for i == 1)
before:

str 'j' 'a' 'v' 'a'


null

prevNode

after:

str 'j' 'a' 'v' 'a'


null

prevNode
_____________ = __________________;

Inserting an Item at Position i


• Special case: i == 0 (insertion at the front of the list)
• Step 1: Create the new node. Fill in the blanks!
before:
ch 'f'

str 'a' 'c' 'e'


null

after:
ch 'f'
'f'
newNode

str 'a' 'c' 'e'


null

StringNode newNode = new StringNode(_______, _______);

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 412


Inserting an Item at Position i (cont.)
• Special case: i == 0 (continued)
• Step 2: Insert the new node. Write the assignment!
before (result of previous slide):

ch 'f'
'f'
newNode

str 'a' 'c' 'e'


null

after:
ch 'f'
'f'
newNode

str 'a' 'c' 'e'


null

Inserting an Item at Position i (cont.)


• General case: i > 0 (insert before the item currently in posn i)
before:

'a' 'c' 'e'


str
null
ch 'm'

after (assume that i == 2): 'm'

newNode
'a' 'c' 'e'
str
x null
ch 'm'
prevNode

StringNode prevNode = getNode(i - 1);


StringNode newNode = new StringNode(ch, ________________);
___________________________________ // one more line

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 413


Returning a Reference to the First Node
• Both deleteChar() and insertChar() return a reference to
the first node in the linked list. For example:
public static StringNode deleteChar(StringNode str, int i) {

if (i == 0) { // special case
str = str.next;
} else { // general case
StringNode prevNode = getNode(str, i-1);
if (prevNode != null && prevNode.next != null) {
prevNode.next = prevNode.next.next;

}
return str;
}

• Clients should call them as part of an assignment:


s1 = StringNode.deleteChar(s1, 0);
s2 = StringNode.insertChar(s2, 0, 'h');
• If the first node changes, the client's variable will be updated
to point to the new first node.

Creating a Copy of a Linked List


• copy(str) – create a copy of the entire list to which str refers

• Recursive approach:
• base case: if str is empty, return null
• else: – make a recursive call to copy the rest of the linked list
– create and return a copy of the first node,
with its next field pointing to the copy of the rest

public static StringNode copy(StringNode str) {


if (str == null) { // base case
return null;
}
// make a recursive call to copy the rest of the list
StringNode copyRest = copy(str.next);

// create and return a copy of the first node,


// with its next field pointing to the copy of the rest
return new StringNode(str.ch, copyRest);
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 414


Tracing copy(): the initial call
• From a client: StringNode s2 = StringNode.copy(s1);

public static StringNode copy(StringNode str) {


if (str == null) {
return null;
}
StringNode copyRest = copy(str.next);
return new StringNode(str.ch, copyRest);
}

stack heap

copyRest
str

s2 'd' 'o' 'g'


s1 null

Tracing copy(): the initial call


• From a client: StringNode s2 = StringNode.copy(s1);

public static StringNode copy(StringNode str) {


if (str == null) {
return null;
}
StringNode copyRest = copy(str.next);
return new StringNode(str.ch, copyRest);
}

copyRest
str

s2 'd' 'o' 'g'


s1 null

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 415


Tracing copy(): the initial call
• From a client: StringNode s2 = StringNode.copy(s1);

public static StringNode copy(StringNode str) {


if (str == null) {
return null;
}
StringNode copyRest = copy(str.next);
return new StringNode(str.ch, copyRest);
}

copyRest
str

copyRest
str

s2 'd' 'o' 'g'


s1 null

Tracing copy(): the recursive calls


public static StringNode copy(...) {
if (str == null) {
return null;
}
StringNode copyRest = copy(str.next);
return new StringNode(str.ch,
copyRest);
}

copyRest
str

copyRest
str

s2 'd' 'o' 'g'


s1 null

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 416


Tracing copy(): the recursive calls
public static StringNode copy(...) {
if (str == null) {
return null;
}
StringNode copyRest = copy(str.next);
return new StringNode(str.ch,
copyRest);
}

copyRest
str

copyRest
str

s2 'd' 'o' 'g'


s1 null

Tracing copy(): the recursive calls


public static StringNode copy(...) {
if (str == null) {
return null;
}
StringNode copyRest = copy(str.next);
return new StringNode(str.ch,
copyRest);
}

copyRest
str

copyRest
str

copyRest
str

s2 'd' 'o' 'g'


s1 null

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 417


Tracing copy(): the recursive calls
public static StringNode copy(...) {
if (str == null) {
return null;
}
StringNode copyRest = copy(str.next);
return new StringNode(str.ch,
copyRest);
}

copyRest
str

copyRest
str

copyRest
str

s2 'd' 'o' 'g'


s1 null

Tracing copy(): the recursive calls


public static StringNode copy(...) {
if (str == null) {
return null;
}
StringNode copyRest = copy(str.next);
copyRest return new StringNode(str.ch,
copyRest);
str null }

copyRest
str

copyRest
str

copyRest
str

s2 'd' 'o' 'g'


s1 null

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 418


Tracing copy(): the base case
public static StringNode copy(...) {
if (str == null) {
return null;
}
heap
StringNode copyRest = copy(str.next);
copyRest return new StringNode(str.ch,
copyRest);
str null }

copyRest
str

copyRest
str

copyRest
str

s2 'd' 'o' 'g'


s1 null

Tracing copy(): returning from the base case


public static StringNode copy(...) {
if (str == null) {
return null;
}
StringNode copyRest = copy(str.next);
return new StringNode(str.ch,
copyRest);
}

copyRest null
str

copyRest
str

copyRest
str

s2 'd' 'o' 'g'


s1 null

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 419


Tracing copy(): returning from the base case
public static StringNode copy(...) {
if (str == null) {
return null;
}
StringNode copyRest = copy(str.next);
return new StringNode(str.ch,
copyRest);
}

copyRest null 'g'


str null

copyRest
str

copyRest
str

s2 'd' 'o' 'g'


s1 null

Tracing copy(): returning from the base case


public static StringNode copy(...) {
if (str == null) {
return null;
}
StringNode copyRest = copy(str.next);
return new StringNode(str.ch,
copyRest);
}

'g'
null

copyRest
str

copyRest
str

s2 'd' 'o' 'g'


s1 null

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 420


Tracing copy(): returning from the base case
public static StringNode copy(...) {
if (str == null) {
return null;
}
StringNode copyRest = copy(str.next);
return new StringNode(str.ch,
copyRest);
}

'g'
null

copyRest 'o'
str

copyRest
str

s2 'd' 'o' 'g'


s1 null

Tracing copy(): returning from the base case


public static StringNode copy(...) {
if (str == null) {
return null;
}
StringNode copyRest = copy(str.next);
return new StringNode(str.ch,
copyRest);
}

'g'
null

'o'

copyRest
str

s2 'd' 'o' 'g'


s1 null

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 421


Tracing copy(): returning from the base case
public static StringNode copy(...) {
if (str == null) {
return null;
}
StringNode copyRest = copy(str.next);
return new StringNode(str.ch,
copyRest);
}

'g'
null

'o'

copyRest 'd'
str

s2 'd' 'o' 'g'


s1 null

Tracing copy(): returning from the base case


• From a client: StringNode s2 = StringNode.copy(s1);

'g'
null

'o'

'd'

s2 'd' 'o' 'g'


s1 null

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 422


Tracing copy(): Final Result
• s2 now holds a reference to a linked list that is a copy of the
linked list to which s1 holds a reference.

'g'
null

'o'

'd'

s2 'd' 'o' 'g'


s1 null

Using a "Trailing Reference" During Traversal


• When traversing a linked list, one trav may not be enough.
• Ex: insert ch = 'n' at the right place in this sorted linked list:
‘a’ ‘c’ ‘p’ ‘z’
str
null

trav

• Traverse the list to find the right position:


StringNode trav = str;
while (trav != null && trav.ch < ch) {
trav = trav.next;
}
• When we exit the loop, where will trav point? Can we insert 'n'?

• The following changed version doesn't work either. Why not?


while (trav != null && trav.next.ch < ch) {
trav = trav.next;
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 423


Using a "Trailing Reference" (cont.)
• To get around the problem seen on the previous page,
we traverse the list using two different references:
• trav, which we use as before
• trail, which stays one node behind trav

'a' 'c' 'p' 'z'


str
null

trail null trav

StringNode trav = str;


StringNode trail = null;
while (trav != null && trav.ch < ch) {
trail = trav;
trav = trav.next;
}
// if trail == null, insert at the front of the list
// else insert after the node to which trail refers

Using a "Trailing Reference" (cont.)


• To get around the problem seen on the previous page,
we traverse the list using two different references:
• trav, which we use as before
• trail, which stays one node behind trav

'a' 'c' 'p' 'z'


str
null

trail trav

StringNode trav = str;


StringNode trail = null;
while (trav != null && trav.ch < ch) {
trail = trav;
trav = trav.next;
}
// if trail == null, insert at the front of the list
// else insert after the node to which trail refers

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 424


Using a "Trailing Reference" (cont.)
• To get around the problem seen on the previous page,
we traverse the list using two different references:
• trav, which we use as before
• trail, which stays one node behind trav

'a' 'c' 'p' 'z'


str
null

trail trav

StringNode trav = str;


StringNode trail = null;
while (trav != null && trav.ch < ch) {
trail = trav;
trav = trav.next;
}
// if trail == null, insert at the front of the list
// else insert after the node to which trail refers

Doubly Linked Lists

• In a doubly linked list, every node stores two references:


• next, which works the same as before
• prev, which holds a reference to the previous node
• in the first node, prev has a value of null

• The prev references allow us to "back up" as needed.


• remove the need for a trailing reference during traversal!

• Insertion and deletion must update both types of references.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 425


Find the address and value of s.next.next.ch

Extra practice!

address value
A. 0xbe00 'r'
B. 0x3004 'e'
C. 0xbb00 'a'
D. none of these

Find the address and value of s.next.next.ch

• s.next is the next field in the node to which s refers


• it holds a reference to the 'r' node
• thus, s.next.next is the next field in the 'r' node
• it holds a reference to the 'e' node
• thus, s.next.next.ch is the ch field in the 'e' node
• it holds the 'e'!
address value
A. 0xbe00 'r'
B. 0x3004 'e'
C. 0xbb00 'a'
D. none of these

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 426


Unit 8, Part 2

Lists, Stacks, and Queues

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Representing a Sequence: Arrays vs. Linked Lists


• Sequence – an ordered collection of items (position matters)
• we will look at several types: lists, stacks, and queues
• Can represent any sequence using an array or a linked list
array linked list
representation elements occupy consecutive nodes can be at arbitrary
in memory memory locations locations in memory; the links
connect the nodes together
advantages • provide random access • can grow to an arbitrary length
(access to any item in • allocate nodes as needed
constant time)
• inserting or deleting does not
• no extra memory needed for require shifting items
links
disadvantages • have to preallocate the • no random access (may need
memory needed for the to traverse the list)
maximum sequence size • need extra memory for links
• inserting or deleting can
require shifting items

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 427


The List ADT
• A list is a sequence in which items can be accessed,
inserted, and removed at any position in the sequence.

• The operations supported by our List ADT:


• getItem(i): get the item at position i
• addItem(item, i): add the specified item at position i
• removeItem(i): remove the item at position i
• length(): get the number of items in the list
• isFull(): test if the list already has the maximum number
of items

• Note that we don’t specify how the list will be implemented.

Our List Interface


public interface List {
Object getItem(int i);
boolean addItem(Object item, int i);
Object removeItem(int i);
int length();
boolean isFull();
}

• Recall that all methods in an interface must be public ,


so we don’t need the keyword public in the headers.

• We use the Object type to allow for items of any type.

• addItem() returns false if the list is full, and true otherwise.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 428


Implementing a List Using an Array
public class ArrayList implements List {
private Object[] items;
private int length;
public ArrayList(int maxSize) {
// code to check for invalid maxSize goes here...
this.items = new Object[maxSize];
this.length = 0;
}
public int length() {
return this.length;
}
public boolean isFull() {
return (this.length == this.items.length);
}
...
}

items null …
list
length 2
"if"
a variable of type
ArrayList an ArrayList object "for"

Recall: The Implicit Parameter


public class ArrayList implements List {
private Object[] items;
private int length;
public ArrayList(int maxSize) {
this.items = new Object[maxSize];
this.length = 0;
}
public int length() {
return this.length;
}
public boolean isFull() {
return (this.length == this.items.length);
}
...
}

• All non-static methods have an implicit parameter (this)


that refers to the called object.

• In most cases, we're allowed to omit it!


• we'll do so in the remaining notes

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 429


Omitting The Implicit Parameter
public class ArrayList implements List {
private Object[] items;
private int length;
public ArrayList(int maxSize) {
items = new Object[maxSize];
length = 0;
}
public int length() {
return length;
}
public boolean isFull() {
return (length == items.length);
}
...
}

• In a non-static method, if we use a variable that


• isn't declared in the method
• has the name of one of the fields
Java assumes that we're using the field.

Adding an Item to an ArrayList


• Adding at position i (shifting items i, i+1, … to the right by one):
public boolean addItem(Object item, int i) {
if (item == null || i < 0 || i > length) {
throw new IllegalArgumentException();
} else if (isFull()) {
return false;
}
// make room for the new item
for (int j = length - 1; j >= i; j--) {
items[j + 1] = items[j];
}
items[i] = item;
length++;
return true;
}
example for i = 3: 1 5
0 2 3 4 6 7 8
items
length 6

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 430


Adding an Item to an ArrayList
• Adding at position i (shifting items i, i+1, … to the right by one):
public boolean addItem(Object item, int i) {
if (item == null || i < 0 || i > length) {
throw new IllegalArgumentException();
} else if (isFull()) {
return false;
}
// make room for the new item
for (int j = length - 1; j >= i; j--) {
items[j + 1] = items[j];
}
items[i] = item;
length++;
return true;
}
example for i = 3: 1 5
0 2 3 4 6 7 8
items
length 7

Removing an Item from an ArrayList


• Removing item i (shifting items i+1, i+2, … to the left by one):
public Object removeItem(int i) {
if (i < 0 || i >= length) {
throw new IndexOutOfBoundsException();
}
Object removed = items[i];
// shift items after items[i] to the left
for (int j = i; j < length - 1; j++) {
____________________________;
}
items[length - 1] = null;
length--;
return removed;
}
example for i = 1:
0 1 2 3 4 5 6 7 8
items null null null null
length 5
"Dave" "Libby"
"Cody" "Ash"
removed "Kylie"

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 431


Getting an Item from an ArrayList
• Getting item i (without removing it):
public Object getItem(int i) {
if (i < 0 || i >= length) {
throw new IndexOutOfBoundsException();
}
return items[i];
}

toString() Method for the ArrayList Class


public String toString() {
String str = "{";
if (length > 0) {
for (int i = 0; i < length - 1; i++) {
str = str + items[i] + ", ";
}
str = str + items[length - 1];
}
str = str + "}";
return str;
}

• Produces a string of the following form:


{items[0], items[1], … }

• Why is the last item added outside the loop?

• Why do we need the if statement?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 432


Implementing a List Using a Linked List
public class LLList implements List {
private Node head;
private int length;
...
}

null "how" "are" "you"


head
list null
length 3
variable of type dummy head node
LLList LLList object
Node objects

• Differences from the linked lists we used for strings:


• we "embed" the linked list inside another class
• users of our LLList class won't actually touch the nodes
• we use non-static methods instead of static ones
myList.length() instead of length(myList)
• we use a special dummy head node as the first node

Using a Dummy Head Node


null "how" "are" "you"
head
null
length 3

dummy head node


LLList object

• The dummy head node is always at the front of the linked list.
• like the other nodes in the linked list, it’s of type Node
• it does not store an item
• it does not count towards the length of the list

• Using it allows us to avoid special cases when adding and


removing nodes from the linked list.

• An empty LLList still has a dummy head node:


null
head
null
length 0

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 433


An Inner Class for the Nodes
public class LLList implements List {
private class Node { item "hi"
private Object item; next
private private Node next;
since only Node object
private Node(Object i, Node n) {
LLList item = i;
will use it next = n;
}
}
...
}

• We make Node an inner class, defining it within LLList.


• allows the LLList methods to directly access Node’s private
fields, while restricting access from outside LLList
• the compiler creates this class file: LLList$Node.class
• For simplicity, our diagrams may show the items inside the nodes.
"hi" instead of "hi"

Other Details of Our LLList Class


public class LLList implements List {
private class Node {
// see previous slide
}
private Node head;
private int length;

public LLList() {
head = new Node(null, null);
length = 0;
}

public boolean isFull() {


return false;
}
...
}

• Unlike ArrayList, there’s no need to preallocate space for the


items. The constructor simply creates the dummy head node.
• The linked list can grow indefinitely, so the list is never full!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 434


Getting a Node
• Private helper method for getting node i
• to get the dummy head node, use i = -1
private Node getNode(int i) {
// private method, so we assume i is valid!

Node trav = ;
int travIndex = -1;
while ( ) {
travIndex++;
;
}
return trav;
} trav travIndex -1

example for i = 1: -1 0 1 2
item null "how" "are" "you"
head
next null
length 3

LLList object Node objects

Getting an Item
public Object getItem(int i) {
if (i < 0 || i >= length) {
throw new IndexOutOfBoundsException();
}

Node n = getNode(i);
return ________;
}

example for i = 1:
n

-1 0 1 2
item null "how" "are" "you"
head
next null
length 3

LLList object Node objects

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 435


Adding an Item to an LLList
public boolean addItem(Object item, int i) {
if (item == null || i < 0 || i > length) {
throw new IllegalArgumentException();
}
Node newNode = new Node(item, null);
Node prevNode = getNode(i - 1);
newNode.next = prevNode.next;
prevNode.next = newNode;
length++;
return true;
}
• This works even when adding at the front of the list (i = 0):
-1 0 1 2
item null "how" "are" "you"
head
next x null
length 4
3

prevNode "hi!"
newNode null

addItem() Without a Dummy Head Node


public boolean addItem(Object item, int i) {
if (item == null || i < 0 || i > length) {
throw new IllegalArgumentException();
}
Node newNode = new Node(item, null);

if (i == 0) { // case 1: add to front


newNode.next = head;
head = newNode;
} else { // case 2: i > 0
Node prevNode = getNode(i - 1);
newNode.next = prevNode.next;
prevNode.next = newNode;
}

length++;
return true;
}

(the gray code shows what we would need to add if we didn't have a dummy head node)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 436


Removing an Item from an LLList
public Object removeItem(int i) {
if (i < 0 || i >= length) {
throw new IndexOutOfBoundsException();
}
Node prevNode = getNode(i - 1);
Object removed = prevNode.next.item;
// what line goes here?
length--;
return removed;
}

• This works even when removing the first item (i = 0):


removed "how" "are" "you"
-1 0 1 2
item null
head
next x null
length 3

prevNode

toString() Method for the LLList Class


public String toString() {
String str = "{";

// what should go here?

str = str + "}";

return str;
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 437


Efficiency of the List ADT Implementations
n = number of items in the list
ArrayList LLList
getItem() only one case: best:
worst:

average:

addItem() best: best:

worst: worst:

average: average:

Efficiency of the List ADT Implementations (cont.)


n = number of items in the list
ArrayList LLList
removeItem() best: best:

worst: worst:

average: average:

space
efficiency

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 438


Counting the Number of Occurrences of an Item
public class MyClass {
public static int numOccur(List l, Object item) {
int numOccur = 0;
for (int i = 0; i < l.length(); i++) {
Object itemAt = l.getItem(i);
if (itemAt.equals(item)) {
numOccur++;
}
}
return numOccur;
} ...

• This method works fine if we pass in an ArrayList object.


• time efficiency (as a function of the length, n) = ?
• However, it's not efficient if we pass in an LLList.
• each call to getItem() calls getNode()
• to access item 0, getNode() accesses 2 nodes (dummy + node 0)
• to access item 1, getNode() accesses 3 nodes
• to access item i, getNode() accesses i+2 nodes
• 2 + 3 + … + (n+1) = ?

Solution: Provide an Iterator


public class MyClass {
public static int numOccur(List l, Object item) {
int numOccur = 0;
ListIterator iter = l.iterator();
while (iter.hasNext()) {
Object itemAt = iter.next();
if (itemAt.equals(item)) {
numOccur++;
}
}
return numOccur;
} ...

• We add an iterator() method to the List interface.


• it returns a separate iterator object that can efficiently
iterate over the items in the list
• The iterator has two key methods:
• hasNext(): tells us if there are items we haven't seen yet
• next(): returns the next item and advances the iterator

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 439


An Interface for List Iterators
• Here again, the interface only includes the method headers:
public interface ListIterator { // in ListIterator.java
boolean hasNext();
Object next();
}

• We can then implement this interface for each type of list:


• LLListIterator for an iterator that works with LLLists
• ArrayListIterator for an iterator for ArrayLists

• We use the interfaces when declaring variables in client code:


public class MyClass {
public static int numOccur(List l, Object item) {
int numOccur = 0;
ListIterator iter = l.iterator();
...
• doing so allows the code to work for any type of list!

Using an Inner Class for the Iterator


public class LLList {
private Node head;
private int length;
private class LLListIterator implements ListIterator {
private Node nextNode; // points to node with the next item
public LLListIterator() {
nextNode = head.next; // skip over dummy head node
}
...
}
public ListIterator iterator() {
return new LLListIterator();
}
...

• Using an inner class gives the iterator access to the list’s internals.
• The iterator() method is an LLList method.
• it creates an instance of the inner class and returns it
• its return type is the interface type
• so it will work in the context of client code

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 440


Full LLListIterator Implementation
private class LLListIterator implements ListIterator {
private Node nextNode; // points to node with the next item
public LLListIterator() {
nextNode = head.next; // skip over the dummy head node
}
public boolean hasNext() {
return (nextNode != null);
}
public Object next() {
// throw an exception if nextNode is null

Object item = _______________;

nextNode = _______________;
return item; "how" "are" "you"
}
}
item null
head
next null
length 3
LLList
object nextNode
LLListIterator object

Stack ADT
• A stack is a sequence in which:
• items can be added and removed only at one end (the top)
• you can only access the item that is currently at the top

• Operations:
• push: add an item to the top of the stack
• pop: remove the item at the top of the stack
• peek: get the item at the top of the stack, but don’t remove it
• isEmpty: test if the stack is empty
• isFull: test if the stack is full

• Example: a stack of integers

start: push 8: pop: pop: push 3:


8
15 15 15 3
7 7 7 7 7

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 441


A Stack Interface: First Version
public interface Stack {
boolean push(Object item);
Object pop();
Object peek();
boolean isEmpty();
boolean isFull();
}

• push() returns false if the stack is full, and true otherwise.


• pop() and peek() take no arguments, because we know that
we always access the item at the top of the stack.
• return null if the stack is empty.
• The interface provides no way to access/insert/delete an item
at an arbitrary position.
• encapsulation allows us to ensure that our stacks are
only manipulated in appropriate ways

Implementing a Stack Using an Array: First Version


public class ArrayStack implements Stack {
private Object[] items;
private int top; // index of the top item
public ArrayStack(int maxSize) {
// code to check for invalid maxSize goes here...
items = new Object[maxSize];
top = -1;
}
...

• Example: the stack 15


7
0 1 2
items null …
s1
top 1
7
variable of type
ArrayStack ArrayStack object 15

• Items are added from left to right (top item = the rightmost one).
• push() and pop() won't require any shifting!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 442


Collection Classes and Data Types
public class ArrayStack implements Stack {
private Object[] items;
private int top; // index of the top item
...
} 0 1 2 3
items null null
s1
top 1
7 "hi"

• So far, our collections have allowed us to add objects of any type.


ArrayStack s1 = new ArrayStack(4);
s1.push(7); // 7 is turned into an Integer object for 7
s1.push("hi");
String item = s1.pop(); // won't compile
String item = (String)s1.pop(); // need a type cast

• We'd like to be able to limit a given collection to one type.


ArrayStack<String> s2 = new ArrayStack<String>(10);
s2.push(7); // won't compile
s2.push("hello");
String item = s2.pop(); // no cast needed!

Limiting a Stack to Objects of a Given Type


• We can do this by using a generic interface and class.

• Here's a generic version of our Stack interface:


public interface Stack<T> {
boolean push(T item);
T pop();
T peek();
boolean isEmpty();
boolean isFull();
}

• It includes a type variable T in its header and body.


• used as a placeholder for the actual type of the items

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 443


A Generic ArrayStack Class
public class ArrayStack<T> implements Stack<T> {
private T[] items;
private int top; // index of the top item

public boolean push(T item) {

}

}

• Once again, a type variable T is used as a placeholder for the


actual type of the items.

• When we create an ArrayStack, we specify the type of items


that we intend to store in the stack:
ArrayStack<String> s1 = new ArrayStack<String>(10);
ArrayStack<Integer> s2 = new ArrayStack<Integer>(25);

• We can still allow for a mixed-type collection:


ArrayStack<Object> s3 = new ArrayStack<Object>(20);

Using a Generic Class


public class ArrayStack<String> {
private String[] items;
private int top;
...
public boolean push(String item) {
...

ArrayStack<String> s1 =
new ArrayStack<String>(10);

public class ArrayStack<T> ... {


private T[] items;
private int top;
...
public boolean push(T item) {
...

ArrayStack<Integer> s2 =
new ArrayStack<Integer>(25);

public class ArrayStack<Integer> {


private Integer[] items;
private int top;
...
public boolean push(Integer item) {
...

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 444


ArrayStack Constructor
• Java doesn’t allow you to create an object or array using
a type variable. Thus, we cannot do this:
public ArrayStack(int maxSize) {
// code to check for invalid maxSize goes here...
items = new T[maxSize]; // not allowed
top = -1;
}

• Instead, we do this:
public ArrayStack(int maxSize) {
// code to check for invalid maxSize goes here...
items = (T[])new Object[maxSize];
top = -1;
}

• The cast generates a compile-time warning, but we’ll ignore it.

• Java’s built-in ArrayList class takes this same approach.

Testing if an ArrayStack is Empty or Full


• Empty stack:
0 1 2 3 4 5 6 7 8
items
top -1

public boolean isEmpty() {


return (top == -1);
}

• Full stack:
0 1 2 3 4 5 6 7 8
items
top 8

public boolean isFull() {


return (top == items.length - 1);
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 445


Pushing an Item onto an ArrayStack
0 1 2 3 4 5 6 7 8
items
top 4

public boolean push(T item) {


// code to check for a null item goes here
if (isFull()) {
return false;
}
top++;
items[top] = item;
return true;
}

ArrayStack pop() and peek()


0 1 2 3 4 5 6 7
items null null null null
top 3
10 5 9 13

removed

public T pop() {
if (isEmpty()) {
return null;
}

______ removed = items[top];


items[top] = null;
top--;
return removed;
}

• peek just returns items[top] without decrementing top.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 446


Implementing a Generic Stack Using a Linked List
public class LLStack<T> implements Stack<T> {
private Node top; // top of the stack

}

• Example: the stack 15


7
15 7

s2
top
null
variable of type
LLStack object
LLStack Node objects
• Things worth noting:
• our LLStack class needs only a single field:
a reference to the first node, which holds the top item
• top item = leftmost item (vs. rightmost item in ArrayStack)
• we don’t need a dummy node
• only one case: always insert/delete at the front of the list!

Other Details of Our LLStack Class


public class LLStack<T> implements Stack<T> {
private class Node {
private T item;
private Node next;
...
}
private Node top;
public LLStack() {
top = null;
}
public boolean isEmpty() {
return (top == null);
}
public boolean isFull() {
return false;
}
}

• The inner Node class uses the type parameter T for the item.
• We don’t need to preallocate any memory for the items.
• The stack is never full!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 447


LLStack push()
15 7

top
null

item 8

newNode

public boolean push(T item) {


// code to check for a null item goes here
Node newNode = new Node(item, top);
top = newNode;
return true;
}

LLStack push()
15 7

top x
null

item 8

newNode

public boolean push(T item) {


// code to check for a null item goes here
Node newNode = new Node(item, top);
top = newNode;
return true;
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 448


LLStack pop() and peek()
removed 15 7

top x
null

public T pop() {
if (isEmpty()) {
return null;
}
T removed = _______________;

____________________________;
return removed;
}
public T peek() {
if (isEmpty()) {
return null;
}
return top.item;
}

Efficiency of the Stack Implementations

ArrayStack LLStack
push() O(1) O(1)
pop() O(1) O(1)
peek() O(1) O(1)
space O(m) where m is the O(n) where n is the number of
efficiency anticipated maximum number items currently on the stack
of items

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 449


Applications of Stacks
• Converting a recursive algorithm to an iterative one
• use a stack to emulate the runtime stack

• Making sure that delimiters (parens, brackets, etc.) are balanced:


• push open (i.e., left) delimiters onto a stack
• when you encounter a close (i.e., right) delimiter,
pop an item off the stack and see if it matches
• example:
5 * [3 + {(5 + 16 – 2)]

push [ push { push ( ( ), so ], so


pop. pop.
{ { get (, { get {,
[ [ [ which [ which [
matches doesn’t
match
• Evaluating arithmetic expressions

Queue ADT
• A queue is a sequence in which:
• items are added at the rear and removed from the front
• first in, first out (FIFO) (vs. a stack, which is last in, first out)
• you can only access the item that is currently at the front

• Operations:
• insert: add an item at the rear of the queue
• remove: remove the item at the front of the queue
• peek: get the item at the front of the queue, but don’t remove it
• isEmpty: test if the queue is empty
• isFull: test if the queue is full

• Example: a queue of integers


start: 12 8
insert 5: 12 8 5
remove: 8 5

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 450


Our Generic Queue Interface
public interface Queue<T> {
boolean insert(T item);
T remove();
T peek();
boolean isEmpty();
boolean isFull();
}

• insert() returns false if the queue is full, and true otherwise.

• remove() and peek() take no arguments, because


we always access the item at the front of the queue.
• return null if the queue is empty.
• Here again, we will use encapsulation to ensure that the
data structure is manipulated only in valid ways.

Implementing a Queue Using an Array


public class ArrayQueue<T> implements Queue<T> {
private T[] items;
private int front;
private int rear;
private int numItems;
...
}

• Example: 0 1 2 3
items
queue
front 1
3
73
variable of type rear
ArrayQueue numItems 3 25 51
ArrayQueue object

• We maintain two indices:


• front: the index of the item at the front of the queue
• rear: the index of the item at the rear of the queue

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 451


Avoiding the Need to Shift Items
• Problem: what do we do when we reach the end of the array?
example: a queue of integers:
front rear
54 4 21 17 89 65

the same queue after removing two items and inserting two:
front rear
21 17 89 65 43 81

we have room for more items, but shifting to make room is inefficient

• Solution: maintain a circular queue. When we reach the end of


the array, we wrap around to the beginning.
insert 5: wrap around!
rear front
5 21 17 89 65 43 81

Maintaining a Circular Queue


• We use the mod operator (%) when updating front or rear:
front = (front + 1) % items.length;
rear = (rear + 1) % items.length;

• Example: front rear


items 21 17 89 43
q
front 1
rear 4
numItems 4

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 452


Maintaining a Circular Queue
• We use the mod operator (%) when updating front or rear:
front = (front + 1) % items.length;
rear = (rear + 1) % items.length;

• Example: front rear


items 21 17 89 43 81
q
front 1
rear 5
numItems 5

• q.insert(81): // rear is not at end of array


• rear = (rear + 1) % items.length;
= ( 4 + 1) % 6
= 5 % 6 = 5 (% has no effect)

Maintaining a Circular Queue


• We use the mod operator (%) when updating front or rear:
front = (front + 1) % items.length;
rear = (rear + 1) % items.length;

• Example: rear front


items 33 21 17 89 43 81
q
front 1
rear 0
numItems 6

• q.insert(81): // rear is not at end of array


• rear = (rear + 1) % items.length;
= ( 4 + 1) % 6
= 5 % 6 = 5 (% has no effect)
• q.insert(33): // rear is at end of array
• rear = (rear + 1) % items.length;
= ( 5 + 1) % 6
= 6 % 6 = 0 wrap around!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 453


Inserting an Item in an ArrayQueue
• We increment rear before adding the item:
front rear
before:

front rear
after:

public boolean insert(T item) {


// code to check for a null item goes here
if (isFull()) {
return false;
}
rear = (rear + 1) % items.length;
items[rear] = item;
numItems++;
return true;
}

ArrayQueue remove()

front rear
before:

10 5 9 13

front rear
after: null

removed 10 5 9 13

public T remove() {
if (isEmpty()) {
return null;
}
T removed = _________________;

numItems--;
return removed;
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 454


Constructor
public ArrayQueue(int maxSize) {
// code to check for an invalid maxSize goes here...
items = (T[])new Object[maxSize];
front = 0;
rear = -1;
numItems = 0;
}

• When we insert the first item in a newly created ArrayQueue,


we want it to go in position 0. Thus, we need to:
• start rear at -1, since then it will be incremented to 0
and used to perform the insertion
• start front at 0, since it is not changed by the insertion

0 1 0 1
items null null … items null …
front 0 front 0
rear -1 rear 0
numItems 0 numItems 1 "hi"

Testing if an ArrayQueue is Empty or Full


• In both empty and full queues, rear is one "behind" front:
rear front
initial configuration:
rear front
after two insertions and
two removals:
rear front
after 7 more insertions: 5 36 21 17 89 65 43

• This is why we maintain numItems!


public boolean isEmpty() {
return (numItems == 0);
}

public boolean isFull() {


return (numItems == items.length);
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 455


Implementing a Queue Using a Linked List
public class LLQueue<T> implements Queue<T> {
private Node front; // front of the queue
private Node rear; // rear of the queue

}
"hi" "how" "are" "you"
• Example:
item
front
queue next null
rear

variable of type
LLQueue LLQueue object Node objects

• In a linked list, we can efficiently:


• remove the item at the front
• add an item to the rear (if we have a ref. to the last node)

• Thus, this implementation is simpler than the array-based one!

Other Details of Our LLQueue Class


public class LLQueue<T> implements Queue<T> {
private class Node {
private T item;
private Node next;
...
}
private Node front;
private Node rear;
public LLQueue() {
front = null;
rear = null;
}
public boolean isEmpty() {
return (front == null);
}
public boolean isFull() {
return false;
}

}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 456


Inserting an Item in an Empty LLQueue

front null
rear null
The next field in the newNode
item "now"
will be null regardless of whether
the queue is empty. Why?
newNode
null

public boolean insert(T item) {


// code to check for a null item goes here
Node newNode = new Node(item, null);
if (isEmpty()) {
front = newNode;
rear = newNode;
} else {
// we'll add this later!

}
return true;
}

Inserting an Item in a Non-Empty LLQueue


"hi" "how" "are" "you"

front
rear null
x "now"
item

newNode
null

public boolean insert(T item) {


// code to check for a null item goes here
Node newNode = new Node(item, null);
if (isEmpty()) {
front = newNode; A. rear = newNode;
rear = newNode; rear.next = newNode;
} else {
B. rear.next = newNode;
rear = newNode;
} C. either A or B
return true;
} D. neither A nor B

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 457


Removing from an LLQueue with One Item
removed "hi"

front
null
rear

public T remove() {
if (isEmpty()) {
return null;
}
T removed = _________________;
if (front == rear) { // removing the only item
front = null;
rear = null;
} else {
// we'll add this later
}
return removed;
}

Removing from an LLQueue with Two or More Items


removed "hi" "how" "are" "you"

front x
rear null

public T remove() {
if (isEmpty()) {
return null;
}
T removed = _________________;
if (front == rear) { // removing the only item
front = null;
rear = null;
} else {

}
return removed;
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 458


Efficiency of the Queue Implementations

ArrayQueue LLQueue
insert() O(1) O(1)
remove() O(1) O(1)
peek() O(1) O(1)
space O(m) where m is the O(n) where n is the number of
efficiency anticipated maximum number items currently in the queue
of items

Applications of Queues
• first-in first-out (FIFO) inventory control

• OS scheduling: processes, print jobs, packets, etc.

• simulations of banks, supermarkets, airports, etc.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 459


Unit 9, Part 1

Binary Trees and Huffman Encoding

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Motivation: Implementing a Dictionary


• A data dictionary is a collection of data with two main operations:
• search for an item (and possibly delete it)
• insert a new item

• If we use a sorted list to implement it, efficiency = O(n).


data structure searching for an item inserting an item
a list implemented using O(log n) O(n)
an array using binary search because we need to shift
items over
a list implemented using O(n) O(n)
a linked list using linear search (O(1) to do the actual
(binary search in a linked insertion, but O(n) to find
list is O(n log n)) where it belongs)

• In the next few lectures, we’ll look at how we can use a tree
for a data dictionary, and we'll try to get better efficiency.

• We’ll also look at other applications of trees.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 460


What Is a Tree?
root
node

edge

• A tree consists of:


• a set of nodes
• a set of edges, each of which connects a pair of nodes

• Each node may have one or more data items.


• each data item consists of one or more fields
• key field = the field used when searching for a data item
• data items with the same key are referred to as duplicates

• The node at the "top" of the tree is called the root of the tree.

Relationships Between Nodes


1

2 3 4 5 6

7 8 9 10 11 12

• If a node N is connected to nodes directly below it in the tree:


• N is referred to as their parent
• they are referred to as its children.
• example: node 5 is the parent of nodes 10, 11, and 12

• Each node is the child of at most one parent.

• Nodes with the same parent are siblings.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 461


Relationships Between Nodes (cont.)
1

2 3 4 5 6

7 8 9 10 11 12

• A node’s ancestors are its parent, its parent’s parent, etc.


• example: node 9’s ancestors are 3 and 1

• A node’s descendants are its children, their children, etc.


• example: node 1’s descendants are all of the other nodes

Types of Nodes
1

2 3 4 5 6

7 8 9 10 11 12

13

• A leaf node is a node without children.

• An interior node is a node with one or more children.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 462


A Tree is a Recursive Data Structure
1

2 3 4 5 6

7 8 9 10 11 12

13

• Each node in the tree is the root of a smaller tree!


• refer to such trees as subtrees to distinguish them from
the tree as a whole
• example: node 2 is the root of the subtree circled above
• example: node 6 is the root of a subtree with only one node

• We’ll see that tree algorithms often lend themselves to


recursive implementations.

Path, Depth, Level, and Height

level 0

level 1

depth = 2 level 2

• There is exactly one path (one sequence of edges) connecting


each node to the root.

• depth of a node = # of edges on the path from it to the root

• Nodes with the same depth form a level of the tree.

• The height of a tree is the maximum depth of its nodes.


• example: the tree above has a height of 2

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 463


Binary Trees
• In a binary tree, nodes have at most two children.
• distinguish between them using the direction left or right

• Example: 26

26’s left child 12 32 26’s right child

4 18 38

26’s left subtree 26’s right subtree


7 4’s right child

• Recursive definition: a binary tree is either:


1) empty, or
2) a node (the root of the tree) that has:
• one or more pieces of data (the key, and possibly others)
• a left subtree, which is itself a binary tree
• a right subtree, which is itself a binary tree

Which of the following is/are not true?


26

12 32

4 18 38

A. This tree has a height of 4.


B. There are 3 leaf nodes.
C. The 38 node is the right child of the 32 node.
D. The 12 node has 3 children.
E. more than one of the above are not true (which ones?)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 464


Representing a Binary Tree Using Linked Nodes
public class LinkedTree {
private class Node {
private int key; // limit ourselves to int keys
private LLList data; // list of data for that key
private Node left; // reference to left child
private Node right; // reference to right child

}
private Node root;

}

Representing a Binary Tree Using Linked Nodes


public class LinkedTree {
key
private class Node { (not showing
private int key; left right data field)
private LLList data;
private Node left;
private Node right; ref. to left child ref. to right child
… (null if none) (null if none)
}
private Node root; 26

}
root
26 12 32
LinkedTree
null
object
12 32
4 18 38
null null null null null
4 18 38

7
7 null null

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 465


Traversing a Binary Tree
• Traversing a tree involves visiting all of the nodes in the tree.
• visiting a node = processing its data in some way
• example: print the key

• We'll look at four types of traversals.


• each visits the nodes in a different order

• To understand traversals, it helps to remember that every node


is the root of a subtree.
26

12 is the root of 12 32 32 is the root of


26’s left subtree 26’s right subtree

4 is the root of 4 18 38
12’s left subtree

1: Preorder Traversal
• preorder traversal of the tree whose root is N:
1) visit the root, N
2) recursively perform a preorder traversal of N’s left subtree
3) recursively perform a preorder traversal of N’s right subtree

9 5

8 6 2

• preorder because a node is visited before its subtrees

• The root of the tree as a whole is visited first.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 466


Implementing Preorder Traversal
public class LinkedTree {
...
private Node root;
public void preorderPrint() {
if (root != null) {
preorderPrintTree(root);
}
System.out.println();
}
private static void preorderPrintTree(Node root) {
System.out.print(root.key + " ");
if (root.left != null) { Not always the
preorderPrintTree(root.left);
same as the root
}
if (root.right != null) { of the entire tree.
preorderPrintTree(root.right);
}
}
• preorderPrintTree() is a static, recursive method that takes
the root of the tree/subtree that you want to print.
• preorderPrint() is a non-static "wrapper" method that makes
the initial call. It passes in the root of the entire tree.

Tracing Preorder Traversal


void preorderPrintTree(Node root) {
7
System.out.print(root.key + " ");
if (root.left != null) {
preorderPrintTree(root.left); 9 5
}
if (root.right != null) {
preorderPrintTree(root.right); 8 6 2
}
} 4
base case, since
neither recursive
call is made! order in which nodes are visited:
we go back
root: 4 up the tree
print 4 by returning!
root: 8 root: 8 root: 8 root: 6
print 8 print 6
root: 9 root: 9 root: 9 root: 9 root: 9 root: 9
print 9 ...
root: 7 root: 7 root: 7 root: 7 root: 7 root: 7 root: 7
print 7
time

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 467


Using Recursion for Traversals
void preorderPrintTree(Node root) {
7
System.out.print(root.key + " ");
if (root.left != null) {
preorderPrintTree(root.left); 9 5
}
if (root.right != null) {
preorderPrintTree(root.right); 8 6 2
}
} 4
base case, since
neither recursive
call is made! order in which nodes are visited:
we go back
root: 4 up the tree
print 4 by returning!
root: 8 root: 8 root: 8 root: 6
print 8 print 6
root: 9
• Using print
recursion
9
root: 9
allows us to easily goroot:
root: 9
back9 root: 9
up the tree. root: 9
...
root: 7 root: 7 root: 7 root: 7 root: 7 root: 7 root: 7
• Using
print 7 a loop would be harder. Why?
time

2: Postorder Traversal
• postorder traversal of the tree whose root is N:
1) recursively perform a postorder traversal of N’s left subtree
2) recursively perform a postorder traversal of N’s right subtree
3) visit the root, N

9 5

8 6 2

• postorder because a node is visited after its subtrees

• The root of the tree as a whole is visited last.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 468


Implementing Postorder Traversal
public class LinkedTree {

private Node root;
public void postorderPrint() {
if (root != null) {
postorderPrintTree(root);
}
System.out.println();
}
private static void postorderPrintTree(Node root) {
if (root.left != null) {
postorderPrintTree(root.left);
}
if (root.right != null) {
postorderPrintTree(root.right);
}
System.out.print(root.key + " ");
}

• Note that the root is printed after the two recursive calls.

Tracing Postorder Traversal


void postorderPrintTree(Node root) {
7
if (root.left != null) {
postorderPrintTree(root.left);
} 9 5
if (root.right != null) {
postorderPrintTree(root.right);
} 8 6 2
System.out.print(root.key + " ");
} 4

order in which nodes are visited:


root: 4
print 4
root: 8 root: 8 root: 8 root: 6
print 8 print 6
root: 9 root: 9 root: 9 root: 9 root: 9 root: 9
...
root: 7 root: 7 root: 7 root: 7 root: 7 root: 7 root: 7

time

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 469


3: Inorder Traversal
• inorder traversal of the tree whose root is N:
1) recursively perform an inorder traversal of N’s left subtree
2) visit the root, N
3) recursively perform an inorder traversal of N’s right subtree

9 5

8 6 2

• The root of the tree as a whole is visited between its subtrees.

• We'll see later why this is called inorder traversal!

Implementing Inorder Traversal


public class LinkedTree {

private Node root;
public void inorderPrint() {
if (root != null) {
inorderPrintTree(root);
}
System.out.println();
}
private static void inorderPrintTree(Node root) {
if (root.left != null) {
inorderPrintTree(root.left);
}
System.out.print(root.key + " ");
if (root.right != null) {
inorderPrintTree(root.right);
}
}
}

• Note that the root is printed between the two recursive calls.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 470


Tracing Inorder Traversal
void inorderPrintTree(Node root) {
7
if (root.left != null) {
inorderPrintTree(root.left);
} 9 5
System.out.print(root.key + " ");
if (root.right != null) {
inorderPrintTree(root.right); 8 6 2
}
} 4

order in which nodes are visited:


root: 4
print 4
root: 8 root: 8 root: 8 root: 6
print 8 print 6
root: 9 root: 9 root: 9 root: 9 root: 9 root: 9
print 9 ...
root: 7 root: 7 root: 7 root: 7 root: 7 root: 7 root: 7

time

Level-Order Traversal
• Visit the nodes one level at a time, from top to bottom
and left to right.

9 5

8 6 2

• Level-order traversal of the tree above: 7 9 5 8 6 2 4

• We can implement this type of traversal using a queue.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 471


Tree-Traversal Summary
preorder: root, left subtree, right subtree
postorder: left subtree, right subtree, root
inorder: left subtree, root, right subtree
level-order: top to bottom, left to right
• Perform each type of traversal on the tree below:

15 7

23 8 10 5

12 6 35 26

Tree Traversal Puzzle


• preorder traversal: A M P K L D H T
• inorder traversal: P M L K A H T D
• Draw the tree!

• What's one fact that we can easily determine from one


of the traversals?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 472


Using a Binary Tree for an Algebraic Expression
• We’ll restrict ourselves to fully parenthesized expressions
using the following binary operators: +, –, *, /

• Example: ((a + (3 * c)) - (d / 2))

+ /

a * d 2

3 c

• Leaf nodes are variables or constants.

• Interior nodes are operators.


• their children are their operands

Traversing an Algebraic-Expression Tree



• Inorder gives conventional
algebraic notation. + /
• print ‘(’ before the recursive
call on the left subtree a * d e
• print ‘)’ after the recursive
b c
call on the right subtree
• for tree at right: ((a + (b * c)) - (d / e))

• Preorder gives functional notation.


• print ‘(’s and ‘)’s as for inorder, and commas after the
recursive call on the left subtree
• for tree above: subtr(add(a, mult(b, c)), divide(d, e))

• Postorder gives the order in which the computation must be


carried out on a stack/RPN calculator.
• for tree above: push a, push b, push c, multiply, add,…

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 473


Fixed-Length Character Encodings
• A character encoding maps each character to a number.

• Computers usually use fixed-length character encodings.


• ASCII - 8 bits per character
char dec binary
'a' 97 01100001 example: "bat" is stored in a text
'b' 98 01100010 file as the following sequence of bits:
01100010 01100001 01110100
… … …
't' 116 01110100

• Unicode - 16 bits per character


(allows for foreign-language characters; ASCII is a subset)

• Fixed-length encodings are simple, because:


• all encodings have the same length
• a given character always has the same encoding

A Problem with Fixed-Length Encodings


• They tend to waste space.

• Example: an English newspaper article with only:


• upper and lower-case letters (52 characters)
• spaces and newlines (2 characters)
• common punctuation (approx. 10 characters)
• total of 64 unique characters  only need ___ bits

• We could gain even more space if we:


• gave the most common letters shorter encodings (3 or 4 bits)
• gave less frequent letters longer encodings (> 6 bits)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 474


Variable-Length Character Encodings
• Variable-length encodings compress a text file by:
• using encodings of different lengths for different characters
• assigning shorter encodings to frequently occurring characters

• Example: if we had only four characters


e 01
"test" would be encoded as
o 100 00 01 111 00  000111100
s 111
t 00

• Challenge: when reading a document, how do we determine


the boundaries between characters?
• how do we know how many bits the next character has?

• One requirement: no character's encoding can be the prefix of


another character's encoding (e.g., couldn't have 00 and 001).

Huffman Encoding
• One type of variable-length encoding

• Based on the actual character frequencies in a given document


• different documents have different encodings

• Huffman encoding uses a binary tree:


• to determine the encoding of each character
• to decode / decompress an encoded file
• putting it back into ASCII

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 475


Huffman Trees
• Example for a text with
only six characters: 0 1

0 1 0 1

t e
0 1 0 1

o i a s

• Left branches are labeled with a 0, right branches with a 1.

• Leaf nodes are characters.

• To get a character's encoding, follow the path from the root


to its leaf node.
• example: i = ?

Building a Huffman Tree


1) Begin by reading through the text to determine the frequencies.
2) Create a list of nodes containing (character, frequency) pairs
for each character in the text – sorted by frequency.
'o' 'i' 'a' 's' 't' 'e'
11 23 25 26 27 40 means
null

3) Remove and "merge" the nodes with -


the two lowest frequencies, forming a 34
new node that is their parent.
• left child = lowest frequency node
• right child = the other node 'o' 'i'
• frequency of parent = sum of the 11 23
frequencies of its children
• in this case, 11 + 23 = 34

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 476


Building a Huffman Tree (cont.)
4) Add the parent to the list of nodes (maintaining sorted order):
'a' 's' 't' - 'e'
25 26 27 34 40

'o' 'i'
11 23

5) Repeat steps 3 and 4 until there is only a single node in the list,
which will be the root of the Huffman tree.

Completing the Huffman Tree Example I


• Merge the two remaining nodes with the lowest frequencies:
'a' 's' 't' - 'e'
25 26 27 34 40

'o' 'i'
11 23

't' - 'e' -
27 34 40 51

'o' 'i' 'a' 's'


11 23 25 26

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 477


Completing the Huffman Tree Example II
• Merge the next two nodes:

Completing the Huffman Tree Example II


• Merge again:

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 478


Completing the Huffman Tree Example IV
• The next merge creates the final tree:

0 1

0 1 0 1

t e
0 1 0 1
o i a s

• Characters that appear more frequently end up higher in the tree,


and thus their encodings are shorter.

The Shape of the Huffman Tree


• The tree on the last slide is fairly symmetric.

• This won't always be the case!


• depends on the character frequencies

• For example, changing the frequency of 'o' from 11 to 21


would produce the tree shown below:

0 1

0 1 0 1

t e
0 1 0 1

o i a s

• This is the tree that we'll use in the remaining slides.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 479


Huffman Encoding: Compressing a File
1) Read through the input file and build its Huffman tree.
2) Write a file header for the output file.
• include the character frequencies so the tree can be rebuilt
when the file is decompressed
3) Traverse the Huffman tree to create a table containing the
encoding of each character:
0 1 a
e

0 1 i
0 1
o
t e
0 1 s
0 1
a s t
o i
4) Read through the input file a second time, and write the
Huffman code for each character to the output file.

Huffman Decoding: Decompressing a File


1) Read the frequency table from the header and rebuild the tree.
2) Read one bit at a time and traverse the tree, starting from the root:
when you read a bit of 1, go to the right child
when you read a bit of 0, go to the left child
when you reach a leaf node, record the character,
return to the root, and continue reading bits
The tree allows us to easily overcome the challenge of
determining the character boundaries!
example: 101111110000111100
0 1
first character = i

0 1 0 1

t e
0 1 0 1

o i a s

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 480


What are the next three characters?
1) Read the frequency table from the header and rebuild the tree.
2) Read one bit at a time and traverse the tree, starting from the root:
when you read a bit of 1, go to the right child
when you read a bit of 0, go to the left child
when you reach a leaf node, record the character,
return to the root, and continue reading bits
The tree allows us to easily overcome the challenge of
determining the character boundaries!
example: 101111110000111100
0 1
first character = i (101)

0 1 0 1

t e
0 1 0 1

o i a s

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 481


Huffman Decoding: Decompressing a File
1) Read the frequency table from the header and rebuild the tree.
2) Read one bit at a time and traverse the tree, starting from the root:
when you read a bit of 1, go to the right child
when you read a bit of 0, go to the left child
when you reach a leaf node, record the character,
return to the root, and continue reading bits
The tree allows us to easily overcome the challenge of
determining the character boundaries!
example: 101111110000111100
0 1
101 = right,left,right = i
111 = right,right,right= s
0 1 0 1 110 = right,right,left = a
00 = left,left = t
t e 01 = left,right = e
0 1 0 1 111 = right,right,right= s
o i a s 00 = left,left = t

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 482


Unit 9, Part 2

Search Trees

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Binary Search Trees


• Search-tree property: for each node k (k is the key):
k
• all nodes in k’s left subtree are < k
• all nodes in k’s right subtree are >= k

• Our earlier binary-tree example is <k k

a search tree:
26

< 26 12 32  26
 12
4 18 38

< 12
7

• With a search tree, an inorder traversal visits the nodes in order!


• in order of increasing key values

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 483


Searching for an Item in a Binary Search Tree
• Algorithm for searching for an item with a key k:
if k == the root node’s key, you’re done
else if k < the root node’s key, search the left subtree
else search the right subtree

• Example: search for 7

26

12 32

4 18 38

Implementing Binary-Tree Search


public class LinkedTree { // Nodes have keys that are ints

private Node root;
public LLList search(int key) { // "wrapper method"
Node n = searchTree(root, key); // get Node for key
if (n == null) {
return null; // no such key
} else {
return n.data; // return list of values for key
}
}
private static Node searchTree(Node root, int key) {
if ( ) {

} else if ( ) { two base cases


(order matters!)
} else if ( ) {
two
} else {
recursive cases
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 484


Inserting an Item in a Binary Search Tree
• public void insert(int key, Object data)
will add a new (key, data) pair to the tree

• Example 1: a search tree containing student records


• key = the student's ID number (an integer)
• data = a string with the rest of the student record
• we want to be able to write client code that looks like this:
LinkedTree students = new LinkedTree();
students.insert(23, "Jill Jones,sophomore,comp sci");
students.insert(45, "Al Zhang,junior,english");

• Example 2: a search tree containing scrabble words


• key = a scrabble score (an integer)
• data = a word with that scrabble score
LinkedTree tree = new LinkedTree();
tree.insert(4, "lost");

Inserting an Item in a Binary Search Tree (cont.)


• To insert an item (k, d), example:
tree.insert(35,
we start by searching for k. "photooxidizes")

• If we find a node with key k, we add 26


d to the list of data values for that node.
12 32
• example: tree.insert(4, "sail")

• If we don’t find k, the last node seen 4 18 38 P


in the search becomes the parent P
of the new node N. 7 35 N
• if k < P’s key, make N the left child of P
• else make N the right child of P

• Special case: if the tree is empty,


make the new node the root of the tree.

• Important: The resulting tree is still a search tree!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 485


Implementing Binary-Tree Insertion
• We'll implement part of the insert() method together.

• We'll use iteration rather than recursion.

• Our method will use two references/pointers: parent


• trav: performs the traversal down
to the point of insertion 26
trav
• parent: stays one behind trav
• like the trail reference that we 12 32
sometimes use when traversing
a linked list 4 18 38

Implementing Binary-Tree Insertion


parent
public void insert(int key, Object data) { insert 35:
Node parent = null; trav
Node trav = root; 26
while (trav != null) {
if (trav.key == key) {
trav.data.addItem(data, 0); 12 32
return;
}
// what should go here? 4 18 38

}
Node newNode = new Node(key, data);
if (root == null) { // the tree was empty
root = newNode;
} else if (key < parent.key) {
parent.left = newNode;
} else {
parent.right = newNode;
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 486


Deleting Items from a Binary Search Tree
• Three cases for deleting a node x
• Case 1: x has no children.
Remove x from the tree by setting its parent’s reference to null.
26 26

ex: delete 4 12 32 12 32

4 18 38 18 38

• Case 2: x has one child.


Take the parent’s reference to x and make it refer to x’s child.
26 26

ex: delete 12 12 32 18 32

18 38 38

Deleting Items from a Binary Search Tree (cont.)


• Case 3: x has two children
• we can't give both children to the parent. why?

• instead, we leave x's node where it is, and we replace its


key and data with those from another node
• the replacement must maintain the search-tree inequalities
ex: 26 two options: which ones?
delete 12
12 32

4 18 38

7 20

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 487


Deleting Items from a Binary Search Tree (cont.)
• Case 3: x has two children (continued):
• replace x's key and data with those from the smallest node
in x’s right subtree—call it y
• we then delete y
• it will either be a leaf node or will have one right child. why?

• thus, we can delete it using case 1 or 2

ex:
delete 12 12 x copy node y's 18 x 18 x
contents into delete
node x node y
4 18 y 4 18 y 4 20

7 20 7 20 7

Which Nodes Could We Use To Replace 9?


9

4 17

3 8 10 25

1 5 20 36

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 488


Implementing Deletion delete 26:
50
public LLList delete(int key) { parent
// Find the node and its parent. trav
Node parent = null; 15
Node trav = root;
26
while (trav != null && trav.key != key) {
parent = trav;
if (key < trav.key) { 18 45
trav = trav.left;
} else {
trav = trav.right; 30
} 35
}
// Delete the node (if any) and return the removed items.
if (trav == null) { // no such key
return null;
} else {
LLList removedData = trav.data;
deleteNode(trav, parent); // call helper method
return removedData;
}
}

Implementing Case 3
private void deleteNode(Node toDelete, Node parent) {
if (toDelete.left != null && toDelete.right != null) {
// Find a replacement – and
// the replacement's parent. toDelete
Node replaceParent = toDelete;
// Get the smallest item
26
// in the right subtree.
Node replace = toDelete.right;
// what should go here? 18 45

30
// Replace toDelete's key and data
// with those of the replacement item. 35
toDelete.key = replace.key;
toDelete.data = replace.data;
// Recursively delete the replacement
// item's old node. It has at most one
// child, so we don't have to
// worry about infinite recursion.
deleteNode(replace, replaceParent);
} else {
...
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 489


Implementing Cases 1 and 2
private void deleteNode(Node toDelete, Node parent) {
if (toDelete.left != null && toDelete.right != null) {
...
} else {
Node toDeleteChild;
if (toDelete.left != null)
toDeleteChild = toDelete.left; 30 parent
else
toDeleteChild = toDelete.right;
// Note: in case 1, toDeleteChild 18 45
// will have a value of null. toDelete
if (toDelete == root) 30
root = toDeleteChild;
else if (toDelete.key < parent.key) 35
parent.left = toDeleteChild;
else
parent.right = toDeleteChild; toDeleteChild
}
}

Recall: Path, Depth, Level, and Height

level 0

level 1

depth = 2 level 2

• There is exactly one path (one sequence of edges) connecting


each node to the root.

• depth of a node = # of edges on the path from it to the root

• Nodes with the same depth form a level of the tree.

• The height of a tree is the maximum depth of its nodes.


• example: the tree above has a height of 2

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 490


Efficiency of a Binary Search Tree
• For a tree containing n items, what is the efficiency
of any of the traversal algorithms?
• you process all n of the nodes
• you perform O(1) operations on each of them

• Search, insert, and delete all have the same time complexity.
• insert is a search followed by O(1) operations
• delete involves either:
• a search followed by O(1) operations (cases 1 and 2)
• a search partway down the tree for the item,
followed by a search further down for its replacement,
followed by O(1) operations (case 3)

Efficiency of a Binary Search Tree (cont.)


• Time complexity of searching:
• best case:

• worst case:
• you have to go all the way down to level h
before finding the key or realizing it isn't there
• along the path to level h, you process h + 1 nodes
• average case:

• What is the height of a tree containing n items?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 491


Balanced Trees
• A tree is balanced if, for each of its nodes, the node’s subtrees
have the same height or have heights that differ by 1.
• example: 26
• 26: both subtrees have a height of 1
• 12: left subtree has height 0 12 32
right subtree is empty (height = -1)
• 32: both subtrees have a height of 0 4 30 38
• all leaf nodes: both subtrees are empty

• For a balanced tree with n nodes, height = O(log n)


• each time that you follow an edge down the longest path,
you cut the problem size roughly in half!

• Therefore, for a balanced binary search tree, the worst case


for search / insert / delete is O(h) = O(log n)
• the "best" worst-case time complexity

What If the Tree Isn't Balanced?


• Extreme case: the tree is equivalent to a linked list
• height = n - 1
4

• Therefore, for a unbalanced


12
binary search tree, the worst case
for search / insert / delete is O(h) = O(n) 26
• the "worst" worst-case time complexity
32
• We’ll look next at search-tree variants
that take special measures to ensure balance. 36

38

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 492


2-3 Trees
• A 2-3 tree is a balanced tree in which:
• all nodes have equal-height subtrees (perfect balance)
• each node is either
• a 2-node, which contains one data item and 0 or 2 children
• a 3-node, which contains two data items and 0 or 3 children
• the keys form a search tree
• Example: 28 61

10 40 77 90

3 14 20 34 51 68 80 87 93 97

2-node: k 3-node: k1 k2

 k1
<k k <k1 <k2  k2

Search in 2-3 Trees


• Algorithm for searching for an item with a key k: k1 k2

if k == one of the root node’s keys, you’re done


else if k < the root node’s first key  k1
<k1 <k2  k2
search the left subtree
else if the root is a 3-node and k < its second key
search the middle subtree
else
search the right subtree

• Example: search for 87


28 61

10 40 77 90

3 14 20 34 51 68 80 87 93 97

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 493


Insertion in 2-3 Trees
• Algorithm for inserting an item with a key k:
search for k, but don’t stop until you hit a leaf node
let L be the leaf node at the end of the search
if L is a 2-node 10 10
add k to L, making it a 3-node
3 20 3 14 20
else if L is a 3-node
split L into two 2-nodes containing the items with the
smallest and largest of: k, L’s 1st key, L’s 2nd key
the middle item is “sent up” and inserted in L’s parent
example: add 52

50 50 50 54
… … …
54 70 52 54 70 52 70

Example 1: Insert 8
• Search for 8:
28 61

10 40 77 90

3 14 20 34 51 68 80 87 93 97

• Add 8 to the leaf node, making it a 3-node:


28 61

10 40 77 90

3 8 14 20 34 51 68 80 87 93 97

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 494


Example 2: Insert 17
• Search for 17:
28 61

10 40 77 90

3 14 20 34 51 68 80 87 93 97

• Split the leaf node, and send up the middle of 14, 17, 20
and insert it the leaf node’s parent:

28 61 28 61

… 10 17 40

10 40
17
3 14 20 34 51 3 14 20 34 51

Example 3: Insert 92
28 61

10 40 77 90

3 14 20 34 51 68 80 87 93 97

• In which node will we initially try to insert it?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 495


Example 3: Insert 92
• Search for 92:
28 61

10 40 77 90

3 14 20 34 51 68 80 87 93 97

• Split the leaf node, and send up the middle of 92, 93, 97
and insert it the leaf node’s parent:

28 61 28 61

… …
40 77 90 40 77 90 93

34 51 68 80 87 92 93 97 34 51 68 80 87 92 97

• In this case, the leaf node’s parent is also a 3-node, so we


need to split is as well…

Example 3 (cont.)
• We split the [77 90] node and we send up the middle of 77, 90, 93:

• We try to insert it in the root node, but the root is also full!

28 61 28 61 90

… …
40 77 90 93 40 77 93

34 51 68 80 87 92 97 34 51 68 80 87 92 97

• Then we split the root,


61
which increases the
tree’s height by 1, but 28 90
the tree is still balanced. …
40 77 93
• This is only case in which
the tree’s height increases. 34 51 68 80 87 92 97

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 496


Efficiency of 2-3 Trees

• A 2-3 tree containing n items has a height h <= log2n.

• Thus, search and insertion are both O(log n).


• search visits at most h + 1 nodes
• insertion visits at most 2h + 1 nodes:
• starts by going down the full height
• in the worst case, performs splits all the way back up to the root

• Deletion is tricky – you may need to coalesce nodes!


However, it also has a time complexity of O(log n).

• Thus, we can use 2-3 trees for a O(log n)-time data dictionary!

External Storage
• The balanced trees that we've covered don't work well if you
want to store the data dictionary externally – i.e., on disk.

• Key facts about disks:


• data is transferred to and from disk in units called blocks,
which are typically 4 or 8 KB in size
• disk accesses are slow!
• reading a block takes ~10 milliseconds (10-3 sec)
• vs. reading from memory, which takes ~10 nanoseconds
• in 10 ms, a modern CPU can perform millions of operations!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 497


B-Trees
• A B-tree of order m is a tree in which each node has:
• at most 2m entries (and, for internal nodes, 2m + 1 children)
• at least m entries (and, for internal nodes, m + 1 children)
• exception: the root node may have as few as 1 entry
• a 2-3 tree is essentially a B-tree of order 1

• To minimize the number of disk accesses, we make m


as large as possible.
• each disk read brings in more items
• the tree will be shorter (each level has more nodes),
and thus searching for an item requires fewer disk reads

• A large value of m doesn’t make sense for a memory-only tree,


because it leads to many key comparisons per node.

• These comparisons are less expensive than accessing the disk,


so large values of m make sense for on-disk trees.

Example: a B-Tree of Order 2


20 40 68 90

3 10 14 28 34 51 61 77 80 87 93 97

• m = 2: at most 2m = 4 items per node (and at most 5 children)


at least m = 2 items per node (and at least 3 children)
(except the root, which could have 1 item)
• The above tree holds the same keys this 2-3 tree:
28 61

10 40 77 90

3 14 20 34 51 68 80 87 93 97

• We used the same order of insertion to create both trees:


51, 3, 40, 77, 20, 10, 34, 28, 61, 80, 68, 93, 90, 97, 87, 14

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 498


Search in B-Trees
• Similar to search in a 2-3 tree.

• Example: search for 87


20 40 68 90

3 10 14 28 34 51 61 77 80 87 93 97

Insertion in B-Trees
• Similar to insertion in a 2-3 tree:
search for the key until you reach a leaf node
if a leaf node has fewer than 2m items, add the item
to the leaf node
else split the node, dividing up the 2m + 1 items:
the smallest m items remain in the original node
the largest m items go in a new node
send the middle entry up and insert it (and a pointer to
the new node) in the parent

• Example of an insertion without a split: insert 13


20 40 68 90 20 40 68 90
… … … …
3 10 14 28 34 51 61 3 10 13 14 28 34 51 61

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 499


Splits in B-Trees
• Insert 5 into the result of the previous insertion:
m=2
20 40 68 90 10 20 40 68 90
… … … …
3 5 10 13 14 28 34 51 61 3 5 13 14 28 34 51 61

• The middle item (the 10) is sent up to the root.


The root has no room, so it is also split, and a new root is formed:
40

10 20 40 68 90 10 20 68 90
… … … …
3 5 13 14 28 34 51 61 3 5 13 14 28 34 51 61

• Splitting the root increases the tree’s height by 1, but the tree
is still balanced. This is only way that the tree’s height increases.

• When an internal node is split, its 2m + 2 pointers are split evenly


between the original node and the new node.

Analysis of B-Trees
20 40 68 90

3 10 14 28 34 51 61 77 80 87 93 97

• All internal nodes have at least m children (actually, at least m+1).

• Thus, a B-tree with n items has a height <= logmn, and


search and insertion are both O(logmn).

• As with 2-3 trees, deletion is tricky, but it’s still logarithmic.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 500


Search Trees: Conclusions
• Binary search trees can be O(log n), but they can degenerate
to O(n) running time if they are out of balance.

• 2-3 trees and B-trees are balanced search trees that


guarantee O(log n) performance.

• When data is stored on disk, the most important performance


consideration is reducing the number of disk accesses.

• B-trees offer improved performance for on-disk data dictionaries.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 501


Unit 9, Part 3

Heaps and Priority Queues

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Priority Queue
• A priority queue (PQ) is a collection in which each item
has an associated number known as a priority.
• ("Ann Cudd", 10), ("Robert Brown", 15),
("Dave Sullivan", 5)
• use a higher priority for items that are "more important"

• Example application: scheduling a shared resource like the CPU


• give some processes/applications a higher priority,
so that they will be scheduled first and/or more often

• Key operations:
• insert: add an item (with a position based on its priority)
• remove: remove the item with the highest priority

• One way to implement a PQ efficiently is using a type of


binary tree known as a heap.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 502


Complete Binary Trees
• A binary tree of height h is complete if:
• levels 0 through h - 1 are fully occupied
• there are no “gaps” to the left of a node in level h

• Complete:

• Not complete ( = missing node):

Representing a Complete Binary Tree


• A complete binary tree has a simple array representation.
a[0]
• The tree's nodes are stored in the array
in the order given by a level-order traversal.
a[1] a[2]
• top to bottom, left to right
a[3] a[4] …
• Examples:

26 10 8 17 14 3

12 32
10
4 18 28
8 17

26 12 32 4 18 28 14 3

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 503


Navigating a Complete Binary Tree in Array Form
• The root node is in a[0]
a[0]
• Given the node in a[i]:
• its left child is in a[2*i + 1] a[1] a[2]

• its right child is in a[2*i + 2]


a[3] a[4] …
a[5] a[6]
• its parent is in a[(i - 1)/2]
(using integer division)
a[7] a[8]
• Examples:
• the left child of the node in a[1] is in a[2*1 + 1] = a[3]
• the left child of the node in a[2] is in a[2*2 + 1] = a[5]
• the right child of the node in a[3] is in a[2*3 + 2] = a[8]
• the right child of the node in a[2] is in _________________
• the parent of the node in a[4] is in a[(4 - 1)/2] = a[1]
• the parent of the node in a[7] is in ___________________

What is the left child of 24?


• Assume that the following array represents a complete tree:
0 1 2 3 4 5 6 7 8
26 12 32 24 18 28 47 10 9

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 504


Heaps
• Heap: a complete binary tree in which each interior node
is greater than or equal to its children
• examples:

28 18 12

16 20 8 2 7 10

12 8 5 3 7

• The largest value is always at the root of the tree.

• The smallest value can be in any leaf node - there’s no


guarantee about which one it will be.

• We're using max-at-top heaps.


• in a min-at-top heap, every interior node <= its children

Which of these is a heap?


• A. 28 B. 18 C. 12

16 20 8 2 7 10

12 18 5 3 7 2 5

D. more than one (which ones?)

E. none of them

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 505


Removing the Largest Item from a Heap
• Remove and return the item in the root node.
• In addition, need to move the largest remaining item to the root,
while maintaining a complete tree with each node >= children
• Algorithm:
1. make a copy of the largest item 28
2. move the last item in the heap
to the root 20 12
3. “sift down” the new root item
until it is >= its children (or it’s a leaf) 16 8 5
4. return the largest item

sift down 5 20 20
the 5:
20 12 5 12 16 12

16 8 16 8 5 8

Sifting Down an Item


• To sift down item x (i.e., the item whose key is x):
1. compare x with the larger of the item’s children, y
2. if x < y, swap x and y and repeat
• Other examples:
sift down
the 10: 10 18

7 18 7 10

3 5 8 6 3 5 8 6

sift down
7
the 7:

26 23

15 18 10

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 506


Inserting an Item in a Heap
• Algorithm:
1. put the item in the next available slot (grow array if needed)
2. “sift up” the new item
until it is <= its parent (or it becomes the root item)
• Example: insert 35
put it in 20 20
place:

16 12 16 12

5 8 5 8 35

sift it up: 20 20 35

16 12 16 35 16 20

5 8 35 5 8 12 5 8 12

Time Complexity of a Heap


5

16 8

14 20 1 26

• A heap containing n items has a height <= log2n. Why?

• Thus, removal and insertion are both O(log n).


• remove: go down at most log2n levels when sifting down;
do a constant number of operations per level
• insert: go up at most log2n levels when sifting up;
do a constant number of operations per level

• This means we can use a heap for a O(log n)-time priority queue.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 507


Using a Heap for a Priority Queue
• Recall: a priority queue (PQ) is a collection in which each item
has an associated number known as a priority.
• ("Ann Cudd", 10), ("Robert Brown", 15),
("Dave Sullivan", 5)
• use a higher priority for items that are "more important"

• To implement a PQ using a heap:


• order the items in the heap according to their priorities
• every item in the heap will have a priority >= its children
• the highest priority item will be in the root node
• get the highest priority item by calling heap.remove()!

Using a Heap to Sort an Array


• Recall selection sort: it repeatedly finds the smallest remaining
element and swaps it into place:
0 1 2 3 4 5 6
5 16 8 14 20 1 26
0 1 2 3 4 5 6
1 16 8 14 20 5 26
0 1 2 3 4 5 6
1 5 8 14 20 16 26

• It isn’t efficient, because it performs a linear scan to
find the smallest remaining element (O(n) steps per scan).

• Heapsort is a sorting algorithm that repeatedly finds the largest


remaining element and puts it in place.

• It is efficient, because it turns the array into a heap.


• it can find/remove the largest remaining in O(log n) steps!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 508


Converting an Arbitrary Array to a Heap
• To convert an array (call it contents) with n items to a heap:
1. start with the parent of the last element:
contents[i], where i = ((n – 1) – 1)/2 = (n – 2)/2
2. sift down contents[i] and all elements to its left
• Example: 0 1 2 3 4 5 6 5
5 16 8 14 20 1 26
16 8

14 20 1 26
• Last element’s parent = contents[(7 – 2)/2] = contents[2].
Sift it down:
5 5

16 8 16 26

14 20 1 26 14 20 1 8

Converting an Array to a Heap (cont.)


• Next, sift down contents[1]:
5 5

16 26 20 26

14 20 1 8 14 16 1 8

• Finally, sift down contents[0]:


5 26 26

20 26 20 5 20 8

14 16 1 8 14 16 1 8 14 16 1 5

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 509


Heapsort
• Pseudocode:
heapSort(arr) {
// Turn the array into a max-at-top heap.
heap = new Heap(arr);
endUnsorted = arr.length - 1;
while (endUnsorted > 0) {
// Get the largest remaining element and put it
// at the end of the unsorted portion of the array.
largestRemaining = heap.remove();
arr[endUnsorted] = largestRemaining;
endUnsorted--;
}
}

Heapsort Example
0 1 2 3 4 5 6
• Sort the following array: 13 6 45 10 3 22 5

• Here’s the corresponding complete tree:


13

6 45

10 3 22 5

• Begin by converting it to a heap:

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 510


Heapsort Example (cont.)
• Here’s the heap in both tree and array forms:
45 0 1 2 3 4 5 6
45 10 22 6 3 13 5
10 22 endUnsorted: 6

6 3 13 5

• We begin looping:
while (endUnsorted > 0) {
// Get the largest remaining element and put it
// at the end of the unsorted portion of the array.
largestRemaining = heap.remove();
arr[endUnsorted] = largestRemaining;
endUnsorted--;
}

Heapsort Example (cont.)


• Here’s the heap in both tree and array forms:
45 0 1 2 3 4 5 6
45 10 22 6 3 13 5
10 22 endUnsorted: 6

6 3 13 5

• Remove the largest item and put it in place:


remove()
copies 45; 5
45 remove() 22 heapSort() puts 45 in place; 22
moves 5 sifts down 5; decrements endUnsorted
to root returns 45
10 22 10 13 10 13

6 3 13 5 6 3 5 6 3 5
0 1 2 3 4 5 6 0 1 2 3 4 5 6
toRemove: 45 22 10 13 6 3 5 5 22 10 13 6 3 5 45
endUnsorted: 6 endUnsorted: 5
largestRemaining: 45

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 511


Heapsort Example (cont.)
copy 22; 5
22 13 put 22 in place; 13
move 5 sift down 5;
return 22 decrement endUnsorted
to root
10 13 10 5 10 5

6 3 5 6 3 6 3
0 1 2 3 4 5 6 0 1 2 3 4 5 6
toRemove: 22 13 10 5 6 3 5 45 13 10 5 6 3 22 45
endUnsorted: 5 endUnsorted: 4
largestRemaining: 22

copy 13; 3
13 10 10
move 3 sift down 3; put 13 in place;
to root return 13 decrement
10 5 6 5 6 5

6 3 3 3
0 1 2 3 4 5 6 0 1 2 3 4 5 6
toRemove: 13 10 6 53 3 22 45 10 6 5
3 13 22 45
endUnsorted: 4 endUnsorted: 3
largestRemaining: 13

Heapsort Example (cont.)


copy 10; 3
10 6 6
move 3 sift down 3; put 10 in place;
to root return 10 decrement
6 5 3 5 3 5

3
0 1 2 3 4 5 6 0 1 2 3 4 5 6
toRemove: 10 6 3 53 13 22 45 6 3
5 10 13 22 45
endUnsorted: 3 endUnsorted: 2
largestRemaining: 10

copy 6; 65 5 5
move 5 sift down 5; put 6 in place;
to root return 6 decrement
3 5 3 3

0 1 2 3 4 5 6 0 1 2 3 4 5 6
toRemove: 6 5 3 5 10 13 22 45 5 3
6 10 13 22 45
endUnsorted: 2 endUnsorted: 1
largestRemaining: 6

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 512


Heapsort Example (cont.)
copy 5; 3
5 3 3
move 3 sift down 3; put 5 in place;
to root return 5 decrement
3

0 1 2 3 4 5 6 0 1 2 3 4 5 6
toRemove: 5 3 3 6 10 13 22 45 3 5
6 10 13 22 45
endUnsorted: 1 endUnsorted: 0
largestRemaining: 5

• And now we terminate the loop:


while (endUnsorted > 0) {
// Get the largest remaining element and put it
// at the end of the unsorted portion of the array.
largestRemaining = heap.remove();
arr[endUnsorted] = largestRemaining;
endUnsorted--;
}

Time Complexity of Heapsort


5

16 8

14 20 1 26

• Time complexity of creating a heap from an array?

• Time complexity of sorting the array?

• Thus, total time complexity = ?

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 513


How Does Heapsort Compare?
algorithm best case avg case worst case extra
memory
selection sort O(n2) O(n2) O(n2) O(1)
insertion sort O(n) O(n2) O(n2) O(1)
Shell sort O(n log n) O(n1.5) O(n1.5) O(1)
bubble sort O (n2) O (n2) O (n2) O(1)
quicksort O(n log n) O(n log n) O (n2) O(log n)
worst: O(n)
mergesort O(n log n) O(n log n) O(nlog n) O(n)
heapsort O(n log n) O(n log n) O(nlog n) O(1)
• Heapsort matches mergesort for the best worst-case time
complexity, but it has better space complexity.
• Insertion sort is still best for arrays that are almost sorted.
• heapsort will scramble an almost sorted array before sorting it!
• Quicksort is still typically fastest in the average case.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 514


Unit 9, Part 4

Hash Tables

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

Data Dictionary Revisited


• We've considered several data structures that allow us to store
and search for data items using their key fields:
data structure searching for an item inserting an item
a list implemented using O(log n) O(n)
an array using binary search
a list implemented using O(n) O(n)
a linked list using linear search
binary search tree

balanced search trees


(2-3 tree, B-tree, others)

• We'll now look at hash tables, which can do better than O(log n).

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 515


Ideal Case: Searching = Indexing
• We would achieve optimal efficiency if we could treat
the key as an index into an array.

• Example: storing data about members of a sports team


• key = jersey number (some value from 0-99).
• class for an individual player's record:
public class Player {
private int jerseyNum;
private String firstName;

}
• store the player records in an array:
Player[] teamRecords = new Player[100];

• In such cases, search and insertion are O(1):


public Player search(int jerseyNum) {
return teamRecords[jerseyNum];
}

Hashing: Turning Keys into Array Indices


• In most real-world problems, indexing is not as simple as
the sports-team example. Why?


• To handle these problems, we perform hashing:


• use a hash function to convert the keys into array indices
"Sullivan"  18
• use techniques to handle cases in which multiple keys
are assigned the same hash value

• The resulting data structure is known as a hash table.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 516


Hash Functions
• A hash function defines a mapping from keys to integers.

• We then use the modulus operator to get a valid array index.


hash %
key value integer integer in [0, n – 1]
function
(n = array length)

• Here's a very simple hash function for keys of lower-case letters:


h(key) = ASCII value of first char – ASCII value of 'a'
• examples:
h("ant") = ASCII for 'a' – ASCII for 'a' = 0
h("cat") = ASCII for 'c' – ASCII for 'a' = 2

• h(key) is known as the key's hash code.

• A collision occurs when items with different keys are assigned


the same hash code.

Dealing with Collisions I: Separate Chaining


• Each position in the hash table serves as a bucket that can
store multiple data items.

• Two options:
1. each bucket is itself an array
• need to preallocate, and a bucket may become full
2. each bucket is a linked list
• items with the same hash code are "chained" together
• each "chain" can grow as needed

0 "ant" "ape"
1 null null

2 "cat"
3 null null

… ...

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 517


Dealing with Collisions II: Open Addressing
• When the position assigned by the hash function is occupied,
find another open position.

• Example: "wasp" has a hash code of 22,


but it ends up in position 23 because 0 "ant"
position 22 is occupied. 1
2 "cat"
• We'll consider three ways of finding an 3
open position – a process known as probing. 4 "emu"
5
• We also perform probing when searching. 6
• example: search for "wasp" 7
• look in position 22 … ...

• then look in position 23 22 "wolf"


23 "wasp"
• need to figure out when to safely stop
24 "yak"
searching (more on this soon!)
25 "zebra"

Linear Probing
• Probe sequence: h(key), h(key) + 1, h(key) + 2, …,
wrapping around as necessary.

• Examples:
• "ape" (h = 0) would be placed in position 1, 0 "ant"
because position 0 is already full. 1 "ape"
• "bear" (h = 1): try 1, 1 + 1, 1 + 2 – open! 2 "cat"
• where would "zebu" end up? 3 "bear"
4 "emu"
5
• Advantage: if there is an open cell,
6
linear probing will eventually find it.
7
… ...
• Disadvantage: get "clusters" of occupied cells
22 "wolf"
that lead to longer subsequent probes.
23 "wasp"
• probe length = the number of positions 24 "yak"
considered during a probe 25 "zebra"

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 518


Quadratic Probing
• Probe sequence: h(key), h(key) + 12, h(key) + 22, h(key) + 32, …,
wrapping around as necessary.

• Examples:
• "ape" (h = 0): try 0, 0 + 1 – open! 0 "ant"
• "bear" (h = 1): try 1, 1 + 1, 1 + 4 – open! 1 "ape"
• "zebu"? 2 "cat"
3
• Advantage: smaller clusters of occupied cells 4 "emu"
5 "bear"
• Disadvantage: may fail to find an existing 6
open position. For example: 7
table size = 10 … ...
x = occupied 0 x 5 x 25
1 x 1 81 6 x 16 36 22 "wolf"
trying to insert a
key with h(key) = 0 2 7 23 "wasp"
3 8 24 "yak"
offsets of the probe
sequence in italics 4 x 4 64 9 x 9 49 25 "zebra"

Double Hashing
• Use two hash functions:
• h1 computes the hash code
• h2 computes the increment for probing
• probe sequence: h1, h1 + h2, h1 + 2*h2, …
0 "ant"
1 "bear"
• Examples:
2 "cat"
• h1 = our previous h
3 "ape"
• h2 = number of characters in the string
4 "emu"
• "ape" (h1 = 0, h2 = 3): try 0, 0 + 3 – open!
5
• "bear" (h1 = 1, h2 = 4): try 1 – open!
6
• "zebu"?
7
… ...
• Combines good features of linear and quadratic:
22 "wolf"
• reduces clustering
23 "wasp"
• will find an open position if there is one, 24 "yak"
provided the table size is a prime number 25 "zebra"

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 519


Removing Items Under Open Addressing
• Problematic example (using linear probing): 0 "ant"
• insert "ape" (h = 0): try 0, 0 + 1 – open! 1
• insert "bear" (h = 1): try 1, 1 + 1, 1 + 2 – open! 2 "cat"
• remove "ape" 3 "bear"
• search for "ape": try 0, 0 + 1 – conclude not in table 4 "emu"
• search for "bear": try 1 – conclude not in table, 5
but "bear" is further down in the table! … ...
22 "wolf"
• To fix this problem, distinguish between: 23 "wasp"
• removed positions that previously held an item 24 "yak"
• empty positions that have never held an item 25 "zebra"

• During probing, we don't stop if we see a removed position.


ex: search for "bear": try 1 (removed), 1 + 1, 1 + 2 – found!

• We can insert items in either empty or removed positions.

An Interface For Hash Tables


public interface HashTable {
boolean insert(Object key, Object value);
Queue<Object> search(Object key);
Queue<Object> remove(Object key);
}

• insert() takes a key-value pair and returns:


• true if the key-value pair can be added
• false if it cannot be added (referred to as overflow)
• search() and remove() both take a key, and return a queue
containing all of the values associated with that key.
• example: an index for a book
• key = word
• values = the pages on which that word appears
• return null if the key is not found

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 520


An Implementation Using Open Addressing
public class OpenHashTable implements HashTable {
private class Entry {
private Object key;
private LLQueue<Object> values;

}

private Entry[] table;
private int probeType; "ant"
} LLQueue
0 object
table
1 "ape"
probeType LINEAR
2 null LLQueue
3 null object

4 null
… …

• We use a private inner class for the entries in the hash table.

• We use an LLQueue for the values associated with a given key.

Empty vs. Removed


• When we remove a key and its values, we:
• leave the Entry object in the table
• set the Entry object's key and values fields to null
• example: after remove("ape"):
"ant"
LLQueue
0 object
table
1 null "ape"
probeType LINEAR
2 null null LLQueue
3 null object

4 null
… …

• Note the difference:


• a truly empty position has a value of null in the table
(example: positions 2, 3 and 4 above)
• a removed position refers to an Entry object whose
key and values fields are null (example: position 1 above)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 521


Probing Using Double Hashing
private int probe(Object key) {
int i = h1(key); // first hash function
int h2 = h2(key); // second hash function

// keep probing until we get an empty position or match


while (table[i] != null && !key.equals(table[i].key)) {
i = (i + h2) % table.length;
}

return i;
}

• It is essential that we:


• check for table[i] != null first. why?

• call the equals method on key, not table[i].key. why?

Avoiding an Infinite Loop


• The while loop in our probe method could lead to an infinite loop.
while (table[i] != null && !key.equals(table[i].key)) {
i = (i + h2) % table.length;
}

• When would this happen?

• We can stop probing after checking n positions (n = table size),


because the probe sequence will just repeat after that point.
• for quadratic probing:
(h1 + n2) % n = h1 % n
(h1 + (n+1)2) % n = (h1 + n2 + 2n + 1) % n = (h1 + 1) % n
• for double hashing:
(h1 + n*h2) % n = h1 % n
(h1 + (n+1)*h2) % n = (h1 + n*h2 + h2) % n = (h1 + h2) % n

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 522


Avoiding an Infinite Loop (cont.)
private int probe(Object key) {
int i = h1(key); // first hash function
int h2 = h2(key); // second hash function
int numChecked = 1;

// keep probing until we get an empty position or a match


while (table[i] != null && !key.equals(table[i].key)) {
if (numChecked == table.length) {
return -1;
}
i = (i + h2) % table.length;
numChecked++;
}

return i;
}

Search and Removal


public LLQueue<Object> search(Object key) {
// throw an exception if key == null
int i = probe(key);
if (i == -1 || table[i] == null) {
return null;
} else {
return table[i].values;
}
}

public LLQueue<Object> remove(Object key) {


// throw an exception if key == null
int i = probe(key);
if (i == -1 || table[i] == null) {
return null;
}
LLQueue<Object> removedVals = table[i].values;
table[i].key = null;
table[i].values = null;
return removedVals;
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 523


Insertion
• We begin by probing for the key.
• Several cases:
1. the key is already in the table (we're inserting a duplicate)
 add the value to the values in the key's Entry

2. the key is not in the table: three subcases:


a. encountered 1 or more removed positions while probing
 put the (key, value) pair in the first removed position
seen during probing. why?

b. no removed position; reached an empty position


 put the (key, value) pair in the empty position
c. no removed position or empty position
 overflow; return false

Tracing Through Some Examples


• Start with the hash table at right with:
0 "ant"
• double hashing
1
• our earlier hash functions h1 and h2 2 "cat"
3
• Perform the following operations:
4 "emu"
• insert "bear" (h1 = 1, h2 = 4): 5 "fox"
• insert "bison" (h1 = 1, h2 = 5): 6
7
• insert "cow" (h1 = 2, h2 = 3): 8
9

• delete "emu" (h1 = 4, h2 = 3): 10

• search "eel" (h1 = 4, h2 = 3):

• insert "bee" (h1 = ___, h2 = ____):

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 524


Dealing with Overflow
• Overflow = can't find a position for an item

• When does it occur?


• linear probing:
• quadratic probing:


• double hashing:
• if the table size is a prime number: same as linear
• if the table size is not a prime number: same as quadratic

• To avoid overflow (and reduce search times), grow the hash table
when the % of occupied positions gets too big.
• problem: we need to rehash all of the existing items. why?

Implementing the Hash Function


• Characteristics of a good hash function:
1) efficient to compute
2) uses the entire key
• changing any char/digit/etc. should change the hash code
3) distributes the keys more or less uniformly across the table
4) must be a function!
• a key must always get the same hash code

• In Java, every object has a hashCode() method.


• the version inherited from Object returns a value
based on an object's memory location
• classes can override this version with their own

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 525


Hash Functions for Strings: version 1
• ha = the sum of the characters' ASCII values
• example: ha("eat") = 101 + 97 + 116 = 314

• All permutations of a given set of characters get the same code.


• example: ha("tea") = ha("eat")
• could be useful in a Scrabble game
• allow you to look up all words that can be formed
from a given set of characters

• The range of possible hash codes is very limited.


• example: hashing keys composed of 1-5 lower-case char's
(padded with spaces)
• 26*27*27*27*27 = over 13 million possible keys
• smallest code = ha("a ") = 97 + 4*32 = 225
610 – 225
largest code = ha("zzzzz") = 5*122 = 610
= 385 codes

Hash Functions for Strings: version 2


• Compute a weighted sum of the ASCII values:
hb = a0bn–1 + a1bn–2 + … + an–2b + an–1
where ai = ASCII value of the ith character
b = a constant
n = the number of characters

• Multiplying by powers of b allows the positions of the characters


to affect the hash code.
• different permutations get different codes

• We may get arithmetic overflow, and thus the code


may be negative. We adjust it when this happens.

• Java uses this hash function with b = 31 in the hashCode()


method of the String class.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 526


Hash Table Efficiency
• In the best case, search and insertion are O(1).

• In the worst case, search and insertion are linear.


• open addressing: O(m), where m = the size of the hash table
• separate chaining: O(n), where n = the number of keys

• With good choices of hash function and table size,


complexity is generally better than O(log n) and approaches O(1).

• load factor = # keys in table / size of the table.


To prevent performance degradation:
• open addressing: try to keep the load factor < 1/2
• separate chaining: try to keep the load factor < 1

• Time-space tradeoff: bigger tables have better performance,


but they use up more memory.

Hash Table Limitations


• It can be hard to come up with a good hash function for a
particular data set.

• The items are not ordered by key. As a result, we can't easily:


• print the contents in sorted order
• perform a range search (find all values between v1 and v2)
• perform a rank search – get the kth largest item
We can do all of these things with a search tree.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 527


Extra Practice
• Start with the hash table at right with: 0 "ant"
• double hashing 1
• h1(key) = ASCII of first letter – ASCII of 'a' 2 "cat"

• h2(key) = key.length() 3
4 "emu"
• shaded cells are removed cells
5
• What is the probe sequence for "baboon"? 6
(the sequence of positions seen during probing) 7
8
9
10
A. 1, 2, 5
B. 1, 6
C. 1, 7, 2
D. 1, 7, 3
E. 1, 7, 2, 8

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 528


Extra Practice
• Start with the hash table at right with: 0 "ant"
• double hashing 1
• h1(key) = ASCII of first letter – ASCII of 'a' 2 "cat"

• h2(key) = key.length() 3
4 "emu"
• shaded cells are removed cells
5
• What is the probe sequence for "baboon"? 6
(h1 = 1, h2 = 6) try: 1 % 11 = 1 7
(1 + 6) % 11 = 7 8
(1 + 2*6) % 11 = 2 9
(1 + 3*6) % 11 = 8 10
A. 1, 2, 5
empty cell, so stop probing
B. 1, 6
C. 1, 7, 2
D. 1, 7, 3
E. 1, 7, 2, 8

Extra Practice
• Start with the hash table at right with: 0 "ant"
• double hashing 1
• h1(key) = ASCII of first letter – ASCII of 'a' 2 "cat"

• h2(key) = key.length() 3
4 "emu"
• shaded cells are removed cells
5
• What is the probe sequence for "baboon"? 6
(h1 = 1, h2 = 6) try: 1 % 11 = 1 7
(1 + 6) % 11 = 7 8
(1 + 2*6) % 11 = 2 9
(1 + 3*6) % 11 = 8 10

• If we insert "baboon", in what position will it go?


A. 1 B. 7 C. 2 D. 8

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 529


Extra Practice
• Start with the hash table at right with: 0 "ant"
• double hashing 1 "baboon"
• h1(key) = ASCII of first letter – ASCII of 'a' 2 "cat"

• h2(key) = key.length() 3
4 "emu"
• shaded cells are removed cells
5
• What is the probe sequence for "baboon"? 6
(h1 = 1, h2 = 6) try: 1 % 11 = 1 7
(1 + 6) % 11 = 7 8
(1 + 2*6) % 11 = 2 9
(1 + 3*6) % 11 = 8 10

• If we insert "baboon", in what position will it go?


A. 1 B. 7 C. 2 D. 8
the first removed position seen while probing

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 530


Unit 10

Graphs

Computer Science S-111


Harvard University
David G. Sullivan, Ph.D.

What is a Graph?
e
vertex / node

edge / arc b d f h j

a c i

• A graph consists of:


• a set of vertices (also known as nodes)
• a set of edges (also known as arcs), each of which connects
a pair of vertices

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 531


Example: A Highway Graph
84 Portland 39
Concord Portsmouth
74
63 83
54
134 44
Albany Worcester Boston
42 49

185 Providence
New York

• Vertices represent cities.

• Edges represent highways.

• This is a weighted graph, with a cost associated with each edge.


• in this example, the costs denote mileage

• We’ll use graph algorithms to answer questions like


“What is the shortest route from Portland to Providence?”

Relationships Among Vertices


e

b d f h j

a c i

• Two vertices are adjacent if they are connected by a single edge.


• ex: c and g are adjacent, but c and i are not

• The collection of vertices that are adjacent to a vertex v are


referred to as v’s neighbors.
• ex: c’s neighbors are a, b, d, f, and g

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 532


Paths in a Graph
e

b d f h j

a c i

• A path is a sequence of edges that connects two vertices.

• A graph is connected if there is


a path between any two vertices.
• ex: the six vertices at right are part
of a graph that is not connected

• A graph is complete if there is an


edge between every pair of vertices.
• ex: the graph at right is complete

Directed Graphs
• A directed graph has a direction associated with each edge,
which is depicted using an arrow:
e

b d f

a c

• Edges in a directed graph are often represented as ordered


pairs of the form (start vertex, end vertex).
• ex: (a, b) is an edge in the graph above, but (b, a) is not.

• In a path in a directed graph, the end vertex of edge i


must be the same as the start vertex of edge i + 1.
• ex: { (a, b), (b, e), (e, f) } is a valid path.
{ (a, b), (c, b), (c, a) } is not.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 533


Cycles in a Graph
• A cycle is a path that:
• leaves a given vertex using one edge
• returns to that same vertex using a different edge

• Examples: the highlighted paths below


e

b d f h

a c i

• An acyclic graph has no cycles.

Trees vs. Graphs


• A tree is a special type of graph.
• connected, undirected, and acyclic
• we usually single out one of the vertices to be the root,
but graph theory does not require this
e e

b d f h b d f h

a c i a c i

a graph that is not a tree, a tree using the same nodes


because it has cycles
e

b d f h

a c i

another tree using the same nodes

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 534


Spanning Trees
• A spanning tree is a subset of a connected graph that contains:
• all of the vertices
• a subset of the edges that form a tree

• Recall this graph with cycles e

from the previous slide:


b d f h

a c i

• The trees on that slide were spanning trees for this graph.
Here are two others:
e e

b d f h b d f h

a c i a c i

Representing a Graph: Option 1


• Use an adjacency matrix – a two-dimensional array in which
element [r][c] = the cost of going from vertex r to vertex c

• Example:
0 1 2 3
1. Portland 39
0 54 44
1 39 2. Portsmouth
83
2 54 39 83 54
44
3 44 83 3. Worcester 0. Boston

• Use a special value to indicate there’s no edge from r to c


• shown as a shaded cell above
• can’t use 0, because an edge may have an actual cost of 0

• This representation:
• wastes memory if a graph is sparse (few edges per vertex)
• is memory-efficient if a graph is dense (many edges per vertex)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 535


Representing a Graph: Option 2
• Use one adjacency list for each vertex.
• a linked list with info on the edges coming from that vertex
3 2
44 54
null
1. Portland 39
2
0 39 2. Portsmouth
1 null 83
54
2 1 0 3 44
39 54 83 3. Worcester 0. Boston
3
null
0 2
44 83
null

• This representation uses less memory if a graph is sparse.

• It uses more memory if a graph is dense.


• because of the references linking the nodes

Graph Class
public class Graph {
private class Vertex {
private String id;
private Edge edges; // adjacency list
private Vertex next;
private boolean encountered;
private boolean done;
private Vertex parent;
private double cost;

}
private class Edge {
private Vertex start;
private Vertex end;
private double cost;
private Edge next;

} The highlighted fields
private Vertex vertices; are shown in the diagram
… on the previous page.
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 536


Our Graph Representation
vertices “Boston” Portland 39
44 54
Portsmouth
null 83
54
“Portland” 44
Worcester Boston
39
null

“Portsmouth”
39 54 83
null

“Worcester”
44 83
null null

• Each Vertex object (shown in blue) stores info. about a vertex.


• including an adjacency list of Edge objects (the purple ones)

• A Graph object has a single field called vertices


• a reference to a linked list of Vertex objects
• a linked list of linked lists!

Traversing a Graph
• Traversing a graph involves starting at some vertex and visiting
all vertices that can be reached from that vertex.
• visiting a vertex = processing its data in some way
• if the graph is connected, all of its vertices will be visited

• We will consider two types of traversals:


• depth-first: proceed as far as possible along a given path
before backing up
• breadth-first: visit a vertex
visit all of its neighbors
visit all unvisited vertices 2 edges away
visit all unvisited vertices 3 edges away, etc.

• Applications:
• determining the vertices that can be reached from some vertex
• web crawler (vertices = pages, edges = links)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 537


Depth-First Traversal
• Visit a vertex, then make recursive calls on all of its
yet-to-be-visited neighbors:
dfTrav(v, parent)
visit v and mark it as visited
v.parent = parent
for each vertex w in v’s neighbors
if (w has not been visited)
dfTrav(w, v)
• Java method:
private static void dfTrav(Vertex v, Vertex parent) {
System.out.println(v.id); // visit v
v.done = true;
v.parent = parent;
Edge e = v.edges;
while (e != null) {
Vertex w = e.end;
if (!w.done)
dfTrav(w, v);
e = e.next;
}
}

Example: Depth-First Traversal from Portland


84 Portland 1 39
For the examples, we’ll
Concord 7 Portsmouth 2 assume that the edges in
74 83
63
54
each vertex’s adjacency list
are sorted by increasing
134 44
Albany 8 Worcester 4 Boston 3 edge cost.
42 49

185 Providence 5 dfTrav(Ptl, null)


New York 6 w = Pts
dfTrav(Pts, Ptl)
w = Ptl, Bos
dfTrav(Bos, Pts)
w = Wor
void dfTrav(Vertex v, Vertex parent) { dfTrav(Wor, Bos)
System.out.println(v.id); w = Pro
v.done = true; dfTrav(Pro, Wor)
v.parent = parent; w = Wor, Bos, NY
Edge e = v.edges;
dfTrav(NY, Pro)
while (e != null) {
w = Pro
Vertex w = e.end;
return
if (!w.done)
dfTrav(w, v); no more neighbors
e = e.next; return
} w = Bos, Con
} dfTrav(Con, Wor)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 538


Depth-First Spanning Tree
84 Portland 1 39
Concord 7 Portsmouth 2
74 83
63
54
134 44
Albany 8 Worcester 4 Boston 3
42 49

185 Providence 5
New York 6

Portland The edges obtained by


following the parent
Portsmouth
references form a spanning
Boston tree with the origin of the
Worcester
traversal as its root.
Providence Concord Albany From any city, we can get to
the origin by following the
New York
roads in the spanning tree.

Another Example:
Depth-First Traversal from Worcester
• In what order will the cities be visited?
• Which edges will be in the resulting spanning tree?
84 Portland 39
Concord Portsmouth
74
63 83
54
134 44
Albany Worcester Boston
42 49

185 Providence
New York

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 539


Checking for Cycles in an Undirected Graph
e

cycle b d f h

a c i

• To discover a cycle in an undirected graph, we can:


• perform a depth-first traversal, marking the vertices as visited
• when considering neighbors of a visited vertex, if we discover
one already marked as visited, there must be a cycle

• If no cycles found during the traversal, the graph is acyclic.

• This doesn't work for directed graphs: b

• c is a neighbor of both a and b


• there is no cycle a c

Breadth-First Traversal
• Use a queue to store vertices we've seen but not yet visited:
private static void bfTrav(Vertex origin) {
origin.encountered = true;
origin.parent = null;
Queue<Vertex> q = new LLQueue<Vertex>();
q.insert(origin);
while (!q.isEmpty()) {
Vertex v = q.remove();
System.out.println(v.id); // Visit v.
// Add v’s unencountered neighbors to the queue.
Edge e = v.edges;
while (e != null) {
Vertex w = e.end;
if (!w.encountered) {
w.encountered = true;
w.parent = v;
q.insert(w);
}
e = e.next;
}
}
}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 540


Example: Breadth-First Traversal from Portland
84 Portland 1 39
Concord 3 Portsmouth 2
74
63 83
54
134 44
Albany 7 Worcester 5 Boston 4
42 49

185 Providence 6
New York 8

Evolution of the queue:


remove insert queue contents
Portland Portland
Portland Portsmouth, Concord Portsmouth, Concord
Portsmouth Boston, Worcester Concord, Boston, Worcester
Concord none Boston, Worcester
Boston Providence Worcester, Providence
Worcester Albany Providence, Albany
Providence New York Albany, New York
Albany none New York
New York none empty

Breadth-First Spanning Tree


84 Portland 1 39
Concord 3 Portsmouth 2
74
63 83
54
134 44
Albany 7 Worcester 5 Boston 4
42 49

185 Providence 6
New York 8

breadth-first spanning tree: depth-first spanning tree:


Portland Portland

Portsmouth Concord Portsmouth

Boston Worcester Boston

Providence Albany Worcester

New York Providence Concord Albany

New York

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 541


Another Example:
Breadth-First Traversal from Worcester
84 Portland 39
Concord Portsmouth
74 83
63
54
134 44
Albany Worcester Boston
42 49

185 Providence
New York

Evolution of the queue:


remove insert queue contents

Time Complexity of Graph Traversals


• let V = number of vertices in the graph
E = number of edges

• If we use an adjacency matrix, a traversal requires O(V2) steps.


• why?

• If we use adjacency lists, a traversal requires O(V + E) steps.


• visit each vertex once
• traverse each vertex's adjacency list at most once
• the total length of the adjacency lists is at most 2E = O(E)
• for a sparse graph, O(V + E) is better than O(V2)
• for a dense graph, E = O(V2), so both representations are O(V2)

• In the remaining notes, we'll assume an adjacency-list


implementation.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 542


Minimum Spanning Tree
• A minimum spanning tree (MST) has the smallest total cost
among all possible spanning trees.
• example:
Portland 39 Portland 39
Portsmouth Portsmouth
83 83
54 54
44 44
Worcester Boston Worcester Boston

one possible spanning tree the minimal-cost spanning tree


(total cost = 39 + 83 + 54 = 176) (total cost = 39 + 54 + 44 = 137)

• If all edges have unique costs, there is only one MST.


If some edges have the same cost, there may be more than one.

• Example applications:
• determining the shortest highway system for a set of cities
• calculating the smallest length of cable needed to connect
a network of computers

Building a Minimum Spanning Tree


• Claim: If you divide the vertices into two disjoint subsets A and B,
the lowest-cost edge (va, vb) joining a vertex in A to a vertex in B
must be part of the MST.
84 Portland 39 example:
Concord Portsmouth
subset A = unshaded
63
74
83 subset B = shaded
54
134 44 The 6 bold edges each join
Albany Worcester Boston
a vertex in A to a vertex in B.
42 49
185 The one with the lowest cost
New York Providence
(Portland to Portsmouth)
must be in the MST.
Proof by contradiction:
1. Assume we can create an MST (call it T) that doesn’t include (va, vb).
2. T must include a path from va to vb, so it must include
one of the other edges (va', vb') that span A and B,
such that (va', vb') is part of the path from va to vb. va' vb'
3. Adding (va, vb) to T introduces a cycle. va vb
4. Removing (va', vb') gives a spanning tree with a
lower total cost, which contradicts the original assumption.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 543


Prim’s MST Algorithm
• Begin with the following subsets:
• A = any one of the vertices
• B = all of the other vertices

• Repeatedly do the following:


• select the lowest-cost edge (va, vb)
connecting a vertex in A to a vertex in B
• add (va, vb) to the spanning tree
• move vertex vb from set B to set A

• Continue until set A contains all of the vertices.

Example: Prim’s Starting from Concord


84 Portland (Ptl) 39
Concord (Con) Portsmouth(Pts)
74
63 83
54
134 44
Albany (Alb) Worcester(Wor) Boston (Bos)
42 49

185 Providence (Pro)


New York (NY)

• Tracing the algorithm:


edge added set A set B
{Con} {Alb, Bos, NY, Ptl, Pts, Pro, Wor}
(Con, Wor) {Con, Wor} {Alb, Bos, NY, Ptl, Pts, Pro}
(Wor, Pro) {Con, Wor, Pro} {Alb, Bos, NY, Ptl, Pts}
(Wor, Bos) {Con, Wor, Pro, Bos} {Alb, NY, Ptl, Pts}
(Bos, Pts) {Con, Wor, Pro, Bos, Pts} {Alb, NY, Ptl}
(Pts, Ptl) {Con, Wor, Pro, Bos, Pts, Ptl} {Alb, NY}
(Wor, Alb) {Con, Wor, Pro, Bos, Pts, Ptl, Alb} {NY}
(Pro, NY) {Con,Wor,Pro,Bos,Pts,Ptl,Alb,NY} {}

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 544


MST May Not Give Shortest Paths
84 Portland (Ptl) 39
Concord (Con) Portsmouth(Pts)
74
63 83
54
134 44
Albany (Alb) Worcester(Wor) Boston (Bos)
42 49

185 Providence (Pro)


New York (NY)

• The MST is the spanning tree with the minimal total edge cost.

• It does not necessarily include the minimal cost path


between a pair of vertices.

• Example: shortest path from Boston to Providence


is along the single edge connecting them
• that edge is not in the MST

Implementing Prim’s Algorithm


• We use the done field to keep track of the sets.
• if v.done == true, v is in set A
• if v.done == false, v is in set B

• We repeatedly scan through the lists of vertices and edges


to find the next edge to add.
 O(EV)

• We can do better!
• use a heap-based priority queue to store the vertices in set B
• priority of a vertex x = –1 * cost of the lowest-cost edge
connecting x to a vertex in set A
• why multiply by –1?

• somewhat tricky: need to update the priorities over time


 O(E log V)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 545


The Shortest-Path Problem
• It’s often useful to know the shortest path from one vertex to
another – i.e., the one with the minimal total cost
• example application: routing traffic in the Internet

• For an unweighted graph, we can simply do the following:


• start a breadth-first traversal from the origin, v
• stop the traversal when you reach the other vertex, w
• the path from v to w in the resulting (possibly partial)
spanning tree is a shortest path

• A breadth-first traversal works for an unweighted graph because:


• the shortest path is simply one with the fewest edges
• a breadth-first traversal visits cities in order according to the
number of edges they are from the origin.

• Why might this approach fail to work for a weighted graph?

Dijkstra’s Algorithm
• One algorithm for solving the shortest-path problem for
weighted graphs was developed by E.W. Dijkstra.

• It allows us to find the shortest path from a vertex v (the origin)


to all other vertices that can be reached from v.

• Basic idea:
• maintain estimates of the shortest paths
from the origin to every vertex (along with their costs)
• gradually refine these estimates as we traverse the graph

• Initial estimates:
path cost C (inf)
the origin itself: stay put! 0 5 7
all other vertices: unknown infinity
A 14
B
(0) (inf)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 546


Dijkstra’s Algorithm (cont.)
• We say that a vertex w is finalized if we have found the
shortest path from v to w.

• We repeatedly do the following:


• find the unfinalized vertex w with the lowest cost estimate
• mark w as finalized (shown as a filled circle below)
• examine each unfinalized neighbor x of w to see if there
is a shorter path to x that passes through w
• if there is, update the shortest-path estimate for x

• Example:
C (inf) C (5) C (5)

5 7 5 7 5 7 (5 + 7 < 14)

A 14
B A 14
B A 14
B
(0) (inf) (0) (14) (0) (12)

Another Example: Shortest Paths from Providence


Portsmouth
• Initial estimates: 83
54
Boston infinity 44
Worcester Boston
Worcester infinity
42
Portsmouth infinity 49
Providence 0 Providence

• Providence has the smallest unfinalized estimate, so we finalize it.

• We update our estimates for its neighbors:


Boston 49 (< infinity) Portsmouth
83
Worcester 42 (< infinity) 54
Portsmouth infinity Worcester 44 Boston
Providence 0 42 49
Providence

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 547


Shortest Paths from Providence (cont.)
Portsmouth
Boston 49 83
Worcester 42 54
Portsmouth infinity Worcester 44 Boston
Providence 0 42 49
Providence

• Worcester has the smallest unfinalized estimate, so we finalize it.


• any other route from Prov. to Worc. would need to go via Boston,
and since (Prov  Worc) < (Prov  Bos), we can’t do better.

• We update our estimates for Worcester's unfinalized neighbors:


Boston 49 (no change)
Portsmouth
Worcester 42
83
Portsmouth 125 (42 + 83 < infinity) 54
Providence 0 Worcester 44 Boston
42 49
Providence

Shortest Paths from Providence (cont.)


Portsmouth
Boston 49 83
Worcester 42 54
Portsmouth 125 Worcester 44 Boston
Providence 0 42 49
Providence

• Boston has the smallest unfinalized estimate, so we finalize it.


• we'll see later why we can safely do this!

• We update our estimates for Boston's unfinalized neighbors:


Boston 49
Portsmouth
Worcester 42 83
Portsmouth 103 (49 + 54 < 125) 54
44
Providence 0 Worcester Boston
42 49
Providence

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 548


Shortest Paths from Providence (cont.)
Portsmouth
Boston 49 83
Worcester 42 54
Portsmouth 103 Worcester 44 Boston
Providence 0 42 49
Providence

• Only Portsmouth is left, so we finalize it.

Finalizing a Vertex
origin
other finalized vertices
w encountered but
unfinalized
(i.e., it has a
non-infinite estimate)

• Let w be the unfinalized vertex with the smallest cost estimate.


Why can we finalize w, before seeing the rest of the graph?
• We know that w’s current estimate is for the shortest path to w
that passes through only finalized vertices.
• Any shorter path to w would have to pass through one of the
other encountered-but-unfinalized vertices, but they are all
further away from the origin than w is!
• their cost estimates may decrease in subsequent stages,
but they can’t drop below w’s current estimate!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 549


Pseudocode for Dijkstra’s Algorithm
dijkstra(origin)
origin.cost = 0
for each other vertex v
v.cost = infinity;
while there are still unfinalized vertices with cost < infinity
find the unfinalized vertex w with the minimal cost
mark w as finalized
for each unfinalized vertex x adjacent to w
cost_via_w = w.cost + edge_cost(w, x)
if (cost_via_w < x.cost)
x.cost = cost_via_w
x.parent = w

• At the conclusion of the algorithm, for each vertex v:


• v.cost is the cost of the shortest path from the origin to v
• if v.cost is infinity, there is no path from the origin to v
• starting at v and following the parent references yields
the shortest path

Example: Shortest Paths from Concord


84 Portland 39
Concord Portsmouth
74 83
63
54
134 44
Albany Worcester Boston
42 49

185 Providence
New York

Evolution of the cost estimates (costs in bold have been finalized):


Albany inf inf 197 197 197 197 197
Boston inf 74 74
Concord 0
New York inf inf inf inf inf 290 290 290
Portland inf 84 84 84
Portsmouth inf inf 146 128 123 123
Providence inf inf 105 105 105
Worcester inf 63
Note that the Portsmouth estimate was improved three times!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 550


Another Example: Shortest Paths from Worcester
84 Portland 39
Concord Portsmouth
74 83
63
54
134 44
Albany Worcester Boston
42 49

185 Providence
New York

Evolution of the cost estimates (costs in bold have been finalized):


Albany
Boston
Concord
New York
Portland
Portsmouth
Providence
Worcester

Implementing Dijkstra's Algorithm


• Similar to the implementation of Prim's algorithm.

• Use a heap-based priority queue to store the unfinalized vertices.


• priority = ?

• Need to update a vertex's priority whenever we update its


shortest-path estimate.

• Time complexity = O(ElogV)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 551


Topological Sort
• Used to order the vertices in a directed acyclic graph (a DAG).

• Topological order: an ordering of the vertices such that,


if there is directed edge from a to b, a comes before b.

• Example application: ordering courses according to prerequisites


CSCI E-162
CSCI E-160
CSCI E-170
CSCI E-220 CSCI E-215
CSCI E-50a CSCI E-50b CSCI E-119 CSCI E-234

CSCI E-251
MATH E-10 MATH E-104 CSCI E-124

• a directed edge from a to b indicates that a is a prereq of b

• There may be more than one topological ordering.

Topological Sort Algorithm


• A successor of a vertex v in a directed graph = a vertex w such
that (v, w) is an edge in the graph ( v w)

• Basic idea: find vertices with no successors and work backward.


• there must be at least one such vertex. why?

• Pseudocode for one possible approach:


topolSort
S = a stack to hold the vertices as they are visited
while there are still unvisited vertices
find a vertex v with no unvisited successors
mark v as visited
S.push(v)
return S

• Popping the vertices off the resulting stack gives


one possible topological ordering.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 552


Topological Sort Example
CSCI E-162
CSCI E-160

CSCI E-215
CSCI E-50a CSCI E-50b CSCI E-119

MATH E-10 MATH E-104 CSCI E-124

Evolution of the stack:

push stack contents (top to bottom)


E-124 E-124
E-162 E-162, E-124
E-215 E-215, E-162, E-124
E-104 E-104, E-215, E-162, E-124
E-119 E-119, E-104, E-215, E-162, E-124
E-160 E-160, E-119, E-104, E-215, E-162, E-124
E-10 E-10, E-160, E-119, E-104, E-215, E-162, E-124
E-50b E-50b, E-10, E-160, E-119, E-104, E-215, E-162, E-124
E-50a E-50a, E-50b, E-10, E-160, E-119, E-104, E-215, E-162, E-124
one possible topological ordering

Another Topological Sort Example


A
B C

D E F

G H

Evolution of the stack:

push stack contents (top to bottom)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 553


Traveling Salesperson Problem (TSP)
York
155 180
95
Cambridge Oxford
203
62
132 62
105 257
Canterbury London
55

• A salesperson needs to travel to a number of cities to visit clients,


and wants to do so as efficiently as possible.

• A tour is a path that:


• begins at some starting vertex
• passes through every other vertex once and only once
• returns to the starting vertex

• TSP: find the tour with the lowest total cost

TSP for Santa Claus


A “world TSP” with
1,904,711 cities.
The figure at right
shows a tour with
a total cost of
7,516,353,779
meters – which is
at most 0.068%
longer than the
optimal tour.
source: http://www.tsp.gatech.edu/world/pictures.html

• Other applications:
• coin collection from phone booths
• routes for school buses or garbage trucks
• minimizing the movements of machines in automated
manufacturing processes
• many others

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 554


Solving a TSP: Brute-Force Approach
• Perform an exhaustive search of all possible tours.
• represent the set of all possible tours as a tree
L

Cm Ct O Y

Ct O Y Cm O Y Cm Ct Y Cm Ct O

O Y Ct Y Ct O O Y Cm Y Cm O Ct Y Cm Y Cm Ct Ct O Cm O Cm Ct

Y O Y Ct O Ct Y O Y Cm O Cm Y Ct Y Cm Ct Cm O Ct O Cm Ct Cm

L L L L L L L L L L L L L L L L L L L L L L L L

• The leaf nodes correspond to possible solutions.


• for n cities, there are (n – 1)! leaf nodes in the tree.
• half are redundant (e.g., L-Cm-Ct-O-Y-L = L-Y-O-Ct-Cm-L)

• Problem: exhaustive search is intractable for all but small n.


• example: when n = 14, ((n – 1)!) / 2 = over 3 billion

Solving a TSP: Informed Search


• Focus on the most promising paths through the tree
of possible tours.
• use a function that estimates how good a given path is

• Better than brute force, but still exponential space and time.

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 555


Algorithm Analysis Revisited
• Recall that we can group algorithms into classes (n = problem size):
name example expressions big-O notation
constant time 1, 7, 10 O(1)
logarithmic time 3log10n, log2n + 5 O(log n)
linear time 5n, 10n – 2log2n O(n)
n log n time 4n log2n, n log2n + n O(n log n)
quadratic time 2n2 + 3n, n2 – 1 O(n2)
nc (c > 2) n3 - 5n, 2n5 + 5n2 O(nc)
exponential time 2n, 5en + 2n2 O(cn)
factorial time (n – 1)!/2, 3n! O(n!)

• Algorithms that fall into one of the classes above the dotted line
are referred to as polynomial-time algorithms.

• The term exponential-time algorithm is sometimes used


to include all algorithms that fall below the dotted line.
• algorithms whose running time grows as fast or faster than cn

Classifying Problems
• Problems that can be solved using a polynomial-time algorithm
are considered “easy” problems.
• we can solve large problem instances in a
reasonable amount of time

• Problems that don’t have a polynomial-time solution algorithm


are considered “hard” or "intractable" problems.
• they can only be solved exactly for small values of n

• Increasing the CPU speed doesn't help much for


intractable problems:
CPU 2
CPU 1 (1000x faster)
max problem size for O(n) alg: N 1000N
O(n ) alg:
2 N 31.6 N
O(2n) alg: N N + 9.97

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 556


Dealing With Intractable Problems
• When faced with an intractable problem, we resort to
techniques that quickly find solutions that are "good enough".

• Such techniques are often referred to as heuristic techniques.


• heuristic = rule of thumb
• there's no guarantee these techniques will produce
the optimal solution, but they typically work well

Take-Home Lessons
• Computer science is the science of solving problems
using computers.

• Java is one programming language we can use for this.

• The key concepts transcend Java:


• flow of control
• variables, data types, and expressions
• conditional execution
• procedural decomposition
• definite and indefinite loops
• recursion
• console and file I/O
• memory management (stack, heap, references)

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 557


Take-Home Lessons (cont.)
• Object-oriented programming allows us to capture the
abstractions in the programs that we write.
• creates reusable building blocks
• key concepts: encapsulation, inheritance, polymorphism

• Abstract data types allow us to organize and manipulate


collections of data.
• a given ADT can be implemented in different ways
• fundamental building blocks: arrays, linked nodes

• Efficiency matters when dealing with large collections of data.


• some solutions can be much faster or more space efficient
• what’s the best data structure/algorithm for your workload?
• example: sorting an almost sorted collection

Take-Home Lessons (cont.)


• Use the tools in your toolbox!
• interfaces, generic data structures
• lists/stacks/queues, trees, heaps, hash tables
• recursion, recursive backtracking, divide-and-conquer

• Use built-in/provided collections/interfaces:


• java.util.ArrayList<T> (implements List<T>)
• java.util.LinkedList<T> (implements List<T> and Queue<T>)
• java.util.Stack<T>
• java.util.TreeMap<K, V> (a balanced search tree)
implement
• java.util.HashMap<K, V> (a hash table)
Map<K, V>
• java.util.PriorityQueue<T> (a heap)

• But use them intelligently!


• ex: LinkedList maintains a reference to the last node in the list
• list.add(item, n) will add item to the end in O(n) time
• list.addLast(item) will add item to the end in O(1) time!

CSCI S-111, Summer 2021 David G. Sullivan, Ph.D. 558

You might also like