问题
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)==falseexpression 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 referencereturn 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