我正在尝试使用
Java的Streams并试图找出可能的内容以及它们的优点和缺点.目前我正在尝试使用流来实现Eratosthenes的Sieve,但似乎无法找到循环使用先前过滤的值而不将其存储在单独集合中的好方法.
我想要完成这样的事情:
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,2,
请注意,在过滤每个新值时,会观察到所有先前过滤的值.这样可以轻松实现Eratosthenes的Sieve,因为我可以过滤掉所有非素数值,并为每个新值检查对所有先前通过素数过滤器的数字的可除性.
但是,上面的示例在NetBeans中给出了一个错误:
local variables referenced from a lambda expression must be final or effectively final
这似乎是因为我在已经作用于myStream的过滤器中引用myStream.是否有任何解决此错误的好方法(即,制作仅包含到目前为止已过滤的值的流的最终副本),或者是否有更好的方法解决此类问题而不使用单独的集合来存储值?
解决方法
我设法使用Eratosthenes的Sieve创建了一个无限的素数流,但它实际上并没有使用过去的值.相反,它删除了尾部中的素数的倍数(以懒惰的方式,因为尾部是无限的),就像原始的Eratosthenes算法的Sieve一样.为此,我使用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); } }
我的Eratosthenes流筛选器看起来像这样:
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]
我认为这是一个很好的练习,但它似乎效率很低,而且根本不适合堆栈.