Spliterator Java 8

У меня есть число от 1 до 10000, хранящихся в массиве long, Последовательное добавление их даст 50,005,000.
Я написал Spliterator, где, если размер массива превышает 1000, он будет разделен на другой массив. Вот мой код Но когда я запускаю его, результат сложения намного превышает 50 005 000. Может кто-нибудь сказать мне, что не так с моим кодом?

Огромное спасибо.

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);
    }

}

1 ответ

Решение

У меня сильное чувство, что вы не поняли цель разделения правильно. Это не предназначено, чтобы скопировать лежащие в основе данные, но просто предоставить доступ к диапазону их. Помните, что сплитераторы предоставляют доступ только для чтения. Таким образом, вы должны передать исходный массив новому сплитератору и настроить его с соответствующей позицией и длиной вместо копирования массива.

Но помимо неэффективности копирования логика явно ошибочна: вы передаете Arrays.copyOfRange(numbers, 1_000, numbers.length) в новый сплитератор, поэтому новый сплитератор содержит элементы с позиции 1000 до конца массива, и вы увеличиваете текущую позицию сплитератора на 1000, поэтому старый сплитератор покрывает элементы из currentPosition + 1_000 до конца массива. Таким образом, оба сплитератора будут одновременно покрывать элементы в конце массива, в зависимости от предыдущего значения currentPosition элементы в начале не могут быть покрыты вообще. Поэтому, когда вы хотите продвинуть currentPosition от 1_000 пропущенный диапазон выражается Arrays.copyOfRange(numbers, currentPosition, 1_000) вместо этого, ссылаясь на currentPosition до наступления.

Следует также отметить, что сплитератор должен пытаться разделить баланс, то есть посередине, если известен размер. Поэтому разделение тысяч элементов не является правильной стратегией для массива.

Далее ваш tryAdvance метод неверен. Не следует проверять после звонка потребителю, но до возвращения false если нет больше элементов, что также означает, что потребитель не был вызван.

Собрав все вместе, реализация может выглядеть так

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;
    }
}

Но, конечно, рекомендуется предоставить специализированный forEachRemaining реализация, где это возможно:

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

В заключение, для задачи суммирования длин из массива, Spliterator.OfLong и LongStream является предпочтительным, и эта работа уже выполнена, см. Arrays.spliterator() а также LongStream.sum(), делая всю задачу так же просто, как Arrays.stream(numbers).sum(),

Другие вопросы по тегам