Arrays in Java: Some Examples of Program Development

Array Sum

Suppose that you had a list of numbers and wanted to find the sum. Assuming that each number was relatively small in magnitude and the list was relatively short (so that even the sum would be relatively small in magnitude), one reasonable approach would be to keep in mind a "sum-so-far", initially zero, and then examine the numbers in the list one by one, each time adding the "current" number to the sum-so-far.

current
number
sum-so-far
-0
44
1216 (i.e., 4+12)
-610 (i.e., 4+12+-6)
212 (i.e., 4+12+-6+2)
517 (i.e., 4+12+-6+2+5)
-314 (i.e., 4+12+-6+2+5+-3)

For example, suppose that the list were ⟨ 4 12 -6 2 5 -3 ⟩. Then you would go through a mental process as described by the table to the right:

The first row shows the initial situation, before examining any of the numbers in the list. Each subsequent row shows the number currently being examined and the result of adding it to the "sum-so-far". Obviously, by the time we have examined the last number and added it to the sum-so-far, the sum-so-far is equal to the sum of all the numbers in the list. (In fact, more generally you can say that, at any given moment, the sum-so-far value is equal to the sum of the numbers that have been examined up to that moment.)

We can mimic this process using Java in a straightforward way, as expressed in the following method. The role of the "list of numbers" is played by the parameter a[], which is an array of values of type int. (Of course, the same logic would work just as well with an array of type double[].)

/* Returns the sum of the elements in the specified array (a[]).
*/
public static int sumOf(int[] a)
{
   int sumSoFar = 0;
   // loop invariant: sumSoFar = sum of elements in a[0..i-1]
   for (int i=0; i != a.length; i = i+1)
   {
      sumSoFar = sumSoFar + a[i];
   }
   return sumSoFar;
}

A more versatile version of the sumof() method would allow a caller to identify a segment of the array to be summed. (An array segment is simply a contiguous portion of an array.) In analogy to the substring() method of the class String, we identify an array segment by referring to the location at which it begins and the location immediately after where it ends.

/* Returns the sum of the elements in the specified array segment,
** namely a[low..high).
** Precondition: 0<=low<=high<=a.length
*/
public static int sumOf(int[] a, int low, int high)
{
   int sumSoFar = 0;
   // loop invariant: sumSoFar = sum of elements in a[low..i)
   for (int i=low; i != high; i = i+1)
   {
      sumSoFar = sumSoFar + a[i];
   }
   return sumSoFar;
}

Having developed this more versatile version of sumOf(), we should replace the original one by the following:

/* Returns the sum of the elements in the specified array (a[]).
*/
public static int sumOf(int[] a) { return sumOf(a, 0, a.length); }


Array Maximum

Now we turn to a similar problem, that of finding the maximum value in an array. Again consider how you might do this "manually" if given a list of numbers. As with finding the sum, here you would examine each number in the list, one by one. But this time, rather than adding the current number to the sum-so-far, you would compare it to a max-so-far value that you were keeping in mind. If the current value exceeded that of the max-so-far value, you would replace the latter with the current number, as now it qualifies as the maximum of the numbers seen so far.

For example, suppose that the list were ⟨ 4 7 0 2 9 -3 ⟩. Then you would go through a mental process as described by this table:

current
number
max-so-far
44 (i.e., maximum of {4})
77 (i.e., maximum of {4,7})
07 (i.e., maximum of {4,7,0})
27 (i.e., maximum of {4,7,0,2})
99 (i.e., maximum of {4,7,0,2,9})
-39 (i.e., maximum of {4,7,0,2,9,-3})

Using the sumOf() method as a template, we modify it in the rather obvious way to obtain this:

/* Returns the maximum of the elements in the specified array (a[]).
** pre: a.length != 0
*/
public static int maxOf(int[] a)
{
   int maxSoFar = a[0];   
   // loop invariant: maxSoFar = maximum of the elements in a[0..i)
   for (int i=1; i != a.length; i = i+1)
   {
      if (a[i] > maxSoFar) { maxSoFar = a[i]; }
   }
   return maxSoFar;
}

One difference between the sumOf() and maxOf() methods is that, because there is no value that stands in the same relation to the max operator as zero does to the addition operator (namely, zero is the identity of addition, but max has no identity element), it is necessary to initialize the "max-so-far" variable to one of the array elements.

To generalize maxOf() to return the maximum element in an array segment, we would follow exactly the same steps as we did in generalizing sumOf() above. Specifically, we would replace 0, 1, and a.length by low, low+1, and high, respectively.

From a client's point of view, having a method that identifies the maximum value in an array could be useful. More useful, however, would be to have a method that identifies a location at which an array's maximum value occurs. After all, if you know where the maximum is located, you can easily determine its value simply by referring to the array and using the location of the maximum as the subscript! That is, if r is a location of array b at which b's maximum occurs, the expression b[r] is that maximum value.

So let's develop a new method, locOfMax(), that identifies the lowest-numbered location of an array where the array's maximum value occurs. Given that we already have the maxOf() method, perhaps the most obvious solution is this:

