Sura Watthanalamlert
Sura Watthanalamlert

Reputation: 140

Spliterator Java 8

I have a number from 1 to 10,000 stored in an array of long. When adding them sequentially it will give a result of 50,005,000.
I have writing an Spliterator where if a size of array is longer than 1000, it will be splitted to another array. Here is my code. But when I run it, the result from addition is far greater than 50,005,000. Can someone tell me what is wrong with my code?

Thank you so much.

import java.util.Arrays;
import java.util.Optional;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.stream.LongStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public class SumSpliterator implements Spliterator<Long> {

    private final long[] numbers;
    private int currentPosition = 0;

    public SumSpliterator(long[] numbers) {
        super();
        this.numbers = numbers;
    }

    @Override
    public boolean tryAdvance(Consumer<? super Long> action) {
        action.accept(numbers[currentPosition++]);
        return currentPosition < numbers.length;
    }

    @Override
    public long estimateSize() {
        return numbers.length - currentPosition;
    }

    @Override
    public int characteristics() {
        return SUBSIZED;
    }

    @Override
    public Spliterator<Long> trySplit() {
        int currentSize = numbers.length - currentPosition;

        if( currentSize <= 1_000){
            return null;
        }else{
            currentPosition = currentPosition + 1_000;
            return new SumSpliterator(Arrays.copyOfRange(numbers, 1_000, numbers.length));
        }
    }

    public static void main(String[] args) {

        long[] twoThousandNumbers = LongStream.rangeClosed(1, 10_000).toArray();

        Spliterator<Long> spliterator = new SumSpliterator(twoThousandNumbers);
        Stream<Long> stream = StreamSupport.stream(spliterator, false);

        System.out.println( sumValues(stream) );
    }

    private static long sumValues(Stream<Long> stream){
        Optional<Long> optional = stream.reduce( ( t, u) ->  t + u );

        return optional.get() != null ? optional.get() : Long.valueOf(0);
    }

}

Upvotes: 2

Views: 1557

Answers (1)

Holger
Holger

Reputation: 298133

I have the strong feeling that you didn’t get the purpose of splitting right. It’s not meant to copy the underlying data but just provide access to a range of it. Keep in mind that spliterators provide read-only access. So you should pass the original array to the new spliterator and configure it with an appropriate position and length instead of copying the array.

But besides the inefficiency of copying, the logic is obviously wrong: You pass Arrays.copyOfRange(numbers, 1_000, numbers.length) to the new spliterator, so the new spliterator contains the elements from position 1000 to the end of the array and you advance the current spliterator’s position by 1000, so the old spliterator covers the elements from currentPosition + 1_000 to the end of the array. So both spliterators will cover elements at the end of the array while at the same time, depending on the previous value of currentPosition, elements at the beginning might not be covered at all. So when you want to advance the currentPosition by 1_000 the skipped range is expressed by Arrays.copyOfRange(numbers, currentPosition, 1_000) instead, referring to the currentPosition before advancing.

It’s should also be noted, that a spliterator should attempt to split balanced, that is, in the middle if the size is known. So splitting off thousand elements is not the right strategy for an array.

Further, your tryAdvance method is wrong. It should not test after calling the consumer but before, returning false if there are no more elements, which also implies that the consumer has not been called.

Putting it all together, the implementation may look like

public class MyArraySpliterator implements Spliterator<Long> {

    private final long[] numbers;
    private int currentPosition, endPosition;

    public MyArraySpliterator(long[] numbers) {
        this(numbers, 0, numbers.length);
    }
    public MyArraySpliterator(long[] numbers, int start, int end) {
        this.numbers = numbers;
        currentPosition=start;
        endPosition=end;
    }

    @Override
    public boolean tryAdvance(Consumer<? super Long> action) {
        if(currentPosition < endPosition) {
            action.accept(numbers[currentPosition++]);
            return true;
        }
        return false;
    }

    @Override
    public long estimateSize() {
        return endPosition - currentPosition;
    }

    @Override
    public int characteristics() {
        return ORDERED|NONNULL|SIZED|SUBSIZED;
    }

    @Override
    public Spliterator<Long> trySplit() {
        if(estimateSize()<=1000) return null;
        int middle = (endPosition + currentPosition)>>>1;
        MyArraySpliterator prefix
                           = new MyArraySpliterator(numbers, currentPosition, middle);
        currentPosition=middle;
        return prefix;
    }
}

But of course, it’s recommended to provide a specialized forEachRemaining implementation, where possible:

@Override
public void forEachRemaining(Consumer<? super Long> action) {
    int pos=currentPosition, end=endPosition;
    currentPosition=end;
    for(;pos<end; pos++) action.accept(numbers[pos]);
}

As a final note, for the task of summing longs from an array, a Spliterator.OfLong and a LongStream is preferred and that work has already been done, see Arrays.spliterator() and LongStream.sum(), making the whole task as simple as Arrays.stream(numbers).sum().

Upvotes: 5

Related Questions