Java 通过谓词限制流

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20746429/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 04:03:40  来源:igfitidea点击:

Limit a stream by a predicate

javajava-8java-stream

提问by MForster

Is there a Java 8 stream operation that limits a (potentially infinite) Streamuntil the first element fails to match a predicate?

是否有 Java 8 流操作限制(可能无限)Stream直到第一个元素无法匹配谓词?

In Java 9 we can use takeWhileas in the example below to print all the numbers less than 10.

在 Java 9 中,我们可以使用takeWhile下面的示例来打印所有小于 10 的数字。

IntStream
    .iterate(1, n -> n + 1)
    .takeWhile(n -> n < 10)
    .forEach(System.out::println);

As there is no such operation in Java 8, what's the best way of implementing it in a general way?

由于 Java 8 中没有这样的操作,那么以一般方式实现它的最佳方法是什么?

采纳答案by Louis Wasserman

Such an operation ought to be possiblewith a Java 8 Stream, but it can't necessarily be done efficiently -- for example, you can't necessarily parallelize such an operation, as you have to look at elements in order.

这样的操作应该是可能的与Java 8 Stream,但它不一定能高效地完成-例如,你不一定能并行这样的操作,你一定要看看元素的顺序。

The API doesn't provide an easy way to do it, but what's probably the simplest way is to take Stream.iterator(), wrap the Iteratorto have a "take-while" implementation, and then go back to a Spliteratorand then a Stream. Or -- maybe -- wrap the Spliterator, though it can't really be split anymore in this implementation.

API 没有提供一种简单的方法来做到这一点,但最简单的方法可能是 take Stream.iterator(),将 包装Iterator为具有“take-while”实现,然后返回到 aSpliterator和 a Stream。或者——也许——包装Spliterator,尽管在这个实现中它不能再被拆分了。

Here's an untested implementation of takeWhileon a Spliterator:

这是一个未经测试的takeWhileon a实现Spliterator

static <T> Spliterator<T> takeWhile(
    Spliterator<T> splitr, Predicate<? super T> predicate) {
  return new Spliterators.AbstractSpliterator<T>(splitr.estimateSize(), 0) {
    boolean stillGoing = true;
    @Override public boolean tryAdvance(Consumer<? super T> consumer) {
      if (stillGoing) {
        boolean hadNext = splitr.tryAdvance(elem -> {
          if (predicate.test(elem)) {
            consumer.accept(elem);
          } else {
            stillGoing = false;
          }
        });
        return hadNext && stillGoing;
      }
      return false;
    }
  };
}

static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> predicate) {
   return StreamSupport.stream(takeWhile(stream.spliterator(), predicate), false);
}

回答by The Coordinator

Update: Java 9 Streamnow comes with a takeWhilemethod.

更新:Java 9Stream现在带有takeWhile方法。

No needs for hacks or other solutions. Just use that!

不需要黑客或其他解决方案。就用那个!



I am sure this can be greatly improved upon: (someone could make it thread-safe maybe)

我相信这可以得到很大的改进:(也许有人可以使它成为线程安全的)

Stream<Integer> stream = Stream.iterate(0, n -> n + 1);

TakeWhile.stream(stream, n -> n < 10000)
         .forEach(n -> System.out.print((n == 0 ? "" + n : "," + n)));

A hack for sure... Not elegant - but it works ~:D

肯定是一个黑客......不优雅 - 但它有效~:D

class TakeWhile<T> implements Iterator<T> {

    private final Iterator<T> iterator;
    private final Predicate<T> predicate;
    private volatile T next;
    private volatile boolean keepGoing = true;

    public TakeWhile(Stream<T> s, Predicate<T> p) {
        this.iterator = s.iterator();
        this.predicate = p;
    }

    @Override
    public boolean hasNext() {
        if (!keepGoing) {
            return false;
        }
        if (next != null) {
            return true;
        }
        if (iterator.hasNext()) {
            next = iterator.next();
            keepGoing = predicate.test(next);
            if (!keepGoing) {
                next = null;
            }
        }
        return next != null;
    }

    @Override
    public T next() {
        if (next == null) {
            if (!hasNext()) {
                throw new NoSuchElementException("Sorry. Nothing for you.");
            }
        }
        T temp = next;
        next = null;
        return temp;
    }

    public static <T> Stream<T> stream(Stream<T> s, Predicate<T> p) {
        TakeWhile tw = new TakeWhile(s, p);
        Spliterator split = Spliterators.spliterator(tw, Integer.MAX_VALUE, Spliterator.ORDERED);
        return StreamSupport.stream(split, false);
    }

}

回答by Chris Greenaway

Here is a version done on ints - as asked in the question.

这是一个在整数上完成的版本 - 如问题中所问。

Usage:

用法:

StreamUtil.takeWhile(IntStream.iterate(1, n -> n + 1), n -> n < 10);

Here's code for StreamUtil:

这是 StreamUtil 的代码:

import java.util.PrimitiveIterator;
import java.util.Spliterators;
import java.util.function.IntConsumer;
import java.util.function.IntPredicate;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;

