Using Java 8 Stream Reduce to return List after performing operation on each element using previous elements values

陌路散爱 提交于 2019-12-05 01:55:12

问题


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!


回答1:


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.




回答2:


although the solution with plain for loop provided by @Holger is good enough, I would like to recommend you try third-party library for this kind of common issues. for example: StreamEx or JOOL. Here is solution by StreamEx.

StreamEx.of(listItems).filter(e -> e.getCounterStatus() == CounterStatus.ACTIVE)
        .scanLeft((a,b) -> { b.setStartCounter(a.getEndCounter()); return b; });


来源:https://stackoverflow.com/questions/46350402/using-java-8-stream-reduce-to-return-list-after-performing-operation-on-each-ele

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!