Second Summer 2002


Lecture Notes 18: Partially filled arrays. Array parameters and return values. Simple array algorithms.
Have we ever seen an array of Strings? Apparently main receives one as a parameter.

Really? Where do those Strings come from? From the command line.

Can you give me an example? That's what I like best:
frilled.cs.indiana.edu%java One one two three
Hello! You have 3 arguments on the command line.
Arg 0: one
Arg 1: two
Arg 2: three
Thank you!
frilled.cs.indiana.edu%

OK. Now how do you write this program? Here's how:
class One {
    public static void main(String[] args) {
	System.out.println("Hello! You have " + args.length + 
			   " arguments on the command line.");
	for (int i = 0; i < args.length; i++) {
	    System.out.println("Arg " + i + ": " + args[i]); 
	} 
	System.out.println("Thank you!"); 
    } 
}

Looks good. It usually does.

Now let's go back to our price check program. We have improved on it by asking the user to set the size first.

Yes, but I don't think it's reasonable to ask the user to count the items for us before entering them. After all, this is exactly the kind of work that the user expects the computer to do.

Unfortunately we now run into a problem. Yes, we need to set the size of the array before we know how many elements we need.

But notice how passing the command line arguments to main makes that transparent to you, as a user. Yes, we need to find a solution for our price check program too.

In Java once an array size is set, it cannot be changed. Other programming languages have smarter arrays that can grow on demand,

... and Java also has a Vector class that can overcome this problem. Unfortunately the Vector class is not as easy to use as an array.

We will discuss Vectors before too long. To solve this problem, you can sometimes make an array that is guaranteed to be larger than the largest possible number of entries,

... and then partially fill it. For example you can decide that the user will never need more than 1000 data points.

Then allocate an array of size 1000. Then keep a companion variable that tells how many elements in the array are actually used.

It is an excellent idea always to name this companion variable by adding the suffix Size to the name of the array. Here's the program so far.
class Two { 
    public static void main(String[] args) {
	final int DATA_LENGTH = 1000; 
	double[] price = new double[DATA_LENGTH]; 
	int priceSize = 0; /* first available index,
               also representing number of elements
	       being stored in the array already. */

    } 
}

Now price.length is the
capacity
of the array price
... and priceSize is the
current size
of the array.

Notice how starting with indexing at 0 gives us an alternative semantics for the index. The alternative semantics is that there are exactly i elements in the array stored before the array element price[i].

For any i, index of price. That is, for any i >= 0 and i < price.length

Keep adding elements in the array, incrementing the size variable each time. This way, priceSize always contains the correct element count as well as the next available index in the array.

Two meanings in one variable. When inspecting the array elements, though

... you must be careful to stop at priceSize, ... not at price.length

Also be careful not to overfill the array. Insert elements only if there is still room for them!

Here's what I have so far:
class Two { 
    public static void main(String[] args) {
	final int DATA_LENGTH = 1000; 
	double[] price = new double[DATA_LENGTH]; 
	int priceSize = 0; 
	ConsoleReader console = new ConsoleReader(System.in); 
	System.out.println("Hello, please start entering prices.");
	while (true) {
	    if (priceSize < price.length) {
		double data = console.readDouble();
		price[priceSize] = data;
		priceSize += 1;
		System.out.println("New element entered: " + data);
		for (int i = 0; i < priceSize; i++) {
		    System.out.println("** " + i + ": " + price[i]);
		}
	    } else {
		System.out.println("Sorry, ran out of memory!"); 
		break; 
	    } 
	} 

    } 
}

And how does it work? Here's how:
frilled.cs.indiana.edu%javac Two.java
frilled.cs.indiana.edu%java Two
Hello, please start entering prices.
3.45
New element entered: 3.45
** 0: 3.45
7.12
New element entered: 7.12
** 0: 3.45
** 1: 7.12
0.34
New element entered: 0.34
** 0: 3.45
** 1: 7.12
** 2: 0.34
5.00
New element entered: 5.0
** 0: 3.45
** 1: 7.12
** 2: 0.34
** 3: 5.0
^Cfrilled.cs.indiana.edu%

What happens if the array fills up? Then, there are two approaches you can take.

The simple way out is to refuse additional entries. That's what we have done above.

I have seen that. But, of course, refusing to accept all input is often unreasonable.

