CSC 1051 Algorithms & Data Structures II
Program Organization, Encapsulation and
Algorithm Efficiency
PROGRAM ORGANIZATION
The package Statement
A package is a group of related classes Examples in the Java API:
java.util
java.io.exceptions
In a program, you can refer to a class by its fully qualified name, such as java.util.Scanner
Or you can import a class and use the class name alone (Scanner)
3
import java.util.Scanner;
The package Statement
A package statement is used to put your own class into a package
It must be the first statement in the file that defines the class
package com.rephactor.utils;
public class ArrayUtilities {
// whatever }
If you don't explicitly put a class into a package, it is considered part of the default package
The package Statement
A package name is made up of one or more identifiers separated by periods
Not only does a package form a logical grouping of classes, it also creates a name space
Two classes can have the same name as long as they're in different packages
java.util.Timer
javax.swing.Timer
By organizing classes into packages, you don't have to worry about using a name that another developer has chosen
5
The package Statement
Of course, package names must be unique
Convention: construct package names by reversing an
appropriate domain name (which are guaranteed unique) For example, packages created by the company that owns rephactor.com might begin with com.rephactor
So they might have packages such as com.rephactor.utils com.rephactor.topics
The package Statement
For example, the ArrayUtilities class must be in a subdirectory com/rephactor/utils relative to the program or classpath
7
The import Statement
Classes in the Java API is part of a particular package
String is in the java.lang package Random is in the java.util package
A package is a group of related classes
A class can always be referred to by its fully qualified name
java.util.Random
Package organization allows two classes to have the same name, such as
java.util.Timer and java.awt.Timer
The import Statement
9
Random gen = new Random();
Using the fully qualified name is not always convenient:
An alternative is to import the class
Import statements go above the class that uses them
import java.util.Random;
Then the simple name can be used throughout the program:
java.util.Random gen = new java.util.Random();
The import Statement
import java.util.*;
Classes from the java.lang package are automatically imported That's why we don't import classes like System or String
If you're going to use multiple classes from a package, you can use the * wildcard character:
Javadoc
11
Javadoc comments provide information about program elements, such as classes and methods.
They make it possible for the Java compiler to automatically generate documentation on commented code. This is where the online Java API documentation comes from.
It is recommended that every class and method be described using Javadoc comments. In practice, though, this isn't always done.
Javadoc
/**
* This class contains methods that compute
* various values related to circles.
*
* @author Rephactor Java
* @version 1.0
*/
public class CircleStats {
...
A Javadoc comment begins with the characters /** and ends with the characters */. Here is a class comment:
Javadoc
13
/**
* This class contains methods that compute
* various values related to circles.
*
* @author Rephactor Java
* @version 1.0
*/
public class CircleStats {
...
A Javadoc comment begins with the characters /** and ends with the characters */. Here is a class comment:
Javadoc
There are numerous Javadoc tags that can be used that will be parsed into meaning API documentation. Here are some of the more popular:
@author Identifies the programmer
@param Describes a method or constructor parameter
@return Describes the return value of a method
@throws Indicates a method may throw an exception
@version Indicates version of the code, a number or date
ENCAPSULATION
Encapsulation
Encapsulation is the principle that a class should protect its data from unnecessary access
This is about good design – creating software using
elements that have limited, controlled interaction with other elements
If an object is encapsulated, problems are localized and easier to find and fix
Code that interacts with an object is a client of that object
Encapsulation ensures that a client cannot "reach in"
and change the values of instance variables directly
Encapsulation
From the client's point of view, an object should be a black
boxThe client doesn't need to know exactly what data is managed by an object or how it's organized
17
Instead, the client interacts with an object through its public interface – the set of methods a client can call
Encapsulation
There are actually four access levels in Java – protected access relates to inheritance
If no access modifier is used, the variable or method has default access (or package access)
Modifier Accessible within its class
Accessible from any class in the same package
Accessible from any subclass
Accessible from any classes in other packages
public Yes Yes Yes Yes
protected Yes Yes Yes No
default Yes Yes No No
private Yes No No No
Encapsulation
An accessor method is a method that provides the client with the value of an instance variable
Some accessor methods take the form getX, where X is the attribute – this is also called a "getter" method
19
public double getBalance() {
return balance;
}
Encapsulation
Methods that modify instance data are mutator methods They might be classic "setters"
public void setName(String newName) {
name = newName;
}
Unconditional mutators are almost as bad as public variables, but at least they are explicitly defined as such
The deposit and withdraw methods of a bank account are mutators because they change the balance
Some methods are both accessors and mutators
2D ARRAYS & METHOD
OVERLOADING
Two-Dimensional Arrays
A basic array is a single-dimensional array
A two-dimensional array is accessed via row and column and appropriate for tabular data
Movie Rev 1 Rev 2 Rev 3 Rev 4 Rev 5
Godzilla 3 3 4 2 3
Guardians of the Galaxy 4 4 5 5 4
Jersey Boys 3 3 3 4 3
Maleficent 4 3 3 4 3
The Expendables 3 2 1 3 2 2
The Fault in our Stars 4 3 4 4 3 X-Men: Days of Future Past 4 3 4 3 3
Two-Dimensional Arrays
Both row and column indexes begin at 0 Use brackets for each index
23
Two-Dimensional Arrays
All elements must have the same type
The type of a 2-D array of integers is int[][]
As with a 1-D array, the size of each dimension is specified when the array object is created
int[][] matrix;
matrix = new int[12][20];
This array has 12 rows (indexed 0 to 11) and 20 columns (indexed 0 to 19)
Two-Dimensional Arrays
You can also use an initialization list to create 2-D arrays
25
This is essentially a list of lists
int[][] reviews = { {3, 3, 4, 2, 3}, {4, 3, 4, 3, 3}, {3, 3, 3, 4, 3}, {4, 3, 3, 4, 3}, {2, 1, 3, 2, 2}, {4, 3, 4, 4, 3}, {4, 4, 5, 5, 4} };
Two-Dimensional Arrays
Nested loops are often used to access elements in a 2-D array The following code fills a 2-D array with random double values
double[][] scores = new double[30][5];
for (int row = 0; row < scores.length; row++)
for (int col = 0; col < scores[row].length; col++) scores[row][col] = Math.random() * 100;
scores.length is the number of rows
scores[row].length is the number of columns in that row
Two-Dimensional Arrays
In Java, a 2-D array is really an array of arrays
If values is a 2-D array with 5 rows and 7 columns, it could be depicted like this:
27
Two-Dimensional Arrays
The concept of a 2-D array can be generalized into a multidimensional array
int[][][] accidents = new int[12][31][24];
This array might be used to store the number of traffic accidents in a town, organized by month, day, and hour
Method Overloading
Method overloading is a technique in which two or more methods in a class can have the same name
The compiler must be able to figure out which method is being called by analyzing the parameter list
A method signature is the name of the method along with the types of its parameters
All method signatures in a class must be unique
That means the names can be the same as long as the number, types, and order of the parameters are
distinct
29
Method Overloading
For example:
public void setCoordinates(int x, int y) {
// whatever }
public void setCoordinates(Point p) {
// whatever }
The signatures for these methods are
setCoordinates(int, int) setCoordinates(Point)
Method Overloading
Suppose the following invocation is made:
31
target.setCoordinates(13, 58);
The compiler would map this invocation to the version of the method that takes two int parameters
Method overloading is appropriate in situations where the same operation is being applied to different data
Constructors are often overloaded
Method Overloading
Another example:
public static int add(int i, int j) {
return i + j;
}
public static double add(double i, double j) {
return i + j;
}
public static double add(double i, double j, double k) {
return i + j + k;
}
Method Overloading
The signatures of those methods are
add(int, int)
add(double, double)
add(double, double, double)
The compiler looks for the most specific match it can find If two or more methods are equally appropriate, the
compiler will issue an error (ambiguous invocation) The return type is NOT part of the method signature Overloaded methods cannot differ only by their return type
33
Method Overloading
The print and println methods are overloaded several times
Each version takes a single type as a parameter
println(int)
println(double) println(String)
etc.
So the following two calls invoke different methods
System.out.println("Hello there!");
System.out.println(total);
ALGORITHM EFFICIENCY
Efficiency and Big O Notation
An efficient algorithm solves a problem with minimal waste of resources, particularly CPU time
Efficiency can be measured various ways
Measuring the actual execution time on a particular machine doesn't tell us much about the solution in general
Benchmark scenarios provide a better picture, but are
still a function of particular hardware and input data
Efficiency and Big O Notation
Consider a linear search – finding a target element in a list of n elements
best case – target is first element in the list
worst case – target is last element, or not in the list at all average case – makes n/2 comparisons
Algorithm analysis is the discipline within computer science that studies efficiency
37
Efficiency and Big O Notation
An algorithm's time complexity is a function that
expresses time in terms of n, the amount of input data
Consider two algorithms that sort a list of n values
Efficiency and Big O Notation
For both algorithms, the time increases as the input size increases
What matters is the growth rate of the time complexity function
What is the shape of the curve?
How quickly does it increase (how steep is it)?
Instead of focusing on how fast an algorithm runs, we focus on how well the solution scales as n gets bigger
The time it takes Algorithm A to solve the problem grows much more quickly than Algorithm B, so we conclude that B is better
39
Efficiency and Big O Notation
Now suppose the time complexity of a particular algorithm is
We could plot this curve and compare it to other timing functions However, we don't care about the exact function, we care about how quickly it grows
The n2 term dictates the shape of this curve far more than any of the other terms
This is the dominant term of the function
Efficiency and Big O Notation
Compare various values of n and n
241
n n2
2 4
10 100
50 2,500
100 10,000
1,000 1,000,000 5,000 25,000,000
Compared to n2, the n term becomes irrelevant as n grows Coefficients of the terms are irrelevant as well
Efficiency and Big O Notation
Big O notation is used to simplify the discussion of efficiency
It eliminates the irrelevant details
Big O Category Name
O(1) Constant
O(log n) Logarithmic
O(n) Linear
O(n log n) Log-Linear O(n2) Quadratic O(2n) Exponential
The Big O category is based on the dominant term, and therefore the growth rate, of the time complexity function
Example: O(n) You say:
"Order n"
Efficiency and Big O Notation
Therefore, this timing function is a member of the quadratic class of algorithms
43
Big O categories can be thought of as defining the order of magnitude of an algorithm
Any two algorithms in the same Big O category are generally equal in terms of efficiency
Efficiency and Big O Notation
Comparing Big O terms for small n:
Efficiency and Big O Notation
For larger n:
45
Efficiency and Big O Notation
The ordering of the Big O categories is clear:
O(1) < O(log n) < O(n) < O(n log n) < O(n
2) < O(2
n) Algorithms with O(1) complexity are independent of the size of the input
Example: Finding the first element in a list
Logarithms are almost always base 2 for computer
problems, though that is largely irrelevant in terms of Big O analysis
If a problem cannot be solved in a reasonable amount of
for a moderate value of n, it is said to be intractable
Loop Analysis
The time complexity of an algorithm is a function that describes how long it takes to execute relative to it's input size (n)
Big O notation allows us to categorize time complexity functions by their dominant term
We're concerned about the growth rate (shape) of the curve as N increases
Let's examine a series of code fragments that use loops to determine their Big O complexity
47
Loop Analysis
The following loop executes n times:
The loop body takes a constant time (call it c) to execute So it's time complexity can be expressed as follows:
for (int i = 1; i <= n; i++) sum = sum + i;
This loop has linear complexity
The time it takes to execute grows in direct proportion to the input size n
Loop Analysis
This loop executes exactly 20 times:
49
Therefore, the entire loop has constant complexity
for (int i = 1; i <= 20; i++) sum = sum + i;
The loop is not a function of n (the input size)
If the literal 1000 was used (instead of 20), it would still be constant complexity
Loop Analysis
The amount of code in the loop body is not the issue
for (i = 1; i <= n; i++) {
sum = sum + i;
System.out.println(i);
if (sum > 50) sum = 0;
else
sum = sum * 2;
}
Each part of this loop body contributes constant time
Loop Analysis
Suppose k is calculated to be log2n
51
The loop executes log2n times, so:
for (int i = 1; i <= k; i++) System.out.println(i);
Even though n is not used explicitly, the number of executions is still a function of n
In Big O analysis, the logarithm base doesn't matter: O(log n) equals O(logan) for any base a
Loop Analysis
Now consider a nested loop:
The outer loop executes n times
For each iteration of the outer loop, the inner loop executes n times
for (i = 1; i <= n; i++)
for (j = 1; j <= n; j++)
System.out.println(i + j);
So this code has quadratic complexity
If the input size doubles, the execution time quadruples
Loop Analysis
Not all nested loops have quadratic complexity
53
The number of iterations of the inner loop is not a function of n for (i = 1; i <= n; i++)
for (j = 1; j <= 10; j++)
System.out.println(i + j);
Loop Analysis
In this code, the inner loop is controlled by the state of the outer loop:
The outer loop executes n times
The inner loop executes 1 time on the first iteration of the outer loop, 2 times the second, 3 times the third, and so on
for (i = 1; i <= n; i++)
for (j = 1; j <= i; j++)
System.out.println(i + j);
Loop Analysis
The time complexity of this code is
55
So the code has quadratic complexity
Remember, Big O analysis is focused on the general shape of the curve