Java Streams - 过滤先前过滤的值

标签 java filter lambda java-8 java-stream

我正在试验 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/

相关文章:

arrays - 在 if 语句中过滤带有空字符串的字符串,iOS

python - 向pandas的pivot_table方法提供lambda函数列表时出现"Reindexing only valid with uniquely valued Index objects"错误?

java - "Dispatcher"对 NewRelic 的 @Trace 注解做了什么

java - jsr 303 验证数字并自动初始化为 0

Java 8 : how to get the first number greater than 10 in a stream?

javascript - 使用 Javascript 通过正则表达式过滤数组

filter - 在方案 (SCM) 中的 Define Filter 函数的结果末尾获取 #f 或 False

java - 使用强制转换和 lambda 进行收集

lisp - 我对符号求值器对动态变量集进行推导的想法

java - 如何在 pom 中构建仅包含依赖项 jar 的 jar