/* Returns the lowest-numbered location at which occurs the maximum
** value of the specified array (a[]).
*/
public static int locOfMax(int[] a)
{
   int maxVal = maxOf(a);   // determine the maximum value in a[]

   // Now search a[] until we find that maximum value
   int i=0;
   // loop invariant: maxVal does not occur in a[0..i)
   while (a[i] != maxVal) 
      { i = i+1; }
   return i;
}

This works, but notice that determining the maximum requires a complete "pass" over the array and then finding its first occurrence requires at least a partial pass and up to a full pass. If, during the first pass, we remember not only the max-so-far value but also its location, no second pass would be necessary. Following this idea, and also assuming that the array's length is non-zero, we get:

/* Returns the lowest-numbered location containing a maximum of 
** the elements in the specified array (a[]).
*/
public static int locOfMax(int[] a)
{
   int maxSoFar = a[0];
   int locOfMaxSoFar = 0;
   // loop invariant: maxSoFar = maximum of the elements in a[0..i)  &&
   //                 maxSoFar = a[locOfMaxSoFar]
   for (int i=1; i != a.length; i = i+1)
   {
      if (a[i] > maxSoFar) { 
         maxSoFar = a[i];  locOfMaxSoFar = i;
      }
   }
   return locOfMaxSoFar;
}

But notice that maxSoFar = a[locOfMaxSoFar] is an invariant of the loop. Which means that wherever the value of maxSoFar is used, we could just as well use a[locOfMaxSoFar]. Which is to say that we can write the method without the maxSoFar variable, like this:

/* Returns the lowest-numbered location containing a maximum of 
** the elements in the specified array (a[]).
*/
public static int locOfMax(int[] a)
{
   int locOfMaxSoFar = 0;
   // loop invariant: a[locOfMaxSoFar] = maximum of the elements in a[0..i)
   for (int i=1; i != a.length; i = i+1)
   {
      if (a[i] > a[locOfMaxSoFar]) { 
         locOfMaxSoFar = i;
      }
   }
   return locOfMaxSoFar;
}

Generalizing this (as we did for sumOf()) to allow a caller to ask for the location of a maximum within an array segment, we get this:

/* Returns the lowest-numbered location within a[low..high)
** occupied by a maximum of that segment.
*/
public static int locOfMax(int[] a, int low, int high)
{
   int locOfMaxSoFar = low;
   // loop invariant: a[locOfMaxSoFar] = maximum of the elements in a[low..i)
   for (int i=low+1; i != high; i = i+1)
   {
      if (a[i] > a[locOfMaxSoFar]) { 
         locOfMaxSoFar = i;
      }
   }
   return locOfMaxSoFar;
}

So now we can write the non-generalized version so that it makes use of the generalized version:

/* Returns the lowest-numbered location containing a maximum of 
** the elements in the specified array (a[]).
*/
public static int locOfMax(int[] a) { return locOfMax(a, 0, a.length); }

Having written the generalized and non-generalized versions of locOfMax() in this way, now we can write proper versions of maxOf():

/* Returns the maximum of the elements in the specified array 
** segment (a[low..high-1]).
*/
public static int maxOf(int[] a, int low, int high) { 
   return a[locOfmax(a, low, high)]; 
}

/* Returns the maximum of the elements in the specified array (a[]).
*/
public static int maxOf(int[] a) { return maxOf(a,0,a.length); }


Array Reversal

Now we consider the problem of reversing the elements in an array. That is, we want to modify the contents of an array so that, in the end, the values are in reverse order relative to their original order.

For example, if initially the array were

   0    1    2    3    4    5    6    7  
+----+----+----+----+----+----+----+----+
|  5 | 12 | -7 |  2 |  0 | 14 | -1 |  3 |
+----+----+----+----+----+----+----+----+
then, at the end, we would want it to be

   0    1    2    3    4    5    6    7  
+----+----+----+----+----+----+----+----+
|  3 | -1 | 14 |  0 |  2 | -7 | 12 |  5 |
+----+----+----+----+----+----+----+----+

One way to achieve this result is to carry out a bunch of swap operations. This term is commonly used to refer to the act of interchanging the values of two variables. In particular, we would swap the values at these pairs of locations: (0,7), (1,6), (2,5), and (3,4).

Do you detect a pattern here? Each pair of locations has the same sum, 7, which, not coincidentally, is one less than the array's length. Try it with an array of any length and you will see that the same pattern holds. Indeed, if the array's length is N (and thus its index range is 0..N−1), the pairs of locations whose values should be swapped is (0,N−1), (1,N−2), (2,N−3), ...., each of which adds up to N−1. Thus, letting i be the first (and smaller) component of any such pair of locations, the second component j must satisfy both i+j = N−1 and i < j. Solving the former for j, we get j = N−1−i. Thus, each pair of locations whose values should be swapped is of the form (i, N−1−i).

