Introduction to Computer Science

Start Lecture #10

Remark: The username for the homework solutions is introCS (case sensitive). The password is [to be given orally in class].

Homework: 6.1,

6.7: Returning an Array from a Method

public static int[] reverse(int[] a) {
  int[] b = new int[a.length];
  for (int i=0; i<a.length; i++) {
    b[a.length-1-i] = a[i];
  }
}

We have seen how arrays can be passed into a method. They can also be the result of a method as shown on the right. Note the following points about reverse (which returns an array that has the same elements as its input but in reverse order).

Remark: Lab1 part4 assigned.

6.7.1: Case Study: Counting the Occurrences of Each Letter

Read

Homework: 6.3 (Hint: Declare a counts array of 100 integers. When you read in x, execute counts[x]++.)

6.7.1 (Alternate) Case Study: Histogram of Math.random()

import java.util.Scanner;
public class Histogram {
  public static void main(String[] args) {
    System.out.printf("How many random numbers? ");
    Scanner getInput = new Scanner(System.in);
    int numRandom = getInput.nextInt();
    int[] count = {0,0,0,0,0,0,0,0,0,0};
    double[] limit = {0.0,0.1,0.2,0.3,0.4,0.5,0.6,
                      0.7,0.8,0.9,1.0};
    for(int i=0; i<numRandom; i++) {
      double y = Math.random();
      for (int j=0; j<10; j++) {
        if (y < limit[j+1]) {  // one will succeed
          count[j]++;
          break;
        }
      }
    }
    for (int j=0; j<10; j++) {
      System.out.printf("The number of random numbers "
            + "between %3.1f and %3.1f is %d\n",
            limit[j], limit[j+1], count[j]);
    }
  }
}

If Math.random() is any good, about 1/10 of the values should be less than 0.1, about 1/10 between 0.1 and 0.2, etc.

The program on the right tests that out by taking a bunch of numbers returned by Math.random() counting how many fall in each 0.1 range.

Note the following points.

The program can be downloaded from here.

Lets compile and run it for different number of random numbers and see what happens.

6.8: Variable-Length Argument Lists

public class VarArgsDemo {
  public static void main(String args[]) {
    printMax(34, 3, 3, 2, 56.5);
    printMax(new double[]{1, 2, 3});
  }
  public static void printMax(double... numbers) {
    if (numbers.length == 0) {
      System.out.println("No argument passed");
      return;
    }
    double result = numbers[0];
    for (int i = 1; i < numbers.length; i++)
      if (numbers[i] > result)
        result = numbers[i];
    System.out.println("The max value is " + result);
  }
}

We have used System.out.printf() which has a variable number of arguments and the types of the arguments cannot be determined until run time.

I don't believe you can write printf() in Java since Java does not support such a general version of varargs. Of course that does not prevent you from using printf().

Java does permit the last parameter to be of varying length but all of the arguments corresponding to that parameter must be of the same type. Java treats the parameter as an array.

The example on the right is from the book.

Pay attention to the parameter of printMax. Java uses ellipses to indicate varargs. In this case a variable number of double arguments are permitted and will all be assigned to the array double... numbers.

Other non-vararg parameters could have preceded the double... numbers parameter.

Homework: 6.13

6.9: Searching Arrays

public static int search(int[] A, int val)

Given an array A and a value val, find an index i such that A[i]==val.

What if there is more than one i that works?
Normally, we just report one of them.

What if there are no is that work?
We must indicate this in some way, often we return -1, which cannot be an index (all Java arrays have index starting at 0).

What if we want to search integers and also search reals?
We define two overloaded searches.

6.9.1: The Linear Search Approach

public static int linearSearch(int[] A, int val) {
  final int NOT_FOUND = 1;
  for (int i=0; i<A.length; i++)
    if (A[i] == val)
      return i;
  return NOT_FOUND;
}

There is an obvious solution: Try A[0], if that fails try A[1], etc. If they all fail return -1. This is shown on the right.

The only problem with this approach is that it is slow if the array is large. If the item is found, it will require on average about n/2 loop iterations for an array of size n and if the item is not found, n iterations are required.

Homework: 6.15

6.9.2: The Binary Search Approach

mid = (hi + lo) / 2; // 1.
if (A[mid]==val)
  return mid;  // 2.
else if (A[mid]<val)
  lo = mid + 1;    // 3.
else // A[mid]>val
  hi= mid-1;    // 4.

