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:
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):
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:
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 4:
Implement and 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:
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 5:
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:
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:
// 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:
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)
{
// ...
}
}
© 2006-2020, Rahul Simha & James Taylor (revised 2020)