user11574240
user11574240

Reputation:

How can I filter an Iterable based upon a Predicate?

I want to do a string list filter function using an Iterable<String> and a predicate to select the strings to keep, the other ones must be removed from the list, but I'm not understating how I do the remove.

static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) {
    for (T s: it) {
        if (pred.test(s)==false) {
            // what to do here?
        }
    }
    return ...;
}

For this input:

{"a","","b",""}

I expect

{"a","b"}

Upvotes: 3

Views: 11336

Answers (4)

daniu
daniu

Reputation: 15028

The alternative solution to Holger's I meant in the comment looks like this:

static <T> Iterable<T> select(Iterable<T> toIterate, Predicate<T> pred) {
    return () -> new Iterator<T>() {
        Iterator<T> delegate = toIterate.iterator();
        T next = findNextValid();
        public boolean hasNext() {
            return next != null;
        }
        public T next() {
            if (next == null) throw new NoSuchElementException();
            T result = next;
            next = findNextValid();
            return result;
        }
        private T findNextValid() {
            T result = null;
            while (result == null && delegate.hasNext()) {
                T candidate = delegate.next();
                if (pred.test(candidate)) {
                    result = candidate;
                }
            }
            return result;
        }
    };
}

The difference is that there's no need for an additional marker for the hasCurrent, and it advances the Iterator before the next element is actually requested. You might consider the latter to be undesirable though.

Upvotes: 0

ETO
ETO

Reputation: 7299

First wrap your Iterable<T> into Stream<T>:

  • Plain Java:

    StreamSupport.stream(it.spliterator(), false)
    
  • Guava

    Streams.stream(it)
    
  • StreamEx

    StreamEx.of(it.iterator())
    

Then filter it by your Predicate<T> :

...
stream.filter(pred.negate())
...

And finally return Iterable<T>:

  • as lambda :

    return () -> stream.iterator();
    
  • as method reference

    return stream::iterator;
    

Complete example:

static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) {
    return StreamSupport.stream(it.spliterator(), false).filter(pred.negate())::iterator;
}

or:

static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) {
    Stream<T> stream = stream(it.spliterator(), false);
    Predicate<T> negatedPred = pred.negate();
    Stream<T> filteredStream = stream.filter(negatedPred);
    return filteredStream::iterator;
}

Upvotes: 2

Holger
Holger

Reputation: 298579

An Iterable represents the capability to provide an Iterator on request. So, to decorate an existing iterable with a filtering logic, you have to implement the decorating Iterator.

static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) {
    return () -> new Iterator<T>() {
        Iterator<T> sourceIterator = it.iterator();
        T current;
        boolean hasCurrent;

        @Override
        public boolean hasNext() {
            while(!hasCurrent) {
                if(!sourceIterator.hasNext()) {
                    return false;
                }
                T next = sourceIterator.next();
                if(pred.test(next)) {
                    current = next;
                    hasCurrent = true;
                }
            }
            return true;
        }

        @Override
        public T next() {
            if(!hasNext()) throw new NoSuchElementException();
            T next = current;
            current = null;
            hasCurrent = false;
            return next;
        }
    };
}

which you may test via

List<String> original = new ArrayList<>();
Collections.addAll(original, "foo", "bar", "baz");
Iterable<String> filter = select(original, s -> s.startsWith("b"));
System.out.println(String.join(", ", filter));
original.removeIf(s -> !s.endsWith("r"));
System.out.println(String.join(", ", filter));

The biggest challenge when implementing such an Iterator, is to provide the two methods hasNext and next with the correct semantics, without any guaranty regarding how the caller will invoke them, i.e. you can not assume that it will never invoke hasNext() twice nor that next() will always be invoked with a preceding hasNext().

The same logic can be implemented much easier using the Stream API:

static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) {
    return () -> StreamSupport.stream(it.spliterator(), false)
        .filter(pred).iterator();
}

Upvotes: 5

Nikolas
Nikolas

Reputation: 44496

Since any Collection is Iterable, just add the qualified items to a new collection and return it later:

static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) {
    Collection<T> collection = new ArrayList<>();
    for (T s: it) {
        if (!pred.test(s)) {
            collection.add(s);
        }
    }
    return collection;
}

Few insights:

  • The pred.test(s)==false expression shall be rather simplified to !pred.test(s)
  • The whole content of method could be shortened using in this way:

    static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) {
        return StreamSupport.stream(it.spliterator(), false)
            .filter(pred)
            .collect(Collectors.toList());
    }
    

Upvotes: 1

Related Questions