Java 8: Extracting a pair of arrays out of a Stream<Pair>

旧巷老猫 提交于 2019-12-06 00:37:03

As the size of stream is known, there is no reason of reinventing the wheel again. The simplest solution is usually the best one. The second approach you have shown is nearly there - just use AtomicIntegeras array index and you will achieve your goal - single pass over data, and possible parralel stream execution ( due to AtomicInteger).

SO

AtomicInteger index=new AtomicInteger()
results.parallelStream().forEach (fab -> {
    int idx=index.getAndIncrement();
    foo_array[idx] = fab.foo_int;
    bar_array[idx] = fab.bar_object;
});

Thread safe for parralel execution. One iteratio over whole collection

Holger

If your prerequisites are that both, iterating the list and accessing the list via an index, are expensive operations, there is no chance of getting a benefit from the parallel Stream processing. You can try to go with this answer, if you don’t need the result values in the original list order.

Otherwise, you can’t benefit from the parallel Stream processing as it requires the source to be able to efficiently split its contents into two halves, which implies either, random access or fast iteration. If the source has no customized spliterator, the default implementation will try to enable parallel processing via buffering elements into an array, which already implies iterating before the parallel processing even starts and having additional array storage costs where your sole operation is an array storage operation anyway.

When you accept that there is no benefit from parallel processing, you can stay with your sequential solution, but solve the ugliness of the counter by moving it into the Consumer. Since lambda expressions don’t support this, you can turn to the good old anonymous inner class:

int[]      foo_array = new int[results.size()];
BarClass[] bar_array = new BarClass[results.size()];

results.forEach(new Consumer<AFooAndABarWalkIntoABar>() {
    int index=0;
    public void accept(AFooAndABarWalkIntoABar t) {
        foo_array[index]=t.foo_int;
        bar_array[index]=t.bar_object;
        index++;
    }
});

Of course, there’s also the often-overlooked alternative of the good old for-loop:

int[]      foo_array = new int[results.size()];
BarClass[] bar_array = new BarClass[results.size()];

int index=0;
for(AFooAndABarWalkIntoABar t: results) {
    foo_array[index]=t.foo_int;
    bar_array[index]=t.bar_object;
    index++;
}

I wouldn’t be surprised, if this beats all other alternatives performance-wise for your scenario…

A way to reuse an index in a stream is to wrap your lambda in an IntStream that is in charge of incrementing the index:

IntStream.range(0, results.size()).forEach(i -> {
    foo_array[i] = results.get(i).foo_i;
    bar_array[i] = results.get(i).bar_object;
});

With regards to Antoniossss's answer, using an IntStream seems like a slightly preferable option to using AtomicInteger:

  • It also works with parallel();
  • Two less local variable;
  • Leaves the Stream API in charge of parallel processing;
  • Two less lines of code.

EDIT: as Mikhail Prokhorov pointed out, calling the get method twice on implementations such as LinkedList will be slower than other solutions, given the O(n) complexity of their implementations of get. This can be fixed with:

AFooAndABarWalkIntoABar temp = results.get(i);
foo_array[i] = temp.foo_i;
bar_array[i] = temp.bar_object;
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!