I'm new to Streams and Reduce so I'm trying it out and have hit a problem:
I have a list of counters which have a start counter and end counter. The startcounter of an item is always the endcounter of the previous. I have a list of these counters listItems
which I want to loop through efficiently, filter out inactive records and then reduce the list into a new List where all the StartCounters are set. I have the following code:
List<CounterChain> active = listItems.stream()
.filter(e -> e.getCounterStatus() == CounterStatus.ACTIVE)
.reduce(new ArrayList<CounterChain>(), (a,b) -> { b.setStartCounter(a.getEndCounter()); return b; });
But it doesn't really work and I'm kind of stuck, can anyone give me a few suggestions to help me get this working? Or is there an equally efficient better way to do this? Thanks!
A Reduction reduces all elements to a single value. Using a reduction function of the (a,b) -> b
form will reduce all elements to the last one, so it’s not appropriate when you want to get a List
containing all (matching) elements.
Besides that, you are performing a modification of the input value, which is violating the contract of that operation. Note further, that the function is required to be associative, i.e. it shouldn’t matter whether the stream will perform f(f(e₁,e₂),e₃))
or f(e₁,f(e₂,e₃))
when processing three subsequent stream elements with your reduction function.
Or, to put it in one line, you are not using the right tool for the job.
The cleanest solution is not to mix these unrelated operations:
List<CounterChain> active = listItems.stream()
.filter(e -> e.getCounterStatus() == CounterStatus.ACTIVE)
.collect(Collectors.toList());
for(int ix=1, num=active.size(); ix<num; ix++)
active.get(ix).setStartCounter(active.get(ix-1).getEndCounter());
The second loop could also be implemented using forEach
, but it would require an inner class due to its stateful nature:
active.forEach(new Consumer<CounterChain>() {
CounterChain last;
public void accept(CounterChain next) {
if(last!=null) next.setStartCounter(last.getEndCounter());
last = next;
}
});
Or, using an index based stream:
IntStream.range(1, active.size())
.forEach(ix -> active.get(ix).setStartCounter(active.get(ix-1).getEndCounter()));
But neither has much advantage over a plain for
loop.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With