it-swarm.com.ru

Как создать бесконечный поток <E> из итератора <E>?

Глядя на следующий класс, который я сделал:

public class FibonacciSupplier implements Iterator<Integer> {
    private final IntPredicate hasNextPredicate;

    private int beforePrevious = 0;
    private int previous = 1;

    private FibonacciSupplier(final IntPredicate hasNextPredicate) {
        this.hasNextPredicate = hasNextPredicate;
    }

    @Override
    public boolean hasNext() {
        return hasNextPredicate.test(previous);
    }

    @Override
    public Integer next() {
        int result = beforePrevious + previous;
        beforePrevious = previous;
        previous = result;
        return result;
    }

    public static FibonacciSupplier infinite() {
        return new FibonacciSupplier(i -> true);
    }

    public static FibonacciSupplier finite(final IntPredicate predicate) {
        return new FibonacciSupplier(predicate);
    }
} 

И использование этого в:

public class Problem2 extends Problem<Integer> {
    @Override
    public void run() {
        result = toList(FibonacciSupplier.finite(i -> (i <= 4_000_000)))
                .stream()
                .filter(i -> (i % 2 == 0))
                .mapToInt(i -> i)
                .sum();
    }

    @Override
    public String getName() {
        return "Problem 2";
    }

    private static <E> List<E> toList(final Iterator<E> iterator) {
        List<E> list = new ArrayList<>();
        while (iterator.hasNext()) {
            list.add(iterator.next());
        }
        return list;
    }
}

Как я смогу создать бесконечный Stream<E>?

Если бы я использовал Stream<Integer> infiniteStream = toList(FibonacciSupplier.infinite()).stream(), я, возможно, удивительно, никогда бы не получил бесконечный поток.
Вместо этого код будет бесконечно зацикливаться при создании list в базовом методе.

Пока это чисто теоретически, но я могу определенно понять необходимость этого, если я захочу сначала пропустить первые x чисел из бесконечного потока, а затем ограничить их последними y числами, что-то вроде:

int x = MAGIC_NUMBER_X;
int y = MAGIC_NUMBER_y;
int sum = toList(FibonacciSupplier.infinite())
    .stream()
    .skip(x)
    .limit(y)
    .mapToInt(i -> i)
    .sum();

Код никогда не вернет результат, как это должно быть сделано?

21
skiwi

Ваша ошибка состоит в том, что вы считаете, что вам нужен Iterator или Collection для создания Stream. Для создания бесконечного потока достаточно одного метода, предоставляющего одно значение за другим. Так что для вашего класса FibonacciSupplier самое простое использование:

IntStream s=IntStream.generate(FibonacciSupplier.infinite()::next);

или, если вы предпочитаете в штучной упаковке значения:

Stream<Integer> s=Stream.generate(FibonacciSupplier.infinite()::next);

Обратите внимание, что в этом случае метод не должен называться next и не должен соответствовать интерфейсу Iterator. Но это не имеет значения, если это так, как с вашим классом. Кроме того, как мы только что сказали потоку использовать метод next в качестве Supplier, метод hasNext никогда не будет вызываться. Это просто бесконечно.

Создание конечного потока с использованием Iterator немного сложнее:

Stream<Integer> s=StreamSupport.stream(
  Spliterators.spliteratorUnknownSize(
    FibonacciSupplier.finite(intPredicate), Spliterator.ORDERED),
  false);

В этом случае, если вы хотите получить конечное IntStream с распакованными значениями int, ваша FibonacciSupplier должна реализовать PrimitiveIterator.OfInt.

21
Holger

В Java 8 нет public, конкретных классов, реализующих интерфейс Stream . Однако существуют некоторые статические фабричные методы. Одним из наиболее важных является StreamSupport.stream . В частности, он используется в методе defaultCollection.stream - наследуемом большинством классов коллекции:

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

Реализация этого метода по умолчанию создает Spliterator , вызывая функцию spliterator(), и передает созданный объект в метод фабрики. Spliterator - это новый интерфейс, представленный в Java 8 для поддержки параллельных потоков. Он похож на Iterator, но в отличие от последнего, Spliterator может быть разделен на части, которые могут обрабатываться независимо. Смотрите Spliterator.trySplit для деталей.

Метод defaultIterable.spliterator также был добавлен в Java 8, так что каждый класс Iterable автоматически поддерживает Spliterators. Реализация выглядит следующим образом:

default Spliterator<T> spliterator() {
    return Spliterators.spliteratorUnknownSize(iterator(), 0);
}

