views:

892

answers:

7

How can I filter an array in Java?

I have an array of objects, for example cars:

Class:

public class Car{
    public int doors;
    public Car(int d){
        this.doors = d;
    }
}

Use:

Car [] cars = new Cars[4];
cars[0] = new Car(3);
cars[1] = new Car(2);
cars[2] = new Car(4);
cars[3] = new Car(6);

Now I want to filter the array of cars, keeping only 4 doors and more:

for(int i = 0; i<cars.length; i++){
    if(cars[i].doors > 4)
         //add cars[i] to a new array
    }
}

How should I do this?

Before I did it with a Vector:

Vector subset = new Vector();
for(int i = 0; i<cars.length; i++){
    if(cars[i].doors > 4)
         //add cars[i] to a new array
        subset.addElement(cars[i]);
    }
}

And then I would make a new array with the size of the Vector. Then I would loop over the vector again and fill the new array. I know this is a very large procedure for something simple.

I'm using J2ME.

A: 

You can use System.arrayCopy():

Car[] cars = ...
int length = cars.length < 4 ? cars.length() : 4;
Car filter = new Car[4];
System.arrayCopy(cars, 0, filter, 0, length);

UPDATE: System.arrayCopy is available in Java ME API, unlike Vector.subList(). Thanks for the correction.

notnoop
I don't think I can use these in J2ME...
hsmit
@hsmit, System.arrayCopy() are available in Java ME, but not `Vector.subList()`.
notnoop
+1  A: 

I can't see much wrong with your code. You could just stick with Vectors throughout though.

You could simplify the second part (where you copy the matching items into the new array) using Vector.copyInto(Object[]).

finnw
+1  A: 

If you really need a plain array as the result, I think your way is the way to go: you don't know the number of resulting elements before you filter, and you can't construct a new array without knowing the number of elements.

However, if you don't need thread-safety, consider using ArrayList instead of a Vector. It ought to be somewhat faster. Then use ArrayList's toArray method to get the array.

Joonas Pulakka
I think ArrayList is not used in J2ME unfortunately...
hsmit
Ah, good point. So this comes down to a choosing between memory usage vs. speed: Either create an intermediate Vector and get the array from that (which is fast but takes memory), or traverse the list twice, the first time only to find out then number of elements, and the second time to fill an array of that size (which is probably slower, but produces less garbage).
Joonas Pulakka
+2  A: 

EDIT: saw that ArrayList is not in J2ME, but based on documentation, it does have a Vector. If that Vector class is different than J2SE Vector (as this documentation indicates), then perhaps the following code would work:

Vector carList = new Vector();
for(int i = 0; i<cars.length; i++){
    if(cars[i].doors > 4)
         carList.addElement(cars[i]);
    }
}
Car[] carArray = new Car[carList.size()];
carList.copyInto(carArray);
Kaleb Brasee
carList.toArray() is not working in J2ME, thanks for your help anyway!
hsmit
I didn't realize that Vector behaves differently, maybe try the second piece of code that I added.
Kaleb Brasee
you might want to initialize the vector with cars.length. perhaps it would be more effective.
Bozho
+1  A: 

There's no direct way to remove elements from an array; its size is fixed. Whatever you do, you need to allocate a new array somehow.

If you want to avoid the minor memory overhead of allocating a Vector, another option would be to make two passes over your array. The first time, simply count the number of elements that you want to keep. Then allocate an array that size, and loop over your old array again, copying matching elements into the new array.

Porculus
Yeah, that sounds interesting. Do you have any idea about the performance of both?
hsmit
I would *guess* that my approach could be faster, depending on how expensive it is to grow a vector compared to the extra loop cycles involved. But I don't know much about the performance characteristics of J2ME, and it will also depend on how big your arrays are, etc. You'd have to benchmark to be sure.
Porculus
+1  A: 

You will need to create a new array anyway.

Vector vector = new Vector(array.length);

for (int i = 0; i < array.length; i++) {
    if (array[i].doors > 4) {
        vector.add(array[i]);
    }
}

Car[] result = new Car[vector.size()];
vector.copyInto(result);

This isn't quite efficient, though.

Bozho
There's no `java.util.Iterator` in Java ME: http://java.sun.com/javame/reference/apis/jsr139/
BalusC
woops :) how come I've forgotten. (Updated)
Bozho
A: 

The most efficient way to do this--if the predicate you're filtering on is inexpensive and you're accessing it with a single thread--is usually to traverse the list twice:

public Car[] getFourDoors(Car[] all_cars) {
  int n = 0;
  for (Car c : all_cars) if (c.doorCount()==4) n++;
  Car[] cars_4d = new Car[n];
  n = 0;
  for (Car c : all_cars) if (c.doorCount()==4) cars_4d[n++] = c;
  return cars_4d;
}

This traverses the list twice and calls the test twice, but has no extra allocations or copying. The Vector-style methods traverse the list once, but allocates about twice the memory it needs (transiently) and copies every good element about twice. So if you are filtering a tiny fraction of the list (or performance isn't an issue, which very often it isn't), then the Vector method is good. Otherwise, the version above performs better.

Rex Kerr