Users routinely use software on larger data sets than the original developers ever dreamt of. In Java, there is another approach to cope with data sets whose size cannot be estimated in advance.

When you run out of (allocated) space in an array ...you can create a new, larger array.

Can you also create a new smaller array? Yes, but that's the second case.

If you want to trim it. Let's get back to the array overflow case.

When you run out of allocated space in an array ... you can create a new, larger array;

copy all elements into the new array; and then attach the new array to the old array variable.

An array that grows on demand is often called a dynamic array . If you find that growing an array on demand is too tedious you can use vectors.(We'll get to that in next week's lectures).

We now have all the pieces together to implement the program. Here it is:
class Two { 
    public static void main(String[] args) {
	final int DATA_LENGTH = 1000; 
	double[] price = new double[DATA_LENGTH]; 
	int priceSize = 0; 
	ConsoleReader console = new ConsoleReader(System.in); 
	System.out.println("Hello, please start entering prices.");
	while (true) {
	    if (priceSize < price.length) {
		double data = console.readDouble();
		price[priceSize] = data;
		priceSize += 1;
		System.out.println("New element entered: " + data);
		for (int i = 0; i < priceSize; i++) {
		    System.out.println("** " + i + ": " + price[i] +
                                       " (" + price.length + ")");
		}
	    } else {
		double[] newData = new double[2 * price.length]; 
		for (int i = 0; i < price.length; i++) {
		    newData[i] = price[i];
		} 
		price = newData; 
	    } 
	} 

    } 
}

I think you should study this program carefully. Yes, it's a bit tricky.

The loop executes once every time ... except when the storage limit is reached it is executed one more time, quickly, to reallocate the array, then waits for user input.

How can we experience the reallocation of the array ... without having to set DATA_LENGTH to some small value?

Read it from the command line. Very good.
class Two { 
    public static void main(String[] args) {
	final int DATA_LENGTH = Integer.parseInt(args[0]); 
	double[] price = new double[DATA_LENGTH]; 
	int priceSize = 0; 
	ConsoleReader console = new ConsoleReader(System.in); 
	System.out.println("Hello, please start entering prices.");
	while (true) {
	    if (priceSize < price.length) {
		double data = console.readDouble();
		price[priceSize] = data;
		priceSize += 1;
		System.out.println("New element entered: " + data);
		for (int i = 0; i < priceSize; i++) {
		    System.out.println("** " + i + ": " + price[i] +
                                       " (" + price.length + ")");
		}
	    } else {
		double[] newData = new double[2 * price.length]; 
		for (int i = 0; i < price.length; i++) {
		    newData[i] = price[i];
		} 
		price = newData; 
	    } 
	} 

    } 
}

Of course, this program is for testing, not for distribution. Indeed. Let's now talk about trimming.

That's the easier case. Yes. We just create a new smaller array with size priceSize

... then copy all the elements into the new array. Then attach the new array to the old array variable. This way we keep the data and its size in just one place.

The array itself. But we assume the array won't change after that.

Methods often have array parameters. Such as main, for example.

This method computes the average of an array of floating point numbers. To visit each element of the array data, the method needs to determine the length of data.
public static double average(double[] data) {
  if (data.length == 0) return 0; 
  double sum = 0; 
  for (int i = 0; i < data.length; i++) 
    sum += data[i];
  return sum / data.length;
} 

It inspects all elements, with index starting at 0 ... and going up to, but not including, data.length.

Note that this method is read-only. It strives to be that way. If changes were made to the array the caller would see that.

How come? You pass arrays as if you're passing Rectangles.

Or any other object for that matter. The invoked method simply receives a copy of the array's address.

Or reference. When an array is passed to to a method, the array parameter

... double[] data in our case, ... contains a copy of the reference to the argument array.

The process is identical to that of copying array variables ... which we have discussed yesterday.

Or, to that of passing Rectangles as parameters to objects. Indeed.

Because an array parameter is just another reference to the array, .. a method can actually modify the entries of any array you give to it.

A method can also return an array. This is useful if a method computes a result that consists of a collection of values

... of the same type. Here's an example: a method ... that returns a random data set, perhaps to test a chart-plotting program.
public static int[] randomData(int length, int n) {
  Random generator = new Random(); 
  int[] data = new int[length]; 
  for (int i = 0; i < data.length; i++) 
    data[i] = generator.nextInt(n); 
  return data; 
} 