public static int binarySearch(int[]A, int val) { final int NOT_FOUND = -1; int lo = 0; int hi = A.length -1; int mid = (hi+lo)/2; while (lo <= hi) { // 5 // above if-then-else } return NOT_FOUND; // 6 }

If the array is unsorted, there is nothing better than the above. But of course if we are doing many searches it pays to have the array sorted.

Given a sorted array, we can preform a divide and conquer attack. Assume the array is sorted in increasing order (smallest number first).

  1. Try the middle element.
  2. If it is the desired value, done.
  3. If the middle is smaller than desired, restrict to the bottom half.
  4. If the middle is larger than desired, restrict to the top half.
  5. Repeat the above.
  6. If you restrict to the empty set, the desired element is not there.

6.10: Sorting Arrays

Sorting is crucial. As we just saw, fast searching depends on sorting.

There are many sorting algorithms; we will learn only two, both are bad. That is, both take time proportional to N2, where N is the number of values to be sorted. Good algorithms take time proportional to N*logN. This doesn't matter when N is a few hundred, but is critically important when N is a few million.

Moreover, serious sorting does not assume that all the values can be in memory at once.

Having said all this, sorting is important enough that it is quite worth our while to learn some algorithms. Naturally, it also helps us learn to translate algorithms into Java.

6.10.1: Selection Sort

Read.

6.10.A: Selection Sort (Alternate Version—Bubble Sort)

The basic idea in both the 8e and my selection sort is that you first find the minimum element and put it into the first slot (A[0]) and then repeat for the rest of the array (A[1]...A[A.length-1]).

for (j=0; j<A.length; j++)
  if (A[j] < A[0]) {
    temp = A[0];
    A[0] = A[j];
    A[j] = temp;
  }

The difference is in how you find the minimum and get it into A[0]. The code on the right shows the simple method used by bubble sort. The code in the book is very slightly more complicated and perhaps very slightly faster (both give bad, i.e., N2, sorts).

What remains is to wrap the code with another loop to find the 2nd smallest element and put it in A[1], etc.

Let's do that in class.

6.10.2: Insertion Sort

for (i=1; i<A.length; i++)
  // Insert A[i] correctly into A[0]..A[i-1]

Insertion sort is well described by the code on the right. The remaining question is how do we insert A[i] into A[0]..A[i-1] maintaining the sorted order?


In looking for the correct place to put A[i], we could either start with A[0] and proceed down the array or start with A[i-1] and proceed up the array. Why do we do the latter?

Consider an example where i is 20 and the right place to put A[i] is in slot A[10]. Where are we going to put A[10]?
Answer: in A[11]. Then where does A[11] go?
Answer: in A[12].

public static void insSort(int[] a) {
  for (int i=1; <a.length; i++) {
    int ai = a[i];
    int j = i-1;
    while (j>=0 && ai<a[j]) {
      a[j+1] = a[j];
      j--;
    }
    a[j+1] = ai;
  }
}

Thus we need to move the higher elements before we can move the lower. We can easily do this while searching for the correct spot to put A[i], providing we are traveling from higher to lower indices.

The code on the right deserves a few comments.

Homework: Eliminating duplicates. Write a method that accepts an array of integers and prints out the elements in sorted order with a count of how many times it appeared. For example if the array contains. 1,5,2,3,8,2,1,5,5 you would print
1 appears 2 times
2 appears 2 times
3 appears 1 time
5 appears 3 times
8 appears 1 time

6.11: The Arrays Class

The book notes that Java has many predefined methods for arrays. In particular java.util.Arrays.sort(a) would have sorted the array a (for any primitive type, thanks to overloading).

The best place to see all the methods (and classes, and more) that are guaranteed to be in all Java implementations is http://download.oracle.com/javase/6/docs/api/ (really http://java.sun.com/javase/6/docs/api/).

Chapter 7: Multidimensional Arrays

7.1: Introduction

As we have seen, a one-dimensional arrays corresponds to a list of values of some type. Similarly, two-dimensional arrays correspond to tables of such values and arrays of higher dimension correspond to analogous higher dimensional tables.

7.2: Two-Dimensional Array Basics

7.2.1: Declaring Variables of Two-Dimensional Arrays and Creating Two-Dimensional Arrays

double [][] M; // M for Matrix
M = new double [2][3];
M[0][0]=4.1; M[0][1]=3; M[0][2]=4;
M[1][0]=6.1; M[1][1]=8; M[1][2]=3;

double [][] M = new double [2][3];
double [][] M = { {4.1, 3, 4}, {6.1, 8, 3} };
double [][] M = {{4.1, 3, 4}, {6.1, 8, 3}};

As with one-dimensional arrays, there are three steps in declaring and creating arrays of higher dimension.

  1. Declaring the array.
  2. Creating the array.
  3. Initializing the array (optional).
These can be done separately or can be combined.

On the right we see three possibilities, the analogue of what we did last chapter for one-dimensional (1D) arrays. I wrote the third possibility twice using different spacings.

In fact there are other possibilities since a 2D array in Java is really a 1D array of 1D arrays as we shall see in the very next section.

7.2.2: Obtaining the Lengths of Two-Dimensional Arrays

The simple diagram on the right is very important. It illustrates what is meant by the statement that Java does not have native 2D arrays. Instead, Java 1D arrays can have elements that are themselves 1D arrays.

2d-matrix

The M in the diagram is the same as in the example code above.

Note first of all, that we again have reference semantics for the arrays. Specifically, M refers to (points to) the 1D array of length two in the middle of the diagram. Similarly M[0] refers to the top right 1D array of size 3. However, M[0][0] is a primitive type and thus it IS (as opposed to refers to) a double.

Next note that there are three 1D arrays in the diagram (M, M[0], and M[1]) of lengths, 2, 3, and 3 respectively.

Finally, note that you cannot write M[1,0] for M[1][0], further showing that Java does not have 2D arrays as a native type.

7.2.3: Ragged Arrays

ragged

One advantage of having 1D arrays of 1D arrays rather than true 2D arrays is that the inner 1D arrays can be of different lengths. For example, on the right, M is a length 1 array, M[0], is a length 3 array, and M[1] is a length 1 array.

Text that does not have its right margin aligned is said to have ragged right alignment. Since the right hand boundary of the component 1D arrays look somewhat like ragged right text, arrays such as M are called ragged arrays.

double [][] M = { {3.1,4,6},  {5.5} };

double [][] M; M = new double [2][]; // the 2 is needed M[0] = new double [3]; M[1] = new double [1]; M[0][0]=3.1; M[0][1]=4; M[0][2]=6; M[1][0]=5.5;

There are several ways to declare, create, and initialize a ragged array like M. The simplest is all at once as on the upper right.

The most explicit is the bottom code. Note how clear it is that M has length 2, M[0] has length 3, and M[1] has length 1.

Intermediates are possible. For example the first two lines can be combined. Another possibility is to combine the initialization of M[0] with its creation.

7.3: Processing Two-Dimensional Arrays

for (int i=0; i<n; i++) {  | for (int i=0; i<M.length; i++) {
  for(int j=0; j<m; j++) { |   for (int j=0; j<M[i].length; j++) {
    // process M[i][j]     |     // process M[i][j]
  }                        |     }
}                          |   }

On the far right is the generic code often used when processing a single 2D array (I know 2D arrays are really 1D arrays of 1D arrays). This code works fine for ragged and non-ragged (often called rectangular) arrays. Note that the bound in the inner loop depends on i.

When the array is known to be rectangular (the most common case) with dimensions n and m, the simpler looking code on the near right is used.

7.3.A: Some Examples

double maximum = Matrix[0][0]
double minimum = Matrix[0][0];
double sum = 0;
for (int i=0; i<n; i++)
  for (int j=0; j<m; j++) {
    if (Matrix[i][j]>maximum)
      maximum = Matrix[i][j];
    else if (Matrix[i][j]<minimun)
      minimum = Matrix[i][j];
    sum += Matrix[i][j];
  }

Max, Min, and sum of a Rectangular n by m Array Matrix

These are quite easy since the computation for each element of the matrix is independent of all other elements. The idea is simple, use 2 nested loops to index through all the all the elements. The loop body contains the computation for the generic element.

For a possible ragged array, simply replace n by Matrix.length and replace n by Matrix[0].length.

Initializing a (Possibly Ragged) Array with Random Integers from 1 to N

for (int i=0; i<A.length; i++)
  for (int j=0; j<A[0].length; j++)
    A[i][j] = 1 + (int)((N+1)*Math.random());

We use the nested loop construction appropriate for ragged arrays. The body requires remembering how to scale Math.random() to produce numbers from 1 to N.

The reason for (int) is in case the matrix A is floating point (but we want integer values).

7.4: Passing Two-Dimensional Arrays to Methods

Read