|
Second Summer 2002
|
Lecture Notes 18: Partially filled arrays. Array
parameters and return values. Simple array algorithms.
Have we ever seen an array of String s?
|
Apparently main receives one as a parameter.
|
Really? Where do those String s 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 Vector s
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 Rectangle s.
|
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 Rectangle s 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 Vector s.
|
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