Java流-对以前筛选的值进行筛选

Java流-对以前筛选的值进行筛选,java,filter,lambda,java-8,java-stream,Java,Filter,Lambda,Java 8,Java Stream,我正在用Java的流进行实验,试图找出哪些是可能的,以及它们的优缺点。目前,我正在尝试使用流实现Eratosthenes的筛选,但似乎找不到一种好方法来循环使用以前过滤过的值,而不将它们存储在单独的集合中 我想完成这样的事情: IntStream myStream = IntStream.range(0,3); myStream.filter(s -> { System.out.print("[filtering "+s+"] "); myStream.forEach(q

我正在用Java的流进行实验,试图找出哪些是可能的,以及它们的优缺点。目前,我正在尝试使用流实现Eratosthenes的筛选,但似乎找不到一种好方法来循环使用以前过滤过的值,而不将它们存储在单独的集合中

我想完成这样的事情:

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
});
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)));
    }
}
所需输出为:

[filtering 0] 
[filtering 1] 0, 
[filtering 2] 0, 1, 
[filtering 3] 0, 1, 2, 
请注意,在过滤每个新值时,将观察所有以前过滤的值。这将允许简单地实现Eratosthenes筛选,因为我可以过滤掉所有非素数值,并针对每个新值检查之前通过素数过滤器的所有数字的可除性

但是,上面的示例给出了NetBeans中的一个错误:

local variables referenced from a lambda expression must be final or effectively final

这似乎是因为我在一个已经在myStream上运行的过滤器中引用myStream。是否有解决此错误的好方法(即制作流的最终副本,仅包含到目前为止已过滤的值),或者是否有更好的方法来解决此类问题,而不使用单独的集合来存储值?

您不能多次处理流,因此,无法在筛选器方法内调用myStream.forEach

您可以在过滤器内创建一个新的
IntStream

请注意,您必须向外部流管道添加一些终端操作,以便对其进行处理:

IntStream myStream = IntStream.range(0,4);
myStream.filter(s -> {
    System.out.print("[filtering "+s+"] ");
    IntStream.range(0,s).forEach(q -> System.out.print(q+", "));
    System.out.println();
    return true; //eventually respond to values observed on the line above
}).forEach(i->{});
这将产生:

[filtering 0] 
[filtering 1] 0, 
[filtering 2] 0, 1, 
[filtering 3] 0, 1, 2, 

其他答案表明,我一直尝试的方法是不可能的,必须使用单独的集合

为了提供更完整的答案,我想使用streams提供一种有效的方法来解决这个问题,并将其与更传统的方法进行比较

使用流列出素数(使用Eratosthenes筛):

List<Integer> primes = new ArrayList<Integer>();

IntStream.iterate(2, i -> i + 1)
    .limit(UPPER_BOUND)
    .filter(i -> {
        for(int j=0; j<primes.size(); j++) {
            int prime = primes.get(j);

            if(prime > Math.sqrt(i)) {
                break;
            }

            if(i % prime == 0) {
                return false;
            }
        }
        return true;
    })
    .forEach(primes::add);
List<Integer> primes = new ArrayList<Integer>();

for(int i=2; i < UPPER_BOUND; i++) {
    boolean isPrime = true;

    for(int j=0; j<primes.size(); j++) {
        int prime = primes.get(j);

        if(prime > Math.sqrt(i)) {
            break;
        }

        if(i % prime == 0) {
            isPrime = false;
            break;
        }
    }

    if(isPrime) {
        primes.add(i);
    }
}
List primes=new ArrayList();
迭代(2,i->i+1)
.限制(上限)
.filter(i->{
对于(int j=0;j Math.sqrt(i)){
打破
}
如果(i%prime==0){
返回false;
}
}
返回true;
})
.forEach(primes::add);
传统的、等效的、不使用流的方法:

List<Integer> primes = new ArrayList<Integer>();

IntStream.iterate(2, i -> i + 1)
    .limit(UPPER_BOUND)
    .filter(i -> {
        for(int j=0; j<primes.size(); j++) {
            int prime = primes.get(j);

            if(prime > Math.sqrt(i)) {
                break;
            }

            if(i % prime == 0) {
                return false;
            }
        }
        return true;
    })
    .forEach(primes::add);
List<Integer> primes = new ArrayList<Integer>();

for(int i=2; i < UPPER_BOUND; i++) {
    boolean isPrime = true;

    for(int j=0; j<primes.size(); j++) {
        int prime = primes.get(j);

        if(prime > Math.sqrt(i)) {
            break;
        }

        if(i % prime == 0) {
            isPrime = false;
            break;
        }
    }

    if(isPrime) {
        primes.add(i);
    }
}
List primes=new ArrayList();
for(int i=2;i<上界;i++){
布尔值isPrime=true;
对于(int j=0;j Math.sqrt(i)){
打破
}
如果(i%prime==0){
isPrime=false;
打破
}
}
如果(iPrime){
加上(i);
}
}
性能比较:

List<Integer> primes = new ArrayList<Integer>();

IntStream.iterate(2, i -> i + 1)
    .limit(UPPER_BOUND)
    .filter(i -> {
        for(int j=0; j<primes.size(); j++) {
            int prime = primes.get(j);

            if(prime > Math.sqrt(i)) {
                break;
            }

            if(i % prime == 0) {
                return false;
            }
        }
        return true;
    })
    .forEach(primes::add);
List<Integer> primes = new ArrayList<Integer>();

for(int i=2; i < UPPER_BOUND; i++) {
    boolean isPrime = true;

    for(int j=0; j<primes.size(); j++) {
        int prime = primes.get(j);

        if(prime > Math.sqrt(i)) {
            break;
        }

        if(i % prime == 0) {
            isPrime = false;
            break;
        }
    }

    if(isPrime) {
        primes.add(i);
    }
}
对每个函数的一些实验一致表明,在这种情况下,传统方法实际上比使用流更快。与传统方法(在我的机器上,平均值分别为106ms和70ms)相比,streams方法始终需要1.5倍的时间才能找到100万以下的所有素数

如果流的.parallel()函数可以轻松地并行化问题,那么这种性能差异可能很容易弥补。然而,在这种情况下并行化并不容易,因为ArrayList不是线程安全的,并且会很快导致错误和/或不准确的结果

结论:

List<Integer> primes = new ArrayList<Integer>();

IntStream.iterate(2, i -> i + 1)
    .limit(UPPER_BOUND)
    .filter(i -> {
        for(int j=0; j<primes.size(); j++) {
            int prime = primes.get(j);

            if(prime > Math.sqrt(i)) {
                break;
            }

            if(i % prime == 0) {
                return false;
            }
        }
        return true;
    })
    .forEach(primes::add);
List<Integer> primes = new ArrayList<Integer>();

for(int i=2; i < UPPER_BOUND; i++) {
    boolean isPrime = true;

    for(int j=0; j<primes.size(); j++) {
        int prime = primes.get(j);

        if(prime > Math.sqrt(i)) {
            break;
        }

        if(i % prime == 0) {
            isPrime = false;
            break;
        }
    }

    if(isPrime) {
        primes.add(i);
    }
}
假设其他答案是正确的,那么在Java中,在同一个流上的过滤器中过滤已经过滤过的数据是不可能的


列表素数可以使用流来处理。然而,在找到比我自己的更好的解决方案之前,目前最好还是坚持传统的无流方法。

我使用埃拉托什筛成功地创建了一个无限的
素数流,但它实际上不使用过去的值。相反,它删除了尾部中一个素数的倍数(以一种惰性的方式,因为尾部是无限的),就像最初的埃拉托斯提尼算法的筛子一样。为此,我使用了一个
迭代器
作为辅助工具(因为
只能使用一次),并为流实现了一个
lazycont

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);
    }
}
然后我们可以这样使用它:

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]


我认为这是一个很好的练习,但它似乎效率很低,而且根本不利于堆栈。

如果流是正确的工具,这是有争议的,但是
.filter()
肯定不是。过滤器应该是无状态的,所以这个想法一开始就不应该出现。根据您答案中的示例,收集器可能是一个可行的解决方案

List<Integer> primes = IntStream.range(2, UPPER_BOUND)
  .collect(ArrayList::new,
          (list, number) -> { 
                for(int j=0; j < list.size(); j++) {
                    int prime = list.get(j);

                    if(prime > Math.sqrt(number)) {
                        break;
                    }

                    if(number % prime == 0) {
                        return;
                    }
                }

                list.add(number);
          },
          List::addAll);
List primes=IntStream.range(2,上限)
.collect(数组列表::新建,
(列表,编号)->{
对于(int j=0;j数学sqrt(数)){
打破
}
如果(数字%prime==0){
返回;
}
}
列表。添加(编号);
},
列表::addAll);
ArrayList::new
创建一个新列表,然后消费者将其引用为
list
。为流中的每个元素调用使用者,元素为
number


List::addAll
只与并行流相关,而并行流无论如何都不能用于此算法。

不,没有比使用单独的集合更好的方法了。Stream API不是为这种用途而设计的。使用现有的代码,您可以将
final
关键字放在
IntStream myStream
之前。但是代码无论如何都是不正确的,因为您需要将第二行更改为
myStream=myStream.filter(…)
,否则您将使用strea