В образовательных целях я хочу создать поток простых чисел с использованием Java-8. Вот мой подход. Число x
является простым, если оно не имеет простых делителей, не превышающих sqrt(x)
. Поэтому, предполагая, что у меня уже есть поток простых чисел, я могу проверить это со следующим предикатом:
x -> Seq.seq(primes()).limitWhile(p -> p <= Math.sqrt(x)).allMatch(p -> x % p != 0)
Здесь я использовал jOOλ library (0.9.10, если это имеет значение) только для операции limitWhile
, которая отсутствует в стандартном потоковом API. Итак, теперь, зная некоторые предыдущие простые prev
, я могу сгенерировать следующее простое итерационное число, пока не найду тот, который соответствует этому предикату:
prev -> LongStream.iterate(prev + 1, i -> i + 1)
.filter(x -> Seq.seq(primes()).limitWhile(p -> p <= Math.sqrt(x))
.allMatch(p -> x % p != 0))
.findFirst()
.getAsLong()
Собирая все вместе, я написал следующий метод primes()
:
public static LongStream primes() {
return LongStream.iterate(2L,
prev -> LongStream.iterate(prev + 1, i -> i + 1)
.filter(x -> Seq.seq(primes())
.limitWhile(p -> p <= Math.sqrt(x))
.allMatch(p -> x % p != 0))
.findFirst()
.getAsLong());
}
Теперь для запуска этого я использую:
primes().forEach(System.out::println);
К сожалению, он не работает с неприятным StackOverflowError
, который выглядит так:
Exception in thread "main" java.lang.StackOverflowError
at java.util.stream.ReferencePipeline$StatelessOp.opIsStateful(ReferencePipeline.java:624)
at java.util.stream.AbstractPipeline.<init>(AbstractPipeline.java:211)
at java.util.stream.ReferencePipeline.<init>(ReferencePipeline.java:94)
at java.util.stream.ReferencePipeline$StatelessOp.<init>(ReferencePipeline.java:618)
at java.util.stream.LongPipeline$3.<init>(LongPipeline.java:225)
at java.util.stream.LongPipeline.mapToObj(LongPipeline.java:224)
at java.util.stream.LongPipeline.boxed(LongPipeline.java:201)
at org.jooq.lambda.Seq.seq(Seq.java:2481)
at Primes.lambda$2(Primes.java:13)
at Primes$$Lambda$4/1555009629.test(Unknown Source)
at java.util.stream.LongPipeline$8$1.accept(LongPipeline.java:324)
at java.util.Spliterators$LongIteratorSpliterator.tryAdvance(Spliterators.java:2009)
at java.util.stream.LongPipeline.forEachWithCancel(LongPipeline.java:160)
at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:529)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:516)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.LongPipeline.findFirst(LongPipeline.java:474)
at Primes.lambda$0(Primes.java:14)
at Primes$$Lambda$1/918221580.applyAsLong(Unknown Source)
at java.util.stream.LongStream$1.nextLong(LongStream.java:747)
at java.util.Spliterators$LongIteratorSpliterator.tryAdvance(Spliterators.java:2009)
...
Вы можете подумать, что я заслуживаю того, что получаю: я вызывал primes()
рекурсивно внутри самого метода primes()
. Однако пусть просто измените тип возвращаемого метода на Stream<Long>
и вместо этого используйте Stream.iterate
, оставив все остальное как есть:
public static Stream<Long> primes() {
return Stream.iterate(2L,
prev -> LongStream.iterate(prev + 1, i -> i + 1)
.filter(x -> Seq.seq(primes())
.limitWhile(p -> p <= Math.sqrt(x))
.allMatch(p -> x % p != 0))
.findFirst()
.getAsLong());
}
Теперь это работает как шарм! Не очень быстро, но через пару минут я получаю простые числа, превышающие 1000000 без каких-либо исключений. Результат правильный, который можно проверить по таблице простых чисел:
System.out.println(primes().skip(9999).findFirst());
// prints Optional[104729] which is actually 10000th prime.
Итак, вопрос: что случилось с первой версией на основе LongStream
? Это ошибка jOOλ, ошибка JDK или я что-то не так?
Обратите внимание, что меня не интересуют альтернативные способы генерации простых чисел, я хочу знать, что не так с этим конкретным кодом.