问题
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"}
回答1:
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();
}
回答2:
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 java-stream 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()); }
回答3:
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;
}
回答4:
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.
来源:https://stackoverflow.com/questions/56365595/how-can-i-filter-an-iterable-based-upon-a-predicate