McGlone
McGlone

Reputation: 3444

How to Filter an Element from a List

I'm trying to implement a fairly simple method in which I want to filter a list. This is a list of File objects and there should be exactly one file that ends with .asp - I want that one excluded from the list. Keep in mind that I don't actually want to remove this file from the list, I just want to be able to ignore it for a specific iteration of that list.

My original (brute force) implementation looked like this:

public List<File> getSurveyFiles() throws Exception {
    List<File> surveyFiles = new ArrayList<File>(files.size() - 1);

    for ( File f : files ) {
        if ( !f.getName().endsWith(".asp") ) {
            surveyFiles.add(f);
        }
    }

    return surveyFiles;
}

It works, but it feels very wasteful in the fact that I am creating a second list and doing a lot of copying from one list to another.

Another option I've toyed with is to use guava-libraries (http://code.google.com/p/guava-libraries/) and utilizing their filter function, like this:

public class SurveyFileControllerPredicate implements Predicate<File> {

    @Override
    public boolean apply(File file) {
        return file.getName().endsWith(".asp");
    }
}

...

public Iterable<File> getSurveyFiles() throws Exception {

    return Iterables.filter(
        files,
        Predicates.not(new SurveyFileControllerPredicate())    
    );

}

The implementation of filter removes the .asp file at iteration time, rather than ahead of time, so this code has the benefit of not making a second List, but I feel that it makes my code more complex.

Are there other, simpler, implementations that I'm not considering?

In the whole scheme of things, which implementation I choose probably doesn't matter. I'm just curious how other developers would tackle this and what option they would choose.

Thanks.

Upvotes: 3

Views: 277

Answers (3)

Jeffrey Bosboom
Jeffrey Bosboom

Reputation: 13653

If you're willing to write the filtering at the iteration site (rather than write a function that returns a filtered copy or view), Java 8 streams make this very simple:

files.stream().filter(f -> !f.getName().endsWith(".asp")).forEachOrdered(f -> {
    //process file f
});

If you only do this filtering in a few places, this is more concise than writing a method that returns a filtered copy or view, and keeps the filtering operation close to where the filtered list is used. If you do this filtering in many places and may want to filter the list differently later, writing a method might be better -- but it can be a method that returns a Stream:

public Stream<File> getSurveyFiles() {
    return files.stream().filter(f -> !f.getName().endsWith(".asp"));
}

You can then call forEachOrdered on the return value. If you need a non-stream operation, call iterator to get an iterator or .collect(Collectors.toList()) to get a filtered copy of the list.

Upvotes: 0

Markus A.
Markus A.

Reputation: 12742

At some point, I wrote myself these two very general helper classes that handle problems like this:

public abstract class IteratorFilter<E> implements Iterator<E> {
  private final Iterator<E> iterator;

  private E next = null;

  public IteratorFilter(Iterator<E> iterator) {
    this.iterator = iterator;
  }

  @Override
  public boolean hasNext() {
    if (next!=null) return true;
    while (iterator.hasNext()) {
      next = iterator.next();
      if (keep(next)) return true;
    }
    return false;
  }

  @Override
  public E next() {
    if (next==null)
      do next = iterator.next(); while (!keep(next));
    E result = next;
    next = null;
    return result;
  }

  @Override
  public void remove() {
    iterator.remove(); // Specs require: throw new UnsupportedOperationException();
  }

  protected abstract boolean keep(E item);
}

and:

public abstract class IterableFilter<T> implements Iterable<T> {

  private final Iterable<T> iterable;

  public IterableFilter(Iterable<T> iterable) {
    this.iterable = iterable;
  }

  @Override
  public Iterator<T> iterator() {
    return new IteratorFilter<T>(iterable.iterator()) {
      @Override
      protected boolean keep(T item) {
        return IterableFilter.this.keep(item);
      }
    };
  }

  protected abstract boolean keep(T item);
}

With these, you can simply do this:

public Iterable<File> getSurveyFiles() {
  return new IterableFilter<File>(files) {
    @Override
    protected boolean keep(File item) {
      return !item.getName().endsWith(".asp");
    }
  };
}

It's essentially the same approach as the Guava Predicate method, except that you don't need to keep track of the predicate object and you don't introduce a new library dependency.

Upvotes: 2

ColinD
ColinD

Reputation: 110046

You could compose a regex matching predicate with the toString() function:

public Iterable<File> getSurveyFiles() {
  return Iterables.filter(files, Predicates.compose(
      Predicates.not(Predicates.containsPattern("\\.asp$")),
      Functions.toStringFunction()));
}

Upvotes: 5

Related Questions