[英]How to find nth prime number in Java using parallel Streams
看來,當使用有序Streams在難以限制的數字范圍上處理短路操作時,不能使用parallel()
。 例如:
public class InfiniteTest {
private static boolean isPrime(int x) {
if (x < 2) {
return false;
}
if (x % 2 == 0 && x > 2) {
return false;
}
// loop while i <= sqrt(x), using multiply for speedup
for (int i = 3; i * i <= x; i += 2) {
if (x % i == 0) {
return false;
}
}
return true;
}
private static int findNthPrime(final int n) {
// must not use infinite stream, causes OOME
// but even big size causes huge slowdown
IntStream.range(1, 1000_000_000)
// .parallel()
.filter(InfiniteTest::isPrime)
.skip(n - 1)
.findFirst()
.getAsInt();
}
public static void main(String[] args) {
int n = 1000; // find the nth prime number
System.out.println(findNthPrime(n));
}
}
此順序流工作正常。 但是,當我添加parallel()
,它似乎可以永遠運行(或最終運行很長時間)。 我認為這是因為流線程處理任意數字,而不是從流中的第一個數字開始。 我無法有效地限制整數范圍以掃描素數 。
那么,是否有任何簡單的技巧可以在沒有該陷阱的情況下與流並行運行此問題,例如強制splititerator從流的開頭開始處理大量工作? 還是從覆蓋越來越多的范圍的子流中構建流? 還是以某種方式將多線程設置為生產者/消費者模式但使用流?
所有類似的問題似乎都試圖阻止並行的使用:
除2和3外,所有質數均采用6n-1或6n + 1的形式。 您已經在代碼中將2視為特例。 您可能還想嘗試將3視為特殊:
if (x % 3 == 0) {
return x == 3;
}
然后運行兩個並行流,一個測試編號以6n-1形式從5開始,另一個測試編號以6n + 1形式從7開始。每個流一次可以跳過六個數字。
您可以使用素數定理來估計第n個素數的值,並將搜索范圍設置為稍高於該估計值的安全性。
TL / DR :不可能。
似乎用一種短路方法並行處理無邊界流以找到任何事物的最早出現(按流的順序)是不可能的(一種有用的方式(“有用”的含義比按時間順序查找結果要好))。
解釋我嘗試了AbstractIntSpliterator的自定義實現,該實現不將流拆分為分區(1-100、101-200,...),而是交錯地拆分它們([0、2、4、6、8,...], [1、3、5、6 ...])。 這在順序情況下可以正常工作:
/**
* Provides numbers starting at n, on split splits such that child iterator and
* this take provide interleaving numbers
*/
public class InterleaveSplitIntSplitIterator extends Spliterators.AbstractIntSpliterator {
private int current;
private int increment;
protected InterleaveSplitIntSplitIterator(int start, int increment) {
super(Integer.MAX_VALUE,
Spliterator.DISTINCT
// splitting is interleaved, not prefixing
// | Spliterator.ORDERED
| Spliterator.NONNULL
| Spliterator.IMMUTABLE
// SORTED must imply ORDERED
// | Spliterator.SORTED
);
if (increment == 0) {
throw new IllegalArgumentException("Increment must be non-zero");
}
this.current = start;
this.increment = increment;
}
@Override
public boolean tryAdvance(IntConsumer action) {
// Don't benchmark with this on
// System.out.println(Thread.currentThread() + " " + current);
action.accept(current);
current += increment;
return true;
}
// this is required for ORDERED even if sorted() is never called
@Override
public Comparator<? super Integer> getComparator() {
if (increment > 0) {
return null;
}
return Comparator.<Integer>naturalOrder().reversed();
}
@Override
public OfInt trySplit() {
if (increment >= 2) {
return null;
}
int newIncrement = this.increment * 2;
int oldIncrement = this.increment;
this.increment = newIncrement;
return new InterleaveSplitIntSplitIterator(current + oldIncrement, newIncrement);
}
// for convenience
public static IntStream asIntStream(int start, int increment) {
return StreamSupport.intStream(
new InterleaveSplitIntSplitIterator(start, increment),
/* no, never set parallel here */ false);
}
}
但是,此類流不能具有Spliterator.ORDERED特性,因為
如果是這樣,此Spliterator保證方法
{@link #trySplit}
拆分元素的嚴格前綴
這也意味着此類流無法保持其已排序的特征,因為
報告
{@code SORTED}
SORTED{@code SORTED}
拆分器還必須報告{@code ORDERED}
因此,我的splititerator並行出現了(有些)混亂的數字,必須在應用限制之前通過排序將其固定,這種限制不適用於無限流(通常情況下)。
因此,所有解決方案都必須使用splititerator,該拆分器將塊或前綴數據拆分為任意數據,然后以〜任意順序使用它,這將導致超出實際結果處理范圍的許多數值范圍,通常比順序解決方案要慢(很多) 。
因此,除了限制要測試的數字范圍外,似乎沒有使用並行流的解決方案。 問題在於規范中要求ORDERED特性通過前綴來拆分流,而不是提供不同的方法來重組來自多個splititerator的有序流結果。
但是,使用順序流和並行處理(緩沖)輸入的解決方案仍然可能(但不像調用parallel()
那樣簡單)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.