Метод создает Spliterator из произвольного Iterator. Если вы объедините эти два шага, вы можете создать Stream из произвольного Iterator:

<T> Stream<T> stream(Iterator<T> iterator) {
    Spliterator<T> spliterator
        = Spliterators.spliteratorUnknownSize(iterator, 0);
    return StreamSupport.stream(spliterator, false);
}

Чтобы получить представление о Spliterators, вот очень простой пример без использования коллекций. Следующий класс реализует Spliterator для итерации в полуоткрытом интервале целых чисел:

public final class IntRange implements Spliterator.OfInt {
    private int first, last;
    public IntRange(int first, int last) {
        this.first = first;
        this.last = last;
    }
    public boolean tryAdvance(IntConsumer action) {
        if (first < last) {
            action.accept(first++);
            return true;
        } else {
            return false;
        }
    }
    public OfInt trySplit() {
        int size = last - first;
        if (size >= 10) {
            int temp = first;
            first += size / 2;
            return new IntRange(temp, first);
        } else {
            return null;
        }
    }
    public long estimateSize() {
        return Math.max(last - first, 0);
    }
    public int characteristics() {
        return ORDERED | DISTINCT | SIZED | NONNULL
            | IMMUTABLE | CONCURRENT | SUBSIZED;
    }
}
15
nosid

Вы можете использовать низкоуровневые примитивы поддержки потока и библиотеку Spliterators, чтобы создать поток из Iterator.

Последний параметр StreamSupport.stream() говорит о том, что поток не параллелен. Обязательно сделайте так, потому что ваш итератор Фибоначчи зависит от предыдущих итераций.

return StreamSupport.stream( Spliterators.spliteratorUnknownSize( new Iterator<Node>()
{
    @Override
    public boolean hasNext()
    {
        // to implement
        return ...;
    }

    @Override
    public ContentVersion next()
    {
        // to implement
        return ...;
    }
}, Spliterator.ORDERED ), false );
0
Arnaud Tournier

Чтобы добавить еще один ответ, возможно, AbstractSpliterator является лучшим выбором, особенно с учетом примера кода. Генерирование негибко, так как нет [хорошего] способа дать условие остановки, кроме как с помощью лимита. Ограничение принимает только количество элементов, а не предикат, поэтому мы должны знать, сколько элементов мы хотим сгенерировать - что может быть невозможно, и что, если генератором является черный ящик, переданный нам?

AbstractSpliterator - это нечто среднее между необходимостью написания всего сплитератора и использованием Iterator/Iterable. В AbstractSpliterator отсутствует только метод tryAdvance, в котором мы сначала проверяем наш предикат на предмет выполнения, а если не передаем сгенерированное значение действию. Вот пример последовательности Фибоначчи с использованием AbstractIntSpliterator:

public class Fibonacci {
    private static class FibonacciGenerator extends Spliterators.AbstractIntSpliterator
    {
        private IntPredicate hasNextPredicate;
        private int beforePrevious = 0;
        private int previous = 0;

        protected FibonacciGenerator(IntPredicate hasNextPredicate)
        {
            super(Long.MAX_VALUE, 0);
            this.hasNextPredicate = hasNextPredicate;
        }

        @Override
        public boolean tryAdvance(IntConsumer action)
        {
            if (action == null)
            {
                throw new NullPointerException();
            }

            int next = Math.max(1, beforePrevious + previous);
            beforePrevious = previous;
            previous = next;

            if (!hasNextPredicate.test(next))
            {
                return false;
            }

            action.accept(next);

            return true;
        }

        @Override
        public boolean tryAdvance(Consumer<? super Integer> action)
        {
            if (action == null)
            {
                throw new NullPointerException();
            }

            int next = Math.max(1, beforePrevious + previous);
            beforePrevious = previous;
            previous = next;

            if (!hasNextPredicate.test(next))
            {
                return false;
            }

            action.accept(next);

            return true;
        }
    }

    public static void main(String args[])
    {
        Stream<Integer> infiniteStream = StreamSupport.stream(
                new FibonacciGenerator(i -> true), false);

        Stream<Integer> finiteStream = StreamSupport.stream(
                new FibonacciGenerator(i -> i < 100), false);

        // Print with a side-effect for the demo
        infiniteStream.limit(10).forEach(System.out::println);
        finiteStream.forEach(System.out::println);
    }
} 

Более подробно я описал генераторы на Java 8 в своем блоге http://thecannycoder.wordpress.com/

0
TheCannyCoder