We will discuss several very common ... and very important

... array algoritms. More complex algorithms are described in chapter 15. We will also look at sorting before too long.

Meanwhile let's look how we find a value (also known as searching). Here's an example: suppose we want to find the first price that is lower than 1000 dollars.
int i = 0; 
boolean found = false; 
while (i < prices.length && !found) {
  if (prices[i] <= 1000) 
    found = true; 
  else 
    i += 1; 
} 
if (found) {
  System.out.println("Item " + i + " is the first."); 
} else {
  System.out.println("Not found."); 
} 

Note that the loop may fail to find an answer, namely if all prices are above $1,000. At the end of the loop though, either found is true, in which case prices[i] is the first price that satisfies our requirements,

or i is prices.length, which means that you searched the entire list without finding a match. So you have to give up smoking.

Or buy a lighter. Note though that you should not increment i if you had a match -- ... if you want to have the correct value of i after exiting the loop.

Next comes counting. Suppose you want to find out

... how many prices are below $1,000. TMTOWTDI, but here's one:
double[] prices; 
double targetPrice = 1000; 
// ... initialize the array
int count = 0;
for (int i = 0; i < prices.length; i++) {
  if (prices[i] <= targetPrice) 
    count += 1; 
} 
System.out.println(count + " prices under $1,000."); 

Yes.

Now you don't stop on the first match (if any)

... but keep going to the end of the list,

... counting how many entries do match. How do we remove an element?

There's more than one way to do it. I know, but what cases do you have in mind?

If the elements of the array are not in any particular order, ... simply overwrite the element to be removed with the last element of the array.

Unfortunately, an array cannot be shrunk to get rid of the last element. In this case, you can use the technique of a partially filled array together with a companion variable.

I don't like this method. Neither do I.

The situation is more complex if the order of the elements matters. Then you must move all the elements

... beyond the element to be removed ... by one slot.

Then trim the array. Let's implement that:
class Three {
    public static void main(String[] args) {
	int[] price = new int[Integer.parseInt(args[0])]; 
	for (int i = 0; i < price.length; i++) 
	    price[i] = i + 1; 
	show(price); 
	price = removeElementAt(price, 3); 
	show(price); 
	price = removeElementAt(price, 5);
	show(price);
    } 
    public static int[] removeElementAt(int[] a, int index) {
	System.out.println("--> Attempting to remove element at index " 
			   + index + " in the array."); 
	if (a.length > 0) {
	    int[] copy = new int[a.length - 1]; 
	    for (int i = 0; i < index; i++) 
		copy[i] = a[i]; 
	    for (int i = index; i < a.length - 1; i++) 
		copy[i] = a[i + 1];
	    return copy;  
	} else {
	    System.out.println("Sorry, array is empty."); 
	    return a; 
	} 
       
    } 
    public static void show(int[] a) {
	for (int i = 0; i < a.length; i++) 
	    System.out.println("** " + i + ": " + a[i]);  
    } 
} 

How does this work? There you go:
frilled.cs.indiana.edu%java Three 10
** 0: 1
** 1: 2
** 2: 3
** 3: 4
** 4: 5
** 5: 6
** 6: 7
** 7: 8
** 8: 9
** 9: 10
--> Attempting to remove element at index 3 in the array.
** 0: 1
** 1: 2
** 2: 3
** 3: 5
** 4: 6
** 5: 7
** 6: 8
** 7: 9
** 8: 10
--> Attempting to remove element at index 5 in the array.
** 0: 1
** 1: 2
** 2: 3
** 3: 5
** 4: 6
** 5: 8
** 6: 9
** 7: 10
frilled.cs.indiana.edu%

I think there's a lot we can learn from this program. I think so too; plus, inserting an element in an array is done in the same way.

You're right. Tomorrow we'll start on Vectors.

Among other things. Can I give you a small challenge?

Yes. What's a good name for this method?
public static void fun(int[] a) {
  for (int i = 0; i < a.length - 1; i++) 
    for (int j = i + 1; j < a.length; j++) 
      if (a[i] < a[j]) {
        int temp = a[i]; 
        a[i] = a[j]; 
        a[j] = temp; 
      } 
} 

You mean second best name... Yes, that's what I mean.

I'll have to think about it. Great. See you tomorrow.

Last updated: Jun 16, 2002 by Adrian German for A201