Given that j = N−1−i, the inequality i < j translates to i < N−1−i, which is equivalent to 2i < N−1. All this analysis leads to the following Java method, which employs the yet-to-be-written swap() method:

/* Reverses the elements in the specified array (a[]).
*/
public static int reverse(int[] a)
{
   final int N = a.length;
   for (int i=0; 2*i < N-1; i = i+1)
   {
      swap(a, i, N-1-i);
   }
}

As the reader should have surmised, the method call swap(b,k,m) is intended to have as its effect to swap the elements at locations k and m of array b[].

Before we deal with the design of the swap() method, let's develop an alternative version of reverse(). Above we reasoned that every "swap location pair" (i,j) must satisfy i < j and j = N−1−i. We used these to determine the loop guard and the second argument to be passed to the swap() method.

But we could just as well use j as a program variable and maintain its value so that j = N−1−i is an invariant. Here is the new version of reverse(), which uses a for-loop having two loop-control variables:

/* Reverses the elements in the specified array (a[]).
*/
public static int reverse(int[] a) {
   final int N = a.length;
   for (int i=0, j=N-1; i < j; i = i+1, j = j-1) {
      swap(a, i, j);
   }
}

Notice that, within the for-loop heading, the first and third components can be a sequence of statements separated by commas.

An equivalent method that uses a while-loop is as follows:

/* Reverses the elements in the specified array (a[]).
*/
public static int reverse(int[] a) {
   int i=0, j = a.length - 1;
   while (i < j) 
   {
      swap(a, i, j);
      i = i+1; j = j-1;
   }
}

As for swapping the values of two variables, suppose that you have variables x and y and you want to swap their values. Your first inclination might be to use the following pair of assignments:

x = y; y = x;

But that won't work, because not only will x end up with y's original value, but so will y! Why? Because the first assignment obliterates x's original value, replacing it with y's. Thus, the second assignment copies x's new value (which was y's original value) back into y!

To do it correctly, we need to employ an auxiliary variable to "remember" x's original value and then, after copying y's value into x, to assign the remembered value to y:

temp = x; x = y; y = temp;

Using the same idea but where the variables being swapped are array elements, we get the following method:

/* Swaps the elements in the specified locations (k and m) of
** the specified array (b[]).
*/
public static void swap(int[] b, int k, int m) {
   int temp = a[k];
   a[k] = a[m];
   a[m] = temp;
}

In analogy with the sumOf() and maxOf() examples, we can make the reverse() method more versatile by allowing a caller to specify an array segment that is to be reversed. Here it is:

/* Reverses the elements in the specified array segment (a[low..high)).
*/
public static int reverse(int[] a, int low, int high) {
   int i = low, j = high - 1;
   while (i < j) 
   {
      swap(a, i, j);
      i = i+1; j = j-1;
   }
}

For another variation, consider that the caller may not want to modify an existing array but rather to create a new array containing the same values as those in an existing array segment but in reverse order. Here is a method that does that:

/* Returns a new array whose elements are those in the specified array
** segment (a[low..high)) but in reverse order.
** pre: 0 <= low <= high <= a.length
*/
public static int[] reverseOf(int[] a, int low, int high) {
   final int N = high - low;   // length of array to be returned.
   int[] result = new int[N];  // create array to be returned

   // loop invariant: result[0..i-1] contains the elements in a[high-i..high-1]
   // in reverse order.
   for (int i=0; i != N; i = i+1) {  // Fill new array
      result[i] = a[high-1-i];
   }
   return result;   
}

In case you don't appreciate the difference between the effects of the methods reverse() and reverseOf(), here is an example. Suppose that we have an array junk[] as follows:

        0    1    2    3    4    5    6    7    8
     +----+----+----+----+----+----+----+----+----+
junk |  3 | 12 |  5 | -2 |  0 |  9 | 15 | -4 |  7 |
     +----+----+----+----+----+----+----+----+----+

Then making the call ArrayExamples.reverse(junk, 1, 7) (here we're assuming that the reverse() method is in the ArrayExamples class) will have the effect of reversing the order of the values occupying junk[1..6] so that it looks like this:

        0    1    2    3    4    5    6    7    8
     +----+----+----+----+----+----+----+----+----+
junk |  3 | 15 |  9 |  0 | -2 |  5 | 12 | -4 |  7 |
     +----+----+----+----+----+----+----+----+----+

On the other hand, the call ArrayExamples.reverseOf(junk, 1, 7) will leave the contents of junk[] undisturbed but will create, and return a reference to, a new array like this:

        0    1    2    3    4    5  
     +----+----+----+----+----+----+
     | 15 |  9 |  0 | -2 |  5 | 12 |
     +----+----+----+----+----+----+

Of course, the client would not have called the method in the first place unless it wanted to make use of the resulting array, so typically (but not always) such a call would occur on the right-hand side of an assignment statement. Typical client code might be like this:

garbage = ArrayExamples.reverseOf(junk, 1, 7);

This would assign to garbage a reference to the array shown above. This assumes, of course, that garbage has been declared to be a variable of type int[].