Module 3: Supplemental Material
Selection
Let's examine the code we wrote for finding the smallest element
in an array:
static int findSmallest (int[] A)
{
// Start by assuming first is smallest.
int smallest = A[0];
// Check against A[1], A[2] ... etc.
for (int i=1; i < A.length; i++) {
if (A[i] < smallest) {
smallest = A[i];
}
}
return smallest;
}
Let's look at two variations:
(source file)
static int findSmallest2 (int[] A)
{
// Start with the largest possible int value.
int smallest = Integer.MAX_VALUE;
// Check against A[0], A[1], ...
for (int i=0; i < A.length; i++) {
if (A[i] < smallest) {
smallest = A[i];
}
}
return smallest;
}
static int findSmallest3 (int[] A)
{
// Robustness checks: check that array can be referenced.
if ( (A == null) || (A.length == 0) ) {
// ... need to take action ...
// Return -1? System.exit?
}
// Start by assuming first is smallest.
int smallest = A[0];
// Check against A[1], A[2] ... etc.
for (int i=1; i < A.length; i++) {
if (A[i] < smallest) {
smallest = A[i];
}
}
return smallest;
}
Note:
- In the first variation:
- We set the initial value of the smallest to the largest
possible integer value.
- This way, we are assured that at least one array value,
if not all, will be less.
- In the second:
- We check to make sure the parameter has something in it.
- For example, if it were called as
int[] A; // Not assigned anything
findSmallest (A); // Will crash.
- The crash occurs inside findSmallest() at this point:
for (int i=0; i < A.length; i++) {
// ...
}
If A == null then the attempt to access
A.length will cause an exception.
Exercise 1:
Answer these questions:
- Consider findSmallest(). Suppose we set
int smallest = A[A.length-1];
What would we have to change in the for-loop to make it work?
- In findSmallest2() above, what would go wrong
if we mistakenly set smallest as follows?
int smallest = Integer.MIN_VALUE;
- Modify findSmallest3() so that the first line is:
if ( (A.length == 0) || (A == null) {
// ... need to take action ...
}
Try this out with a null array and see what happens.
Why did the original form of the if-condition work?
Selection Sort
Recall the code for Selection-Sort (for arrays of int's):
(source file)
static void selectionSort (int[] A)
{
// We don't need to find the n-th smallest, so stop at n-1.
for (int i=0; i < A.length-1; i++) {
// Find i-th smallest and swap.
int smallest = A[i];
int pos = i;
// Look from i+1 and up.
for (int j=i+1; j < A.length; j++) {
if (A[j] < smallest) {
smallest = A[j];
pos = j;
}
}
// Swap into position i.
int temp = A[i];
A[i] = A[pos];
A[pos] = temp;
}
Exercise 3:
What would happen if the comparison
if (A[j] < smallest) {
were changed to
if (A[j] <= smallest) {
in the if-statement?
Also, put the code for swapping in a separate swap method.
We've sorted 1D arrays so far. Can one sort a 2D array?
For fun, let's sort an image:
- We'll examine a greyscale image and sort so that the pixels in
row-column order appear in order of intensity.
- To keep the code simple, we'll use pixels[i][j][1]
(the Red value) as the value for comparisons.
- The sorting algorithm is: Selection-Sort.
Here's the program:
(source file)
import java.awt.*;
import java.awt.image.*;
public class SortPixels {
public static void main (String[] argv)
{
ImageTool imTool = new ImageTool ();
Image image = imTool.readImageFile ("FamousPerson.jpg");
imTool.showImage (image, "Original");
Image sortedImage = sort (image);
imTool.showImage (sortedImage, "Sorted pixels");
}
static Image sort (Image image)
{
ImageTool imTool = new ImageTool ();
int[][][] pixels = imTool.imageToPixels (image);
// We'll do a selection sort.
for (int i=0; i < pixels.length; i++) {
for (int j=0; j < pixels[i].length; j++) {
// Find (i,j)-th minimum, using only the R value.
int min = pixels[i][j][1];
int pos1 = i, pos2 = j;
// For i-th row, we have to go along row.
for (int n=j+1; n < pixels[i].length; n++) {
if (pixels[i][n][1] < min) {
min = pixels[i][n][1];
pos1 = i;
pos2 = n;
}
}
// Now from row i+1 onwards.
for (int m=i+1; m < pixels.length; m++) {
for (int n=0; n < pixels[m].length; n++) {
if (pixels[m][n][1] < min) {
min = pixels[m][n][1];
pos1 = m;
pos2 = n;
}
}
}
// Now swap into (i,j) place.
for (int k=0; k < 4; k++) {
int temp = pixels[i][j][k];
pixels[i][j][k] = pixels[pos1][pos2][k];
pixels[pos1][pos2][k] = temp;
}
} //end-for-jj
// Because it runs slowly, we'll print updates ...
System.out.println ("Finished row i=" + i);
} //end-for-i
return imTool.pixelsToImage (pixels);
}
}
Exercise 3:
Run the above program on this image.
See if the sorted result makes sense. Can you identify the person
in the image?
If that seemed confusing, let's try something simpler:
- We'll sort the rows of a 2D array and use the sum-of-elements
in a row as the deciding factor.
=>
The row with the smallest row-sum will be first.
- We will use Selection-sort.
Here's the program:
(source file)
public class RowSort {
public static void main (String[] argv)
{
// Some test data:
int[][] A = {
{1, 0, 1},
{1, 1, 1},
{1, 0, 0},
{0, 0, 0}
};
// Before:
print (A);
rowSort (A);
// After:
print (A);
}
static void rowSort (int[][] X)
{
// Simple Selection Sort. We are sorting only rows.
for (int row=0; row < X.length; row++) {
// This is the minimum so far.
int min = rowSum (X[row]);
int bestRow = row;
// Scan remainder to see if there's a better one.
for (int row2=row+1; row2 < X.length; row2++) {
int sum = rowSum (X[row2]);
if (sum < min) {
min = sum;
bestRow = row2;
}
}
// This is a pointer swap: X[row] is NOT an int. It's a pointer to int[].
int[] temp = X[row];
X[row] = X[bestRow];
X[bestRow] = temp;
}
}
static int rowSum (int[] Y)
{
// Sum of elements of a row.
int sum = 0;
for (int i=0; i < Y.length; i++) {
sum += Y[i];
}
return sum;
}
static void print (int[][] X)
{
// ... straightforward ...
}
}
Note:
- Look carefully at this line:
int min = rowSum (X[row]);
The parameter being passed is an entire row (an array of
int's). This is possible because of the pointer
implementation in Java.
- Similarly, pay close attention to the swap:
int[] temp = X[row];
X[row] = X[bestRow];
X[bestRow] = temp;
Here, whole rows are being swapped (using a pointer swap).
- We've deliberately used the variables row and
row2 here to emphasize that Selection-Sort has
two scans across whatever's being sorted (the rows, in this case).
Exercise 4:
Does the same idea work for sorting columns? Download
ColumnSort.java and implement
a column sort: sort the columns by column-sum (the sum of
elements in a column).
Sorting objects
Let's return to the example where we sorted objects and make
a few modifications:
- Recall, we defined a Person object: (source file)
class Person {
// Some data:
String firstName;
String lastName;
int birthYear;
// ...
}
- In that example, we sorted by age.
- We'll now allow sorting by age or by name (alphabetically).
Here's the (somewhat complicated) program:
(source file)
// The class we define for "Person" objects:
class Person {
// Some data:
String firstName;
String lastName;
int birthYear;
// We'll use this to decide what kind of sorting we want.
public boolean sortByAge = false;
// This is called a constructor:
public Person (String firstName, String lastName, int birthYear)
{
this.firstName = firstName;
this.lastName = lastName;
this.birthYear = birthYear;
}
// To allow for printing via System.out.println:
public String toString ()
{
return "Person: " + firstName + " " + lastName + " (b. " + birthYear + ")";
}
// A comparison method to allow two Person instances to be compared.
public boolean lessThan (Person p)
{
if (sortByAge) {
// Compare ages.
if (birthYear < p.birthYear) {
return true;
}
else {
return false;
}
}
else {
// Sort alphabetically by name, starting with lastname.
if ( lastName.compareTo (p.lastName) < 0 ) {
return true;
}
else if ( lastName.compareTo (p.lastName) > 0 ) {
return false;
}
// If last names are equal, try first names.
if ( firstName.compareTo (p.firstName) < 0 ) {
return true;
}
else if ( firstName.compareTo (p.firstName) > 0 ) {
return false;
}
// If both names are equal, use age.
if (birthYear < p.birthYear) {
return true;
}
else {
return false;
}
}
}
} //end-Person
public class ObjectSortExample2 {
public static void main (String[] argv)
{
Person[] someKennedys = new Person [10];
Person p = new Person ("Patrick", "Kennedy", 1858);
System.out.println ("First one: " + p);
someKennedys[0] = p;
// Do the others:
someKennedys[1] = new Person ("Joseph", "Kennedy", 1888);
someKennedys[2] = new Person ("Rose", "Fitzgerald", 1890);
someKennedys[3] = new Person ("Joseph", "Kennedy", 1915);
someKennedys[4] = new Person ("John", "Kennedy", 1917);
someKennedys[5] = new Person ("Jacqueline", "Bouvier", 1929);
someKennedys[6] = new Person ("Robert", "Kennedy", 1925);
someKennedys[7] = new Person ("Edward", "Kennedy", 1932);
someKennedys[8] = new Person ("Caroline", "Kennedy", 1957);
someKennedys[9] = new Person ("Maria", "Shriver", 1955);
System.out.println ("Unsorted: ");
print (someKennedys);
System.out.println ("Sorted: ");
sort (someKennedys);
print (someKennedys);
// We want to sort by age, so set that flag in all instances.
for (int i=0; i < someKennedys.length; i++) {
someKennedys[i].sortByAge = true;
}
System.out.println ("Sorted by age: ");
sort (someKennedys);
print (someKennedys);
}
static void print (Person[] people)
{
for (int i=0; i < people.length; i++) {
// people[i] is a Person instance.
System.out.println (people[i]);
}
}
static void sort (Person[] people)
{
// Selection Sort.
for (int i=0; i < people.length-1; i++) {
// Find i-th min and swap.
Person min = people[i];
int pos = i;
// Scan the rest.
for (int j=i+1; j < people.length; j++) {
if ( people[j].lessThan (min) ) {
min = people[j];
pos = j;
}
}
// Swap.
Person temp = people[i];
people[i] = people[pos];
people[pos] = temp;
}
}
}
The are many things to note:
- We've now added a flag to let us decide whether to sort by age
or by name:
// We'll use this to decide what kind of sorting we want.
public boolean sortByAge = false;
- Notice how a "constructor" is defined:
public Person (String firstName, String lastName, int birthYear)
{
this.firstName = firstName;
this.lastName = lastName;
this.birthYear = birthYear;
}
- It's like a method (with parameters)
- It must be public and cannot have a return type declared.
- A constructor executes only once per instance when the
instance is created.
- There's a lot more to constructors; we've only introduced
them here. See the advanced Java
material for more details.
- There's a special method defined called toString():
public String toString ()
{
return "Person: " + firstName + " " + lastName + " (b. " + birthYear + ")";
}
- Java treats this a little differently.
- A little later in main, you see the line
Person p = new Person ("Patrick", "Kennedy", 1858);
System.out.println ("First one: " + p);
- Whenever an object is given to System.out.println()
or invoked where there should be a String, Java
automatically calls the object's toString() method.
- This is useful for debugging, when we want to dump an
object's contents to screen.
- We don't have to write
System.out.println (p.firstName);
System.out.println (p.lastName);
System.out.println (p.birthYear);
- Instead, we write
System.out.println (p);
- Moving on (in main), we see that a sort-by-name
occurs first. Then we set sortByAge=true in each instance,
and sort again.
- We use a simple Selection-Sort.
- To compare two Person instances, we simply
call the lessThan() method we defined for one of them,
and pass the other as parameter:
// "min" is of type Person.
if ( people[j].lessThan (min) ) {
// ...
}
- We needed a Person variable for the swap:
// Swap.
Person temp = people[i];
people[i] = people[pos];
people[pos] = temp;
Now for something more complicated (but more useful):
- We will make our Person object satisfy the
interface called Comparable in the Java library.
class Person implements Comparable {
// ...
}
- This requires us to implement a method called compareTo():
class Person implements Comparable {
// ...
public int compareTo (Object d)
{
// ...
}
}
- Why should we do this?
- By making our class Person implement the
Comparable, we are in effect saying that it can
behave like a Comparable object.
- The only behavior required of a Comparable object
is to have the method
public int compareTo (Object d)
{
// ...
}
implemented correctly.
- Here, "correctly" means: proper comparison of Person instances.
- Once this is done, we can take advantage of any sorting
algorithm written for Comparable objects.
- For example, Java's own sorting algorithm in the
Arrays class:
public static void sort (Object[] A)
- The code below uses Java's sorting algorithm.
Here's the program:
(source file)
import java.util.*;
class Person implements Comparable<Person> {
// Some data:
String firstName;
String lastName;
int birthYear;
// We'll use this to decide what kind of sorting we want.
public boolean sortByAge = false;
// This is called a constructor:
public Person (String firstName, String lastName, int birthYear)
{
this.firstName = firstName;
this.lastName = lastName;
this.birthYear = birthYear;
}
// To allow for printing via System.out.println:
public String toString ()
{
return "Person: " + firstName + " " + lastName + " (b. " + birthYear + ")";
}
// A comparison method to allow two Person instances to be compared.
public int compareTo (Person d)
{
// Check if object passed in is of correct type.
if (! (d instanceof Person) ) {
return -1;
}
// Cast to a Person variable so that we can access fields.
Person p = (Person) d;
if (sortByAge) {
// Compare ages.
if (birthYear < p.birthYear) {
return -1;
}
else if (birthYear > p.birthYear) {
return 1;
}
else {
return 0;
}
}
else {
// Sort alphabetically by name, starting with lastname.
if ( lastName.compareTo (p.lastName) < 0 ) {
return -1;
}
else if ( lastName.compareTo (p.lastName) > 0 ) {
return 1;
}
// If last names are equal, try first names.
if ( firstName.compareTo (p.firstName) < 0 ) {
return -1;
}
else if ( firstName.compareTo (p.firstName) > 0 ) {
return 1;
}
else {
return 0;
}
}
}
} //end-Person
public class ObjectSortExample4 {
public static void main (String[] argv)
{
Person[] someKennedys = new Person [10];
Person p = new Person ("Patrick", "Kennedy", 1858);
System.out.println ("First one: " + p);
someKennedys[0] = p;
// Do the others:
someKennedys[1] = new Person ("Joseph", "Kennedy", 1888);
someKennedys[2] = new Person ("Rose", "Fitzgerald", 1890);
someKennedys[3] = new Person ("Joseph", "Kennedy", 1915);
someKennedys[4] = new Person ("John", "Kennedy", 1917);
someKennedys[5] = new Person ("Jacqueline", "Bouvier", 1929);
someKennedys[6] = new Person ("Robert", "Kennedy", 1925);
someKennedys[7] = new Person ("Edward", "Kennedy", 1932);
someKennedys[8] = new Person ("Caroline", "Kennedy", 1957);
someKennedys[9] = new Person ("Maria", "Shriver", 1955);
System.out.println ("Unsorted: ");
print (someKennedys);
System.out.println ("Sorted: ");
// Java's sort method.
Arrays.sort (someKennedys);
print (someKennedys);
// We want to sort by age, so set that flag in all instances.
for (int i=0; i < someKennedys.length; i++) {
someKennedys[i].sortByAge = true;
}
System.out.println ("Sorted by age: ");
// Java's sort method:
Arrays.sort (someKennedys);
print (someKennedys);
}
static void print (Person[] people)
{
// ...
}
}