As mentioned in the comments: The usage scenario sounds a bit dubious. On the one hand, because of the usage of reduce
instead of collect
, on the other hand because of the fact that the condition that should be used for stopping the reduction also appears in the accumulator. It sounds like simply limiting the stream to a certain number of elements, or based on a condition, as shown in another question, may be more appropriate here.
Of course, in the real application, it might be that the condition is in fact unrelated to the number of elements that have been processed. For this case, I sketched a solution here that basically corresponds to the answer by the8472, and is very similar to the solution from the question mentioned above: It uses a Stream
that is created from a Spliterator
that simply delegates to the original Spliterator
, unless the stopping condition is met.
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class StopStreamReduction
{
public static void main(String[] args)
{
ResultSupplier r = new ResultSupplier();
System.out.println(r.get());
}
}
class Accumulator
{
final Set<Integer> set = new HashSet<Integer>();
}
class ResultSupplier implements Supplier<String>
{
private final List<Integer> ids;
ResultSupplier()
{
ids = new ArrayList<Integer>(Collections.nCopies(20, 1));
}
public String get()
{
//return getOriginal();
return getStopping();
}
private String getOriginal()
{
Accumulator acc =
ids.stream().reduce(new Accumulator(), f(), (x, y) -> null);
return (acc.set.size() > 11) ? "invalid" : String.valueOf(acc.set);
}
private String getStopping()
{
Spliterator<Integer> originalSpliterator = ids.spliterator();
Accumulator accumulator = new Accumulator();
Spliterator<Integer> stoppingSpliterator =
new Spliterators.AbstractSpliterator<Integer>(
originalSpliterator.estimateSize(), 0)
{
@Override
public boolean tryAdvance(Consumer<? super Integer> action)
{
return accumulator.set.size() > 10 ? false :
originalSpliterator.tryAdvance(action);
}
};
Stream<Integer> stream =
StreamSupport.stream(stoppingSpliterator, false);
Accumulator acc =
stream.reduce(accumulator, f(), (x, y) -> null);
return (acc.set.size() > 11) ? "invalid" : String.valueOf(acc.set);
}
private static int counter = 0;
private static BiFunction<Accumulator, Integer, Accumulator> f()
{
return (acc, element) -> {
System.out.print("Step " + counter);
if (acc.set.size() <= 10)
{
System.out.print(" expensive");
acc.set.add(counter);
}
System.out.println();
counter++;
return acc;
};
}
}
Edit in response to the comments:
Of course, it is possible to write it "more functional". However, due to the vague descriptions in the questions and the rather "sketchy" code example, it's hard to find "THE" most appropriate solution here. (And "appropriate" refers to the specific caveats of the actual task, and to the question of how functional it should be without sacrificing readability).
Possible functionalization steps might include the creation of a generic StoppingSpliterator
class that operates on a delegate Spliterator
and has a Supplier<Boolean>
as its stopping condition, and feeding this with a Predicate
on the actual Accumulator
, together with using some utility methods and method references here and there.
But again: It is debatable whether this is actually an appropriate solution, or whether one should not rather use the simple and pragmatic solution from the answer by Lukas Eder...
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.StreamSupport;
public class StopStreamReduction
{
public static void main(String[] args)
{
List<Integer> collection =
new ArrayList<Integer>(Collections.nCopies(20, 1));
System.out.println(compute(collection));
}
private static String compute(List<Integer> collection)
{
Predicate<Accumulator> stopCondition = (a) -> a.set.size() > 10;
Accumulator result = reduceStopping(collection,
new Accumulator(), StopStreamReduction::accumulate, stopCondition);
return (result.set.size() > 11) ? "invalid" : String.valueOf(result.set);
}
private static int counter;
private static Accumulator accumulate(Accumulator a, Integer element)
{
System.out.print("Step " + counter);
if (a.set.size() <= 10)
{
System.out.print(" expensive");
a.set.add(counter);
}
System.out.println();
counter++;
return a;
}
static <U, T> U reduceStopping(
Collection<T> collection, U identity,
BiFunction<U, ? super T, U> accumulator,
Predicate<U> stopCondition)
{
// This assumes that the accumulator always returns
// the identity instance (with the accumulated values).
// This may not always be true!
return StreamSupport.stream(
new StoppingSpliterator<T>(
collection.spliterator(),
() -> stopCondition.test(identity)), false).
reduce(identity, accumulator, (x, y) -> null);
}
}
class Accumulator
{
final Set<Integer> set = new HashSet<Integer>();
}
class StoppingSpliterator<T> extends Spliterators.AbstractSpliterator<T>
{
private final Spliterator<T> delegate;
private final Supplier<Boolean> stopCondition;
StoppingSpliterator(Spliterator<T> delegate, Supplier<Boolean> stopCondition)
{
super(delegate.estimateSize(), 0);
this.delegate = delegate;
this.stopCondition = stopCondition;
}
@Override
public boolean tryAdvance(Consumer<? super T> action)
{
if (stopCondition.get())
{
return false;
}
return delegate.tryAdvance(action);
}
}