我正在试验 Java 的 Streams,并试图弄清楚什么是可能的,以及它们的优缺点。目前我正在尝试使用流来实现埃拉托色尼筛法,但似乎无法找到一种好方法来循环遍历以前过滤的值而不将它们存储在单独的集合中。
我想完成这样的事情:
IntStream myStream = IntStream.range(0,3);
myStream.filter(s -> {
System.out.print("[filtering "+s+"] ");
myStream.forEach(q -> System.out.print(q+", "));
System.out.println();
return true; //eventually respond to values observed on the line above
});
期望输出:
[filtering 0]
[filtering 1] 0,
[filtering 2] 0, 1,
[filtering 3] 0, 1, 2,
请注意,在过滤每个新值时,会观察到所有先前过滤的值。这将允许轻松实现埃拉托色尼筛法,因为我可以过滤掉所有非素数值,并针对每个新值检查之前通过素数过滤器的所有数字是否可整除。
但是,上面的例子在 NetBeans 中给我一个错误:
local variables referenced from a lambda expression must be final or effectively final
这似乎是因为我在一个已经作用于 myStream 的过滤器中引用了 myStream。是否有解决此错误的好方法(即制作仅包含到目前为止已过滤的值的流的最终副本),或者是否有更好的方法来解决此类问题而不使用单独的集合来存储值(value)观?
最佳答案
我设法使用埃拉托色尼筛法创建了无限的素数流
,但它实际上并没有使用过去的值。相反,它会删除尾部素数的倍数(以惰性方式,因为尾部是无限的),就像最初的埃拉托色尼筛法算法一样。为此,我使用了一个 Iterator
作为辅助(因为 Stream
只能使用一次)并为流实现了一个 lazyConcat
。
class StreamUtils {
public static IntStream fromIterator(PrimitiveIterator.OfInt it) {
return StreamSupport.intStream(
Spliterators.spliteratorUnknownSize(it, Spliterator.ORDERED), false);
}
public static IntStream lazyConcat(Supplier<IntStream> a, Supplier<IntStream> b) {
return StreamSupport.intStream(new Spliterator.OfInt() {
boolean beforeSplit = true;
Spliterator.OfInt spliterator;
@Override
public OfInt trySplit() {
return null;
}
@Override
public long estimateSize() {
return Long.MAX_VALUE;
}
@Override
public int characteristics() {
return Spliterator.ORDERED;
}
@Override
public boolean tryAdvance(IntConsumer action) {
boolean hasNext;
if (spliterator == null) {
spliterator = a.get().spliterator();
}
hasNext = spliterator.tryAdvance(action);
if (!hasNext && beforeSplit) {
beforeSplit = false;
spliterator = b.get().spliterator();
hasNext = spliterator.tryAdvance(action);
}
return hasNext;
}
}, false);
}
}
我的埃拉托色尼筛法流看起来像这样:
class Primes {
public static IntStream stream() {
return sieve(IntStream.iterate(2, n -> n + 1));
}
private static IntStream sieve(IntStream s) {
PrimitiveIterator.OfInt it = s.iterator();
int head = it.nextInt();
IntStream tail = StreamUtils.fromIterator(it);
return StreamUtils.lazyConcat(
() -> IntStream.of(head),
() -> sieve(tail.filter(n -> n % head != 0)));
}
}
那么我们可以这样使用它:
System.out.println(Primes.stream().limit(20).boxed().collect(Collectors.toList()));
输出:
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]
我认为这是一个很好的练习,但它似乎效率很低而且根本不适合堆栈。
关于Java Streams - 过滤先前过滤的值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32102028/