Limit a stream by a predicate

前端 未结 19 3038
别跟我提以往
别跟我提以往 2020-11-21 22:54

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

In Java 9 we can use

相关标签:
19条回答
  • 2020-11-21 23:50

    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();
                }
            };
        }
    }
    
    0 讨论(0)
  • 2020-11-21 23:51

    As a follow-up to @StuartMarks answer. My StreamEx library has the takeWhile operation which is compatible with current JDK-9 implementation. When running under JDK-9 it will just delegate to the JDK implementation (via MethodHandle.invokeExact which 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:

    IntStreamEx.iterate(1, n -> n + 1)
               .takeWhile(n -> n < 10)
               .forEach(System.out::println);
    
    0 讨论(0)
  • 2020-11-21 23:53

    This is the source copied from JDK 9 java.util.stream.Stream.takeWhile(Predicate). A little difference in order to work with JDK 8.

    static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> p) {
        class Taking extends Spliterators.AbstractSpliterator<T> implements Consumer<T> {
            private static final int CANCEL_CHECK_COUNT = 63;
            private final Spliterator<T> s;
            private int count;
            private T t;
            private final AtomicBoolean cancel = new AtomicBoolean();
            private boolean takeOrDrop = true;
    
            Taking(Spliterator<T> s) {
                super(s.estimateSize(), s.characteristics() & ~(Spliterator.SIZED | Spliterator.SUBSIZED));
                this.s = s;
            }
    
            @Override
            public boolean tryAdvance(Consumer<? super T> action) {
                boolean test = true;
                if (takeOrDrop &&               // If can take
                        (count != 0 || !cancel.get()) && // and if not cancelled
                        s.tryAdvance(this) &&   // and if advanced one element
                        (test = p.test(t))) {   // and test on element passes
                    action.accept(t);           // then accept element
                    return true;
                } else {
                    // Taking is finished
                    takeOrDrop = false;
                    // Cancel all further traversal and splitting operations
                    // only if test of element failed (short-circuited)
                    if (!test)
                        cancel.set(true);
                    return false;
                }
            }
    
            @Override
            public Comparator<? super T> getComparator() {
                return s.getComparator();
            }
    
            @Override
            public void accept(T t) {
                count = (count + 1) & CANCEL_CHECK_COUNT;
                this.t = t;
            }
    
            @Override
            public Spliterator<T> trySplit() {
                return null;
            }
        }
        return StreamSupport.stream(new Taking(stream.spliterator()), stream.isParallel()).onClose(stream::close);
    }
    
    0 讨论(0)
  • 2020-11-21 23:55

    Such an operation ought to be possible with 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.

    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 Iterator to have a "take-while" implementation, and then go back to a Spliterator and then a Stream. Or -- maybe -- wrap the Spliterator, though it can't really be split anymore in this implementation.

    Here's an untested implementation of takeWhile on 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);
    }
    
    0 讨论(0)
  • 2020-11-21 23:56

    Update: Java 9 Stream now comes with a takeWhile method.

    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

    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);
        }
    
    }
    
    0 讨论(0)
  • 2020-11-21 23:56

    Actually there are 2 ways to do it in Java 8 without any extra libraries or using Java 9.

    If you want to print numbers from 2 to 20 on the console you can do this:

    IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).allMatch(i -> i < 20);
    

    or

    IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).anyMatch(i -> i >= 20);
    

    The output is in both cases:

    2
    4
    6
    8
    10
    12
    14
    16
    18
    20
    

    No one mentioned anyMatch yet. This is the reason for this post.

    0 讨论(0)
提交回复
热议问题