public class StreamUtil
{
    public static IntStream takeWhile(IntStream stream, IntPredicate predicate)
    {
        return StreamSupport.intStream(new PredicateIntSpliterator(stream, predicate), false);
    }

    private static class PredicateIntSpliterator extends Spliterators.AbstractIntSpliterator
    {
        private final PrimitiveIterator.OfInt iterator;
        private final IntPredicate predicate;

        public PredicateIntSpliterator(IntStream stream, IntPredicate predicate)
        {
            super(Long.MAX_VALUE, IMMUTABLE);
            this.iterator = stream.iterator();
            this.predicate = predicate;
        }

        @Override
        public boolean tryAdvance(IntConsumer action)
        {
            if (iterator.hasNext()) {
                int value = iterator.nextInt();
                if (predicate.test(value)) {
                    action.accept(value);
                    return true;
                }
            }

            return false;
        }
    }
}

回答by Dominic Fox

takeWhileis one of the functions provided by the protonpack library.

takeWhileprotonpack 库提供的功能之一。

Stream<Integer> infiniteInts = Stream.iterate(0, i -> i + 1);
Stream<Integer> finiteInts = StreamUtils.takeWhile(infiniteInts, i -> i < 10);

assertThat(finiteInts.collect(Collectors.toList()),
           hasSize(10));

回答by user2504380

I have another quick solution by implementing this (which is rly unclean in fact, but you get the idea):

我通过实现这个有另一个快速解决方案(实际上这是不干净的,但你明白了):

public static void main(String[] args) {
    System.out.println(StreamUtil.iterate(1, o -> o + 1).terminateOn(15)
            .map(o -> o.toString()).collect(Collectors.joining(", ")));
}

static interface TerminatedStream<T> {
    Stream<T> terminateOn(T e);
}

static class StreamUtil {
    static <T> TerminatedStream<T> iterate(T seed, UnaryOperator<T> op) {
        return new TerminatedStream<T>() {
            public Stream<T> terminateOn(T e) {
                Builder<T> builder = Stream.<T> builder().add(seed);
                T current = seed;
                while (!current.equals(e)) {
                    current = op.apply(current);
                    builder.add(current);
                }
                return builder.build();
            }
        };
    }
}

回答by Michael Rowley

allMatch()is a short-circuiting function, so you can use it to stop processing. The main disadvantage is that you have to do your test twice: once to see if you should process it, and again to see whether to keep going.

allMatch()是一个短路功能,因此您可以使用它来停止处理。主要的缺点是你必须做两次测试:一次是看你是否应该处理它,另一次是看是否继续。

IntStream
    .iterate(1, n -> n + 1)
    .peek(n->{if (n<10) System.out.println(n);})
    .allMatch(n->n < 10);

回答by frhack

You can use java8 + rxjava.

您可以使用 java8 + rxjava

import java.util.stream.IntStream;
import rx.Observable;


// Example 1)
IntStream intStream  = IntStream.iterate(1, n -> n + 1);
Observable.from(() -> intStream.iterator())
    .takeWhile(n ->
          {
                System.out.println(n);
                return n < 10;
          }
    ).subscribe() ;


// Example 2
IntStream intStream  = IntStream.iterate(1, n -> n + 1);
Observable.from(() -> intStream.iterator())
    .takeWhile(n -> n < 10)
    .forEach( n -> System.out.println(n));

回答by climbing_bum

Here is my attempt using just Java Stream library.

这是我仅使用 Java Stream 库的尝试。

        IntStream.iterate(0, i -> i + 1)
        .filter(n -> {
                if (n < 10) {
                    System.out.println(n);
                    return false;
                } else {
                    return true;
                }
            })
        .findAny();

回答by Stuart Marks

Operations takeWhileand dropWhilehave been added to JDK 9. Your example code

操作takeWhiledropWhile已添加到 JDK 9。您的示例代码

IntStream
    .iterate(1, n -> n + 1)
    .takeWhile(n -> n < 10)
    .forEach(System.out::println);

will behave exactly as you expect it to when compiled and run under JDK 9.

在 JDK 9 下编译和运行时,它的行为将完全符合您的预期。

JDK 9 has been released. It is available for download here: http://jdk.java.net/9/

JDK 9 已经发布。可在此处下载:http: //jdk.java.net/9/

回答by Tagir Valeev

As a follow-up to @StuartMarks answer. My StreamExlibrary has the takeWhileoperation which is compatible with current JDK-9 implementation. When running under JDK-9 it will just delegate to the JDK implementation (via MethodHandle.invokeExactwhich is really fast). When running under JDK-8, the "polyfill" implementation will be used. So using my library the problem can be solved like this:

作为@StuartMarks answer的后续行动。我的StreamEx库具有takeWhile与当前 JDK-9 实现兼容的操作。在 JDK-9 下运行时,它只会委托给 JDK 实现(通过MethodHandle.invokeExact它真的很快)。在 JDK-8 下运行时,将使用“polyfill”实现。所以使用我的库,问题可以这样解决:

IntStreamEx.iterate(1, n -> n + 1)
           .takeWhile(n -> n < 10)
           .forEach(System.out::println);